Four features, in one deletion
Flow Builder, Compare, Dev Mode, and the Figma integration all shipped, and all four came back out in a single release.
144 files removed, 28k lines trimmed.
27th April 2026
Iteration
AgentUX has shipped every few days since March 2026, and the changelog records all of it. This is the edited version: the work I removed, the things that broke and why, and the one rule I never let move. Each source date comes from the product's own changelog, so any line here can be checked against its log.
Subtraction
Building the wrong thing is cheap to admit and expensive to undo. These are the ones I undid, including four features in a single deletion.
Flow Builder, Compare, Dev Mode, and the Figma integration all shipped, and all four came back out in a single release.
144 files removed, 28k lines trimmed.
27th April 2026
Saved links shipped grouped into per-host sections laid out in two columns. The grouping came out the same day, replaced by a flat grid sorted newest first, with the hostname surfaced on each card so the context survived the change.
added noise and made link scanning harder
13th June 2026
Mobile had its own inline expandable search row alongside the modal everywhere else. The mobile-only pattern was removed and the modal became the single way to search on every viewport.
one mental model for everyone
3rd April 202613th May 2026
Group appearance briefly included an emoji. Taking it out meant the editor, the data layer, and the schema, not just the control: the icon_emoji column was dropped. A removal that stops at the UI leaves the cost behind.
7th to 8th April 20262nd to 4th May 2026
Command-K opened search until it was cut for duplicating the slash key. It returned three weeks later, scoped to searching within Videos, where it was not competing with anything. The second version was not a reversal of the first, it was the first one aimed properly.
redundant with /
13th May 202629th May 20266th June 2026
Quick Upload and Bulk Change Group both used rows of chips to pick a group. Both became a single combobox within two days of each other. The same fix landing twice is usually a sign the pattern was wrong, not the screen.
wall of chips
28th April 202630th April 2026
Failure modes
Each of these has a cause, not just a fix. The one at the top is the reason I now distrust any check that reads a list in a single request. The last one is not a defect at all: it is a conclusion I had measured, argued for, and got wrong, corrected in the log the same day I published it.
Storage cleanup compared the bucket against the list of files still in use. That list came back from a single request that silently capped its results, so every file past the cap looked orphaned and was offered up for deletion. The comparison now runs inside the database, where there is no cap, and the button reports what it found and deletes nothing until you confirm it.
it read the list of in-use files in a single request that came back capped, so anything past that cap looked unreferenced
28th July 2026
Three findings closed in one pass. An unset allowlist variable meant any Telegram user could upload. Row-level security was enabled, but its policy permitted everything. The screenshots bucket accepted public inserts and deletes. The webhook now verifies its secret before any write reaches the database.
RLS was technically on, but the policy said 'anyone can do anything'
13th to 14th May 2026
Correcting which screenshots the Labelling Studio could see meant some clicks could no longer resolve to a screenshot family, so the lightbox stopped opening for them. Worth keeping in the record: the cause was the previous fix, not the original code.
fallout from the recent Studio scope fix
1st June 2026
Annotations were stored as a single JSON array per screenshot, so two concurrent edits meant the second write replaced the first. Each pin and area became its own row, with the existing data migrated across. A concurrency problem solved in the data model rather than in the interface.
29th April 2026
Caching the screenshot list on the device took a warm load from a full fetch down to around thirty milliseconds. It also meant the next person to sign in on a shared machine would have seen the previous account's catalogue. Signing out now clears it.
8th June 2026
Setting a flow label wrote the entire metadata column, rebuilt from whatever the page had loaded when it opened. Anything written since, by the AI labeller or by another member, was replaced. Flow changes now merge against current server state instead. This is the same failure as the annotations one from April arriving on a different column: a whole-object write standing in for a field update, and no error either time to say that anything had been lost.
overwritten and gone with no error
15th August 2026
Screenshots gained a small thumbnail for the grid. The orphan-storage sweep treats any file in the bucket that no database column names as abandoned, and nothing named the thumbnails, so every one of them was a day from deletion. The sweep knows about them now. The tool was doing exactly what it was built to do, which is the point: the cost of the new feature landed in a system nobody thought to go and look at.
Thumbnails would have been deleted a day after they were written
15th August 2026
The MCP transport is stateless, so nothing survives between calls. Each request was reconstructing all 19 tools and their 61 argument validators, using them once and discarding them. That cost was paid regardless of what the request actually asked for, and it put two of the three measured call types over Cloudflare's 10ms free-tier CPU ceiling. The validators now build once per worker. The last row below is the one doing the work: the interface an agent sees came out byte for byte identical.
use them once and throw them away
| Measurement | Before | After |
|---|---|---|
| Tool call returning a fixed string | 8.2ms | 3.0ms |
| Listing the tools | 10.2ms | 5.5ms |
| Three-image search | 26.2ms | 14.5ms |
| Fixed cost every request pays | about 8ms | about 3ms |
| Tool surface an agent sees | 33,179 bytes | 33,179 bytes |
13th August 2026
The plan was to stop inlining screenshots into every search reply, on the assumption that encoding them was expensive. Measurement disagreed: one inline image and three both came out at a 7ms median, because fetching an image is network wait rather than billed compute. The plan was dropped that morning, in writing. It shipped the same afternoon regardless, for a reason the measurement had never been about, which is that a default reply was 514,338 bytes of an agent's context window and returning text and ids instead made it 1,017. The entry that shipped it opens by withdrawing the number from the morning.
Honest correction to this morning's note: this change saves no measurable CPU
13th August 2026
The invariant
Six months of changing almost everything, with one rule that got an exception request each time a new surface opened, and never got the exception.
Four different routes opened for AI to write labels: pasted batches in the Studio, the AI review queue, agent submissions over MCP, and agents revising their own earlier work. Each one had a reason to make an exception, and none of them got one. Pasted batches are forced to Needs review so a label cannot quietly inherit a Verified status. Agents can pick up any draft, including one started by hand, but anything already verified is left alone and every revision returns to the queue. Speed was the argument for the feature every time. Trust was the argument against the exception, and it kept winning.
everything submitted still waits for human verification
6th to 7th May 202622nd July 202623rd July 202627th July 2026
The offline queue went out covering renames, grouping, and comments, and said in writing that crop was not included yet. Crop landed offline the following day, including the case where the same screenshot is cropped twice before reconnecting, where only the last one syncs.
Crop still requires being online, it's coming in the next release
10th June 202611th June 2026