Antigravity Multi-Agent State Tiers — A Three-Layer Design with Ephemeral, Journal, and Canonical
Before your Antigravity Background Agents and Sub-agents start mixing up their memory, split agent state into three lifetimes — ephemeral, journal, canonical — and map each to the right Cloudflare store. Includes the write-back boundary rules I rely on across a 12-year wallpaper-app portfolio.
One morning, an Antigravity Background Agent forgot the previous day's AdMob review and emailed me the same warning three times in a row. Following the run logs, the memory was technically present, but the restore key was off by a few characters and the agent could not read it back. I'm Masaki Hirokawa (Dolice), an indie developer building iOS and Android apps since 2014 and an artist running daily operations through editor-integrated AI agents like Antigravity. After a few rounds of "the agent forgot too much" and "the agent remembered too much," I landed on a single rule: agent state has to be split into three lifetimes, or it will eventually break.
This piece is a practical write-up of those three lifetimes — ephemeral, journal, canonical — together with how I map them onto Cloudflare Workers stores (KV, Durable Objects, R2, D1) and how I draw the write-back boundaries. The patterns come out of running a 50M-download wallpaper-app portfolio for 12 years and orchestrating a small fleet of Background, Sub, and Browser Sub-agents alongside it. If you are running more than one Antigravity agent in production, the boundaries below should give you a frame to sharpen your own design decisions.
The same Antigravity agent holds information with three very different lifespans
When you call everything "agent memory," it eventually breaks. The split I have settled on is:
Tier
Lifetime
Damage if lost
Examples
ephemeral
1 run
near zero (recomputable)
intermediate prompts, raw LLM output, file snapshots
published slugs, purchase records, membership state
The deciding factor is not byte size but blast radius. A 200KB chain-of-thought can be ephemeral. A 60-byte "we already published this slug" record absolutely cannot be ephemeral.
When you internalize the split, agent code only needs to ask three questions:
Can the next run recompute this? If yes — ephemeral.
If not, can a human accept "we have to redo it"? If yes — journal.
If losing it breaks the user-facing product — canonical.
That is the entire rubric. The tiers map to different physical stores, but agent code talks to all three through a single adapter shape.
Ephemeral — working memory that lives and dies inside one agent run
Ephemeral is everything that is born when an agent run starts and dies when it ends: scratch prompts, raw LLM responses, intermediate file listings, retrieval results, in-flight reasoning. Because the next run can rebuild it cheaply, there is almost no value in writing it to a shared store like KV.
In my Antigravity Background Agents this tier lives in plain JavaScript memory — a Map scoped to the run entry function.
// state/ephemeral.tsexport class EphemeralStore { private bucket = new Map<string, unknown>(); get<T = unknown>(key: string): T | undefined { return this.bucket.get(key) as T | undefined; } put<T = unknown>(key: string, value: T): void { this.bucket.set(key, value); } // Always call this at the end of a run. Disposal is explicit. dispose(): void { this.bucket.clear(); }}
The explicit dispose() is intentional. I want ephemeral to be actively thrown away, not silently abandoned. If you let an intermediate LLM response leak into the journal or canonical tier by accident, your storage bill rises without bound. A single Background Agent in my fleet handles roughly 200–800 KB of ephemeral data per run; written naively into KV that adds up to gigabytes per month, far above the ¥800/month budget I keep per site.
One implementation gotcha worth flagging: Cloudflare Workers can reuse the same Isolate across requests, so a Map declared at the module scope will silently persist between runs. I fell into that exact trap once. The fix was to make sure EphemeralStore is always new-ed inside the run entry function. Before pushing this pattern into production, write a test that deliberately fires two runs against the same Isolate to confirm the store is fresh each time.
✦
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
✦Split agent state into ephemeral / journal / canonical so a single Background Agent run can fail without taking your product down
✦Concrete Cloudflare mapping — KV / Durable Objects / R2 / D1 — with a small TypeScript adapter you can drop into a Worker today
✦Three operational rules I have kept across 12 years of running an indie wallpaper-app portfolio of 50M+ downloads
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.
Journal — days to weeks of decision logs that you could re-derive, but slowly
Journal is for information that you can technically reconstruct, but only at meaningful cost in time or money. Agent decision rationales, A/B intermediate scores, Browser Sub-agent observations of an AdMob dashboard delta, automatic-repair logs — all journal.
The test for journal is one question: "If we lost this, could we live without it but only at a half-day cost?" Across a 50M-download wallpaper-app portfolio over 12 years, I have had multiple incidents where missing journal data meant burning a day or two replaying agent decisions. Today I keep that tier seriously.
Cloudflare KV is a near-perfect physical match — cheap reads, metered writes, eventual consistency, all of which suit "write rarely, read sometimes, freshness is loose."
Two operational notes. First, always prefix journal keys with journal: even if you do not share KV with canonical today — your future self will thank you when the boundary moves. Second, always attach a TTL. Without one, agents start dragging months of context into their next decision and begin to behave erratically. I have made that mistake twice across this fleet and now hard-code 30 days for everything.
At a small-scale operation (~100k writes / ~1M reads per month), the journal tier on KV bills around ¥50 / month, easily inside the ¥800/month per-site envelope.
Canonical — the source of truth your product cannot survive without
Canonical is the information whose loss ends the product. Published article slugs, Stripe purchases, premium membership records, monthly AdMob revenue rollups (technically refetchable, but throttled by API limits) — canonical.
My personal rule is strict: the agent never owns canonical. Canonical lives in the surrounding system, and the agent only reads or writes back to it. The moment an agent thinks it owns canonical, an agent bug becomes a product-wide outage.
For physical mapping I lean on two choices depending on the shape of the data.
Shape
Store
Why
Row-oriented, transactional
Cloudflare D1 (or an upstream API like Stripe)
strong consistency + SQL for audits
Document-oriented, concurrent writes
Cloudflare Durable Objects
strong ordering, one instance per key
Large, read-heavy
Cloudflare R2
monthly snapshot archive
A concrete case: in Antigravity Lab, the canonical list of published slugs is the GitHub repo itself. The agent clones it, reads it, and writes back only via the GitHub API. The agent's KV and DO are journal-tier; once the change lands in Git, they can be thrown away.
Here is a minimal Durable Object adapter for the case of "concurrent purchase grants" — the kind of canonical write where two requests can hit the same key at the same time.
Keeping canonical outside the agent buys you something priceless: the freedom to throw the agent away and rebuild. An agent that owns canonical is one you are scared to redeploy.
Mapping three tiers onto Cloudflare stores — KV / DO / R2 / D1
I decide the mapping with three axes:
Lifetime: 1 run / dozens of days / permanent
Concurrent writes: can multiple agents hit the same key at the same time?
Query shape: single-key read, prefix scan, or SQL/joins?
That collapses into a near-mechanical decision tree.
1 run, no concurrency, single-key reads → ephemeral (in-memory Map).
Early on I dumped everything into KV and accidentally crossed agent boundaries five or six times due to missing prefixes. Switching to the three-axis decision rule effectively eliminated state-tier bugs at design time. I recommend forcing yourself through these three questions every time you add a new state key.
Boundary design — where to write back, where to snapshot
Splitting the tiers is not enough; you also have to decide where information crosses between tiers. In my fleet there are three boundaries worth naming.
Step 1. ephemeral → journal (write-down)
Only the decisions worth replaying should land in journal — typically the moments an agent branched on a non-trivial choice. Writing every intermediate value bloats the journal; I keep the rule "log a journal entry only when the agent made a branching decision."
Step 2. journal → canonical (write-back)
This is the boundary you must design most carefully. I strongly recommend a human-in-the-loop checkpoint here, or at minimum an out-of-band notification (Slack, stand.fm, email). I have had an agent decide on its own to flip 12 premium articles back to free in a single batch, and the recovery took half a day. A 5-second confirmation delay or a single approval click would have caught it.
Step 3. canonical → journal (read-back snapshot)
The reverse flow is just as important. Periodically snapshotting canonical into journal keeps the agent from spamming canonical reads (and helps you stay under GitHub's 5,000 req/hour ceiling). An hourly snapshot is usually enough.
Once you spell these three boundaries out in code, the agent reads dramatically more clearly. Leave them implicit, and you will not be able to tell whether a given function is touching ephemeral or canonical without a 10-minute audit.
Three operational rules from 12 years of indie wallpaper apps
A few rules I refuse to break, drawn from running a 50M-download wallpaper portfolio for over 12 years.
Always put either a 1-second delay or an approval step in front of canonical writes. Recovering from a runaway canonical write averages half a day, sometimes two. A 5-second confirmation buffer is free. My AdMob revenue rollup agent uses that exact pattern.
When in doubt, give journal a 30-day TTL. Extending later is trivial; capping an untimed journal after the fact is the storage equivalent of memory loss. Every journal scope in my Background Agents is locked to 30 days.
Never silently drop ephemeral. Provide an explicit dispose() and call it. That single rule wiped out my Isolate-reuse and memory-leak issues. After one painful incident where a Browser Sub-agent ballooned in memory for three days and crashed, I refuse to ship an ephemeral store without it.
All three are about pre-committing to where agent failures are allowed to live — and that is the underlying claim of this whole piece. Agents will misbehave; the design job is to keep their failures inside the smallest tier possible.
The next time you stand up an Antigravity Background Agent, write a one-line comment at the top: "this agent touches ephemeral / journal / canonical." Half of your design review is already done. I would love to hear how this maps onto your own fleet. Thanks for reading.
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.