Technical debrief
Kickgeist
Our World Cup prediction game. 32,252 players made 863,000 predictions across iOS, Android and web, in 29 languages, through one tournament.
Problem
The date does not move.
Tippster Media was a social betting platform for the Bundesliga, the European Championship and the NFL. Our founder was its CTO from 2014 to 2017. Kickgeist is our prediction game for the 2026 World Cup: the same problem, twelve years later.
Kickgeist takes no money from players and pays nothing out. That is what separates the two products, and it is a legal position rather than a preference. It also decides the revenue model: advertising, or nothing.
A tournament product has one deadline and it belongs to somebody else. The tournament ran from 11 June to 19 July 2026, scheduled years before our first commit. A feature that lands after the final was never built.
Load follows the fixtures. It spikes at kickoff and again at the final whistle, when every prediction settles at once. Between matches the system is close to idle.
Scoring is public arithmetic. A player who gets scored wrong can check it, and will say so in public.
Approach
One codebase, no API tier.
Kickgeist runs on iOS, Android and web from one Flutter codebase. It is localised, and it exposes an MCP server for agents. The first release, v25.12.3, shipped on 30 December 2025; the stores went global on 1 March 2026. First commit to global launch took about ten weeks.
An agent pipeline built it.
We built Kickgeist through an agent pipeline rather than by hand. The pipeline drafts, reviews and tests. We set the intent and make the final call, and the gates decide what ships. This is the factory we install for you, and this product is where we proved it. It ran in our repositories, under our review process; nothing here shows it in yours.
The shape is recent. Reasoning models became reliable enough to hold a codebase in view and argue about a change before making it. The result below reports what shipped and what missed; it does not isolate the pipeline's contribution.
The cost is not speed. Review moves from writing code to deciding what is true, and a pipeline that is wrong is wrong quickly and at volume. So the gates matter more, not less.
The database is the boundary.
There is no API tier at all. The app talks to Postgres directly, and row-level security is the authorisation boundary. The schema carries 25 tables, 51 database functions and 48 policies. Scoring, referrals and challenge settlement run inside the database, so a tampered client cannot get past them. What it costs is a failure mode that says nothing. A new table needs an explicit grant beside its policy; a policy alone returns a silent 403.
Store review is the risk.
Two of the three clients ship through a store review queue that nobody on the project controls. That queue sits between a finished build and a player, and it does not care about the fixture list. Each client adds a release path and its own platform behaviour to test. Only the web client has no queue in front of it. Fastlane covers both store paths, 15 lanes for iOS and 17 for Android, from certificates to phased rollout.
Localisation does not retrofit.
Retrofitting a string catalogue into a shipped view layer means editing every screen. Doing it first costs one rule: no string is ever written directly into a view. Doing it later costs the same edit, with the tournament already running. Kickgeist ships 29 languages across 35 locale files, and the store listings and screenshots move with them.
The standard is local, not translated. Every language that distinguishes register uses the informal one. Each carries its own terminology table, and Brazilian Portuguese is rewritten rather than inherited from European Portuguese. Which locale shipped when was a business decision: Indonesian and Brazilian Portuguese went first, scored on smartphone reach, football engagement and regional ad benchmarks.
The agent runs elsewhere.
An MCP server is an integration point: an agent client connects to it and calls the tools it exposes. The game owns its data and its rules; the client does the reasoning. Nothing about that path depends on a model shipped inside the app. The server exposes seven tools behind OAuth 2.1 and static API keys. Agent calls mint a per-user token and run under the same row-level policies as a human player. What it costs is a second public interface to specify, version and secure.
Result
863,000 predictions, three platforms.



Install it from the App Store or Google Play, open it on the web, and switch the locale. You can settle those three claims without asking us anything.
Built once, shipped globally.
All three clients shipped, localised, with the MCP server in place. 32,252 players submitted 863,000 predictions across 14,880 groups, and 45% of that base arrived in the week of 8 June 2026.
Three things missed. First, the group-first architecture produced groups but not growth. Multi-member groups held 52.9% of players, 278 groups reached eleven or more members, and one reached 152.
Second, the invitation loop never closed. The viral coefficient came in at 0.013 against a 0.4 benchmark. Monthly actives fell 60% to 80% within thirty days of the final, and the oldest pre-tournament cohorts retain about 4.2%.
Third, the revenue model. We had assumed a rate per thousand ad impressions roughly eight times what it actually earned. Advertising is the only revenue line, so that assumption was the business model. We are not publishing the absolute rates; the ratio is the part that transfers to your own planning.
The plan had been to build once, run one tournament and wind down. The three misses replaced that plan. One ranked, localised listing goes quiet between tournaments and wakes for the next one, whereas a new listing would need a new audience.
92 releases shipped, 18 of them in January 2026 alone. Those releases carry 417 merged pull requests, gated by 2,032 automated tests and 231 golden images. The crash pipeline auto-filed 1,450 reports and deduplicated them by stack frame; one fix closed 185 at once.
The figures in this debrief were measured on 5 August 2026.
What we got wrong
We got the database wrong.
Signups and predictions failed with a connection error, and only sometimes. Nothing correlated: not the hour, not the platform, not the load. The first reports looked like flaky phones.
They were not. The first releases ran on Cloudflare D1, and D1 is SQLite underneath. SQLite allows one writer. The two-minute fixture sync batched over a hundred statements behind a single lock and held it for two to five seconds. Every request arriving inside that window waited, and a signup that waits long enough fails.
We migrated to Postgres in one move, in January 2026, before the stores went global. A queue in front of the writer would have hidden the symptom and kept the cause. The storage engine did not fit the write pattern, and a workaround does not repair that. The migration deleted more code than it added, and the custom auth layer and the API tier went with it.
We should have caught it before players did. An outside review had told us to leave D1 before the build started, and we published our reasoning for staying. That reasoning was sound about the numbers it used. The review reached the right conclusion anyway.
We measured the wrong axis. We asked how many people arrive at once, when the question was how long one writer holds the lock. What went right was the second decision, not the first. The failure surfaced in January, on a base small enough to absorb it, and the stores went global on 1 March 2026.
The full account is a note of its own: the review, the reasoning we published against it, and the rule we took away.
One mail. No form.
Tell us what is in the way.
Say what you're building and where it stopped.
Copy it into your mail client if that is easier.
The draft opens with
- What you are building
- What is in the way
- The stack
- When you want to start
The contact page says who answers, and what the reply contains.
Next debriefThe planning matrix
