Autonomous engagement & retention agent · diagram deck

Stagenator

An autonomous agent that runs three live mobile games. It runs unattended: it detects who is playing right now, decides what would help, generates new levels with AI and ships them into the games, gifts each player their own promo code, and reviews each day's results overnight. Ten diagrams, simplest first.

These are live store apps you can install: Subliminal Words (App Store · Google Play), AI Movie Quiz (App Store · Google Play) and Palindrome (App Store · Google Play). Everything below acts on them.

input / who's playing AI — decides or creates plain code doing work safety limit saved data outside service
Flow · overview

1 · The whole thing in one loop

Players play; the analytics show it; Stagenator responds with fresh levels and gifts; those give players a reason to come back. That loop, running unattended, is the product.

Players 3 live games Analytics who is playing now Stagenator decides + acts Levels + codes appear in the games plays tells it ships new reasons to come back Players3 live gamesplaysAnalyticswho is playing nowtells itStagenatordecides + actsshipsLevels + codesappear in the gamesnew reasons to come back

◂ swipe the diagram sideways ▸

four boxes run left to right — Players, Analytics, Stagenator, and the Levels + codes it ships — then the loop closes back to Players. Only the Stagenator box thinks: it notices someone is playing and turns that into something new for them. The other three are just the world around it — who is here, and what reaches them. The rest of this page opens up how that loop works — most of it inside the Stagenator box, but also how it senses players, ships content, gifts codes and where it all runs.
Flow · the heartbeat

2 · It checks in every five minutes

Most check-ins find nothing worth doing and cost nothing — choosing to do nothing is a normal, correct outcome. The AI is only woken up when there is a reason.

Timer every 5 min Look around plain code, no AI anyone playing? Do nothing costs $0 AI decides what would help Safety limits 1 level · 1 code /day Do it levels · codes · push Ledger — every step logged no yes Timerevery 5 min Look aroundplain code, no AI anyone playing? no Do nothingcosts $0 yes AI decideswhat would help Safety limits1 level · 1 code /day Do itlevels · codes · push Ledger — every step logged

◂ swipe the diagram sideways ▸

the AI is only woken when a signal arrives — nobody chats with it. Whatever it proposes must still pass the hard limits before anything happens: at most 1 new level and 1 code gift per game per day, and at most 2 notifications per 4 hours. Every decision is written to the ledger on the dashboard, including the decision to do nothing. And when the day’s budgets are already spent, code notices before the AI is even woken — even its restraint is code. The pulse path is detect(): it polls the GA4 Data API (realtime) for active users and reads code inventory — pure code, no LLM. A guardrail pre-check short-circuits to idle when the day's budgets are already spent, so the model is never invoked with nothing to do. Only when a signal survives does it call the Strategist, whose proposed actions pass guardrails.gate_and_enqueue before any reach the queue.
Idea · the core principle

3 · Code does the doing, AI does the thinking — and the creating

Everywhere money moves or the games change, it is ordinary, testable code. AI shows up in two roles: two decision calls (what to do now, what to learn tonight) — and as the creator inside the pipelines, where it designs levels, paints images, films clips and inspects its own work. Code always starts, checks and limits it.

PLAIN CODE — PREDICTABLE · TESTED AI — DECIDES · CREATES Crash-proof to-do list Hard daily limits Handing out promo codes All-or-nothing saves Level pipelinesAI creates inside, code drives Minting · push · email Apple · notify · Gmail Strategist what to do right now Reflector what to learn, overnight 2 decision calls — answer a fixed form, always checked by the safety limits PLAIN CODE — PREDICTABLE · TESTEDCrash-proof to-do listHard daily limitsLevel pipelinesAI creates inside, code drivesHanding out promo codesAll-or-nothing savesMinting · push · emailApple · notify · GmailAI — DECIDES · CREATESStrategistwhat to do right nowReflectorwhat to learn, overnight2 decision calls — always checked by the limits

◂ swipe the diagram sideways ▸

the AI never touches money or the games directly. When it decides, it fills in a fixed form and tested code carries the decision out. When it creates, it works inside a pipeline that code starts, quality-checks, and can throw away. In both cases the safety limits have the last word. The two decision calls are ADK LlmAgents with a locked output schema — the model returns a validated JSON object (chosen actions + reasons), never free-form tool calls. Deterministic code (guardrails.validate) checks every action against the hard caps and enqueues only what passes; execution, minting, saves and deletes are all plain Python. So the model's blast radius is "which of a fixed set of safe actions, when" — never "whatever it decided to call".
Idea · the obvious question

Why not more agents, or a tool-calling agent?

Because of what is at stake. The agent's actions mint codes with monetary value, push to strangers' phones, and publish into store apps — unattended. For a small, fixed, high-stakes action menu, "the model fills in a form, tested code executes behind hard limits" beats handing the model tools: the worst case is which of five safe things, when — never whatever the model decided to call.

Two things worth knowing. First, this is the framework’s own recommended pattern for a case like this: Google’s ADK ships the workflow graph and the fixed-form AI answer as built-in features, and Google’s own expense-agent example uses the same split. Second, there are about eleven specialist AI roles under the surface — deciders, three level designers, visual inspectors, a content-safety screener, an error diagnostician — but they are coordinated by code, not by a “manager AI”. A manager AI is exactly the part that drifts off course or gets tricked by malicious text, so it was left out on purpose. The division of labor is meant to grow on evidence: if the graded test suite shows one role overloaded, that role can be split. Even the cleanup crew is code — the routine that removes old records runs on plain rules, because deleting data is the last job to hand to something that can invent.
Map · how it wakes up

4 · One brain, four doors in

The same program serves four different wake-up calls, all on a schedule. A router reads which one arrived and sends it down the right path.

Router 5-minute checklook → decide → act Nightly reviewlearn from the day Daily restocktop up promo codes Daily health checktests every connection Routerpicks ONE door each time5-minute checklook → decide → actNightly reviewlearn from the dayDaily restocktop up promo codesDaily health checktests every connection

◂ swipe the diagram sideways ▸

once a day, and after every deploy, the health check calls every service the agent depends on. If anything got worse since the last check, the owner gets an email. Each run also stops itself well before the platform’s 9-minute time limit — any leftover work simply waits for the next check. There is one program, not four — a single ADK 2.0 Workflow Graph running on one Cloud Run service. The four Cloud Scheduler jobs all call that same service; each just sends one word saying which kind of run it is: pulse, nightly, replenish, or health. The router is the graph's first node (a function called dispatch()): it reads that word and sends the run down the matching branch — anything unexpected safely defaults to pulse. Each branch is its own mini-pipeline: pulse looks for active players and decides what to do, nightly reviews the day and rewrites the playbook, replenish tops up promo codes, health tests every dependency. The router is plain code picking a branch by name — no AI is involved in the routing itself.
Timeline · step by step

5 · From "someone is playing" to a new level on their phone

The full chain when a check-in finds a player and decides to act — read top to bottom.

Timer Agent Analytics + AI Safety limits AI art / video Game DB Player check in who's playing? 1 player · iOS · US AI: what should we do? "ship a welcome level" within limits → put on the to-do list make the level art / video result → AI double-checks it looks right save to the game — all-or-nothing, then re-verified new level sometimes: a separate code-gift decision too (picture 8) Timer checks inevery 5 minutes1Agent asks analyticswho is playing right now?2Answer comes back1 player · iOS · US3AI decides“ship a welcome level”4Safety limits agreewithin today’s budget5Make the levelAI art / video6AI inspects its own worklooks right?7Save to the gameall-or-nothing, re-verified8New level on their phone9sometimes: a code gift tooits own decision · picture 8

◂ swipe the diagram sideways ▸

the quality check happens before anything is saved. A level only reaches the game after the AI has looked at its own output and approved it — and for the hidden-word game, the published solution is checked once more after saving. A code gift is a separate decision with its own daily limit: it can come along with a level, or not happen at all.
States · what failure looks like

6 · What happens to one job

Every action is a job on a crash-proof to-do list. Failure is expected and contained: try again a few times, then give up loudly — never loop forever, never lose the job.

waiting claimed running done failed gave upemails the ownerwith a diagnosis postponed worked under 3 tries — back in the queue 3rd not due yet / out of time waitingclaimclaimedrunningworkeddonefailedunder 3 tries3rdgave upemails the ownerpostponednot due yet

◂ swipe the diagram sideways ▸

three rules keep this predictable. If the agent crashes in the middle of a job, that is not a failure — the job is simply picked up again. If the agent gives up on a job, the day’s budget is not used up: the player got nothing, so the agent is allowed to try again. And giving up is never silent — an email lands in the owner’s inbox within minutes, with the agent’s own guess at what went wrong. A job is a task document in a Firestore-backed durable queue, addressed by an idempotency key = hash(type, game, stable-payload), so overlapping pulses can't double-enqueue the same intent. It runs as a lease-based state machine: pending → claimed (an optimistic-concurrency transaction writes a random lease token and bumps attempts) → running → done; a genuine error bumps failures and returns it to pending; failures ≥ 3 → dead-letter (CRITICAL log + diagnostic email). Crash-safety: a running task whose lease goes stale (>15 min) is re-leased, and infra kills bump attempts but never failures — so a crash/timeout loop can never burn a task's retry budget or a daily cap. "Postponed" = not-due-yet or stopped before the ~9-min deadline, returned to pending without counting as a failure.
Flow · three games, one recipe

7 · How a level is actually made

Text becomes an image in one game and a short film (with sound) in another — and then the AI switches roles and looks at its own output: vision judges the picture, video understanding watches the clip. The third game, Palindrome, is the opposite extreme: no media to inspect, so correctness is proven in plain code and the model only curates and writes the hints.

SUBLIMINAL WORDS — a word hidden inside a picture Designword + scene Hide the wordletters in the art Paint itAI image gen Inspectvision looks at it Save to gamethen re-verified AI MOVIE QUIZ — guess the film from an 8-second AI clip Pick a film+ write the scene Film itAI video gen Preparefor the game Inspectwatches the clip Save to gamewith an undo fails inspection → throw it away, design something new fails → undo the save, try a fresh film PALINDROME — unscramble a phrase that reads the same both ways Proposefresh candidates Check in codereally a palindrome? Screenkid-safe? Choose + hint18 languages Save to gameno media to inspect SUBLIMINAL WORDSDesignword + sceneHide the wordletters in the artPaint itAI image genInspectvision looks at itSave to gamethen re-verifiedAI MOVIE QUIZPick a film+ write the sceneFilm itAI video genPreparefor the gameInspectwatches the clipSave to gamewith an undofails → design freshfails → undo, retryPALINDROMEProposefresh candidatesCheck in codereally a palindrome?Screenkid-safe?Choose + hint18 languagesSave to gameno media to inspect

◂ swipe the diagram sideways ▸

the inspector judges the finished result, not the instructions that produced it. The image is examined with AI vision, and the video clip is actually watched — a clip marked “dialogue” must even contain a spoken line. The words and titles are guarded too: Movie Quiz only picks film titles the in-game keyboard can type, and Subliminal Words only uses clean words of 3–8 letters. A level that is impossible to solve can never ship. Palindrome needs no vision at all: a phrase is checked in code to read the same backwards, screened for a children’s audience, and matched against the ~900 already in the game. And one thing the agent deliberately cannot do: remove content. If a bad level ever slips through, taking it down is a human decision made in the admin dashboard — content going up passes four checks; content coming down passes one human. Subliminal Words: Gemini 3.7 Flash designs the word+scene, code builds the letter layout + ControlNet mask, Runpod/ComfyUI ControlNet renders the image, then Gemini vision QA inspects it. AI Movie Quiz: Gemini picks the film, Veo 3.1 generates the 8-second clip, the game's own processUploadedVideo callable does ffmpeg/watermark/thumbnail, then Gemini video-understanding QA watches it. Palindrome: candidates from r/palindromes + model proposals, verified in code (reads the same reversed), screened by is_suitable, then judge curates and writes 18-language hints. Every save is an all-or-nothing transaction; Subliminal Words additionally re-checks the published solution after the write.
Flow · the gift promise

8 · A gift code that is yours and yours only

Two ways to deliver, one promise: every code that leaves the shelf is tied to exactly one person. There is no link where two people could grab the same code. Both paths land on proffer.codes — our own claim site, wired into the same system.

SUBLIMINAL WORDS — personal delivery (the game knows its devices) Devicesknown per player Reserve 1 eachone-time link Notify them They claim itreserved for them alone AI MOVIE QUIZ — shared drop (first come, first served) Two shelvesApple · Google One drop linknotify everyone Which phone?iPhone · Android Take one codedistinct · right store iPhone gets an App Store code · Android gets a Google Play code · each visitor can take one SUBLIMINAL WORDS — personalDevicesknown per playerReserve 1 eachone-time linkNotify themThey claim itreserved for them aloneAI MOVIE QUIZ — shared dropTwo shelvesApple · GoogleOne drop linknotify everyoneWhich phone?iPhone · AndroidTake one codedistinct · right storeiPhone → App Store code · Android → Play codeeach visitor can take one

◂ swipe the diagram sideways ▸

taking a code is an all-or-nothing database step, so two people clicking the last code at the same instant can never both get it. Reservations that nobody claims are returned to the shelf by a daily clean-up. And the claim page doubles as cross-promotion: a player who comes for one game’s gift sees the other games’ available codes right there — every gift advertises the rest of the portfolio. Both paths run through proffer.codes' claimByToken callable, which tears one code inside a Firestore transaction (all-or-nothing, so no double-spend). Each visitor gets a stable identity via Firebase Anonymous Auth, and App Check gates the callable. Personal delivery reserves one code per known device; the shared drop hands each visitor a distinct code for their store (App Store vs Play). Unclaimed reservations return to the pool via a daily sweep.
Loop · it improves itself

9 · It learns while everyone sleeps

Once a night, the AI reads what the day's actions achieved — codes claimed, notifications opened or ignored, money earned — and rewrites its own playbook. Tomorrow's decisions start from tonight's lessons. It also runs its own A/B experiments: the AI can write a push notification's copy in two versions with different hooks; the system alternates them between recipients (between days, for shared drops) and counts codes claimed per version, so the nightly review learns which style of writing works.

The day, summed upresults, not raw dumps Reflectorwhat worked? Playbookrewritten · size-capped Strategisttomorrow rewrite read on every check-in — the loop closes The day, summed upresults, not raw dumpsReflectorwhat worked?Playbookrewritten · size-cappedrewriteStrategisttomorrowread on every check-in

◂ swipe the diagram sideways ▸

two safeguards keep the learning sane. With too little data, the Reflector deliberately changes nothing, so one lucky or unlucky day cannot swing the whole strategy. And the playbook has a hard size cap, so months of learning can never bloat the AI’s memory or its costs. The A/B results feed the same loop: the winning notification style becomes a playbook lesson, so the agent’s writing improves from its own measured evidence — not from anyone’s opinion. The nightly path assembles a compact, aggregated 24h context (results, not raw event dumps), passes it to the Reflector LlmAgent, which rewrites a single size-capped playbook document the Strategist reads on every pulse. With too little data it changes nothing (no over-fitting to one lucky day). A/B push variants are tracked per-version through the same claim funnel, so the winning style becomes a measured playbook lesson.
Map · where it all runs

10 · Where it all lives

One small cloud service at the centre — asleep and free whenever there is nothing to do. Timers wake it; Google's data services hold its state and analytics; a few outside services handle generation and delivery.

GOOGLE CLOUD — the agent's home Timers four schedules The agent one Cloud Run service sleeps for free Firestore its memory Analytics who plays now History play + earnings Secret keys never in code wakes it reads + writes OUTSIDE — external services AI text + video designs levels, makes clips AI images paints the puzzles Apple promo codes minted over an API Phone notifications reaches the players Gmail Google Play restocks proffer.codes gifts claimed · cross-promo acts: generate · mint · notify · email GOOGLE CLOUDTimers5-min · nightly · +2 dailywakes itThe agentCloud Run · sleeps freereads + writesFirestoreits memoryAnalyticswho plays nowHistoryplay + earningsSecret keysnever in codethe agent actsOUTSIDE — external servicesAI text + videodesigns · filmsAI imagespaints puzzlesApple codesminted via APINotificationsreach playersGmailPlay restocksproffer.codesgifts + cross-promo

◂ swipe the diagram sideways ▸

running this costs pennies. The service sleeps when nobody needs it, and the only meaningful spend is a small prepaid balance for AI image and video generation — which the agent watches itself and emails about when it runs low. Apple promo codes are minted fully automatically. Google Play offers no such API, so the agent emails the owner exact instructions and imports the reply. That is the one human touchpoint in the system, and it exists by Google’s design, not ours. Cloud Run (the agent service) · Cloud Scheduler (4 cron triggers) · Vertex AIGemini 3.7 Flash + Veo 3.1 · Firestore native (queue, ledger, playbook, state) · Cloud Storage / Firebase Storage (generated media) · BigQuery (GA4 export / history) · Google Analytics 4 Data API (realtime signal) · Secret Manager (credentials) · IAM service account (identity) · Cloud Logging · Firebase Cloud Messaging (push) · Firebase Hosting (dashboard + proffer.codes) · Firebase Auth + App Check (claim security) · Gmail SMTP (alerts + Play restock) · built and run with Google ADK / agents-cli. The only non-Google services: Runpod/ComfyUI (ControlNet image generation) and Apple App Store Connect API (promo codes).
Idea · built to grow

11 · One agent, a whole portfolio

Everything on this page except the level-making is shared machinery, keyed by a per-game settings entry: watching players, deciding, the safety limits, the job queue, code gifts on proffer.codes, the A/B experiments, health checks and nightly learning. Adding a game costs one settings entry plus — at most — one content pipeline.

Palindrome was the live proof of this — a third game brought online mid-project on one config entry and one small text pipeline, sharing everything else unchanged. Concretely, the next three games in the same portfolio: Trivia Player — already has push notifications, so it plugs straight in: the agent schedules in-game events through Firebase Remote Config and announces them, one new tool and one new action type behind the same limits. Penalty 2D — promo codes only: on the agent side just a settings entry, but the game itself needs one app update first to add push support, so the codes can reach players. Snackroach — the hard case, twice over: it needs the push update, and its levels are built in a game editor today, so the agent can take over level-making only once levels become data files the game reads.