ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-09-01Intermediate

Write Hundreds of Files in One Burst, or Space Them Out?

When an agent generates hundreds of files, the slowness usually is not the writing. It is the rescans on the watcher side. I measured four write patterns so you can decide where to batch.

agent operations7file watchingperformance13Node.js6workspace design

Premium Article

The writing itself finished in 33 milliseconds.

That is the measured time for a routine that generates 400 files. The wait felt considerably longer than that, so the number made me suspect the delay was not happening on the side that writes the files. It was happening on the side that watches them.

Handing bulk generation to an agent is routine work now. Emitting article HTML. Exporting every icon size. Running a codemod across a few hundred files. All of them share the same experience: the write completes, and then things stay sluggish for a while.

I measured where that sluggishness comes from by varying how the files get written. Two of the results contradicted what I had expected going in.

The slow part is what happens behind the write

When you write a file, a notification goes out to every process watching that directory. Your editor, the dev server, the test runner, the agent's workspace index. Having several watchers on one directory at the same time is the normal state of affairs, not an unusual one.

The receiving side typically waits briefly before acting. The point of that pause is to collapse a run of consecutive notifications into a single rescan. That waiting period is the debounce window, and it is commonly set somewhere around 100 milliseconds.

Which means the amount of work you create for the other side is not decided by how many files you wrote. It is decided by whether your notifications landed inside the debounce window.

To measure that, write duration alone is not enough. You need to count how many rescans a watcher with a 100 millisecond window would end up starting.

The measurement script

This uses Node.js fs.watch to attach a watcher, then records event timestamps while varying the write pattern. Treating the debounce window as 100 milliseconds, every gap longer than that counts as one more rescan.

// bench.mjs — measure how the write pattern changes file-watch events
// usage: node bench.mjs <inplace|stage> <fileCount> <gapMs> <recursive:true|false>
//   e.g. node bench.mjs inplace 400 0 true
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
 
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const BODY = 'x'.repeat(2048); // 2KB per file; contents are not what we are measuring
const DEBOUNCE_NS = 100n * 1000000n; // 100ms in nanoseconds, to match hrtime.bigint
 
async function run(mode, count, gapMs, recursive) {
  // Rebuild the watched tree every run. Leftovers from a previous run skew the counts.
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'watchbench-'));
  const target = path.join(root, 'out');
  fs.mkdirSync(target);
 
  let events = 0;
  const stamps = [];
  const watcher = fs.watch(target, { recursive }, () => {
    events++;
    stamps.push(process.hrtime.bigint());
  });
 
  const t0 = process.hrtime.bigint();
 
  if (mode === 'inplace') {
    // Write straight into the watched directory
    for (let i = 0; i < count; i++) {
      fs.writeFileSync(path.join(target, `f${i}.txt`), BODY);
      if (gapMs) await sleep(gapMs);
    }
  } else {
    // Build outside the watched tree, then move it in as one operation
    const stage = path.join(root, 'stage');
    fs.mkdirSync(stage);
    for (let i = 0; i < count; i++) {
      fs.writeFileSync(path.join(stage, `f${i}.txt`), BODY);
    }
    fs.renameSync(stage, path.join(target, 'batch'));
  }
 
  const t1 = process.hrtime.bigint();
 
  // Watch events arrive after the writes finish. Closing too early drops them.
  await sleep(1200);
  watcher.close();
 
  // Gaps wider than the debounce window = rescans the other side would start
  let rescans = 0;
  let last = null;
  for (const s of stamps) {
    if (last === null || s - last > DEBOUNCE_NS) rescans++;
    last = s;
  }
 
  console.log(
    JSON.stringify({
      mode,
      count,
      gapMs,
      recursive,
      events,
      rescans_100ms: rescans,
      write_ms: +(Number(t1 - t0) / 1e6).toFixed(1),
    })
  );
 
  fs.rmSync(root, { recursive: true, force: true });
}
 
const [mode, count, gapMs, recursive] = process.argv.slice(2);
await run(mode, Number(count), Number(gapMs), recursive === 'true');

One note on await sleep(1200). Watch events land after the write call returns. Calling watcher.close() immediately after writing drops the events still in flight and gives you a count lower than reality. My first numbers did not add up for exactly this reason, and it took me a while to see why.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You will be able to tell whether a slow bulk-generation step is bottlenecked on writing or on the watchers observing your workspace
You will recognize, before you hit it, the condition under which slowing down your writes to be gentle actually makes things worse
You will be able to decide where generated output belongs in your workspace, taking the watcher's recursion mode into account
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

Agents & Manager2026-07-16
The More I Wrote in AGENTS.md, the Less Got Followed — Measuring Adherence and Cutting Rules
The rules in my AGENTS.md were being ignored — not from precedence conflicts or load failures, just plain ignored. Here is how I turned rules into checkable predicates, measured adherence over three weeks, and cut the file in half.
Agents & Manager2026-07-04
Where to Put Evidence and Approval When Your Agent Self-Debugs in a Real Browser
Antigravity 2.0 launches a real Chrome mid-build, clicking buttons and taking screenshots to self-heal. It is fast, but shipping that as-is is risky. Here is how to capture evidence and draw the approval boundary.
Agents & Manager2026-05-20
Prompt Caching and Context Strategy for Antigravity Agents — Cutting 60-80% Off Monthly API Costs in Long-Running Production
The longer you keep agents running, the more the monthly invoice quietly piles up. Running Antigravity agents alongside an AdMob-monetized indie app business (50M cumulative downloads), I managed to cut API costs by 60-80% by rebuilding prompt caching and context strategy. This article shares the three-layer cache, context compression, and TTL design I now run in production — with the code and numbers behind them.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →