Back to Blogs
#devchallenge#sanitychallenge#sanity#ai

It's Got What Content Craves: Sanity, Built for the People of Idiocracy

An agent proposes watering the crops, a human Cabinet approves in a picture-button Studio, and Sanity Workflows waters and harvests. Plus a stopwatch on three ways to edit the same data.

Updated 9 min read
Cover for It's Got What Content Craves: Sanity, Built for the People of Idiocracy

This is a submission for the Sanity Challenge, Path Two: Vibe-Code Something Strange

This article provides a step by step build of a **Sanity* project for the Department of Agriculture from Idiocracy, where every field is irrigated with Brawndo. An agent proposes watering the crops, a human Cabinet approves in a picture-button Studio, and Sanity Workflows waters and harvests the fields. A stopwatch then times the same edits in three interfaces.*

github.com/xbill9/devto-sanity

Project Sanity ukyhb6bu, dataset production (public read), Growth trial
Studio Sanity Studio 6, two workspaces over one schema: Kiosk and Stock
Workflow joes-plan v1, @sanity/workflow-* 0.33.0 (early access)
Front end Next.js 16 + next-sanity live content, on Cloud Run
Dashboard app App SDK Cabinet Room
Agent Claude (claude-opus-5) on brawndo.gov's server, calling the Workflows engine
Runtime Sanity Functions (Blueprint), plus Cloud Scheduler for the harvest clock
Result agent β†’ Cabinet β†’ watering β†’ harvest on the live project; 27 of 27 timed edits correct

What I Built

In Idiocracy the crops are dying because every field is watered with Brawndo. It's got electrolytes. Joe's plan is to water them. With water. Like from the toilet.

This is that Department of Agriculture, run on Sanity. The Secretary of the Interior is an agent. It drafts a proposal to water the fields and submits it. The Cabinet, a person, approves or rejects it. Citizens vote on a public scoreboard. An approved plan waters the fields, they sprout, and a few minutes later they are harvested. A rejected plan leaves a dust bowl.

The one pitch for a CMS that holds up is that people who can't use git can use a data store. So the editing Studio is a kiosk of giant picture buttons, like the hospital in the film.

Surface Built with Who uses it
The Kiosk Studio with custom picture-button inputs + the Workflows plugin the Cabinet
Joe's Plan Sanity Workflows + Sanity Functions the agent, the Cabinet, the runtime
brawndo.gov Next.js + next-sanity citizens
The Cabinet Room App SDK dashboard app the Cabinet

Demo

brawndo.gov: brawndo-gov-289270257791.us-central1.run.app

  1. Vote on a field: keep ⚑ Brawndo or switch to πŸ’§ water. Your vote is marked; voting again replaces it.
  2. Press πŸ›οΈ Summon the Secretary. The agent reads the tallies and sends the Cabinet a plan for the fields that voted for water.
  3. The Cabinet approves in the Kiosk. Approved fields turn πŸ’§ and sprout 🌱 within seconds, and are harvested 🌽 five minutes later.

Nothing runs on anyone's laptop. The Kiosk is hosted at brawndo-agriculture.sanity.studio and signs in through the Sanity Dashboard, so step 3 needs a seat in the project: the Cabinet.


Code

https://github.com/xbill9/devto-sanity

make check runs the offline suite. The README has the go-live runbook.


My Build Process

The project was built with Claude Code, from an empty directory to a harvested field.


Where Do I Start?

With what Sanity sells. A schema'd JSON store with a query language is common. Three things in Sanity's docs are harder to find elsewhere:

  1. Knowledge Bases surface conflicting sources side by side and keep your ruling across rebuilds.
  2. Workflows make the process a document. People, agents and apps move the same instance through the same transitions.
  3. The App SDK gives live, multi-user documents in your own interface.

A Path Two app built around the second one is the right Brawndo.


At This Point You Should Have…

  • Node.js 22.12 or newer (Sanity Studio 6 requires it; this build used Node 24 LTS through nvm)
  • A Sanity project. npx sanity new creates one with no account and a 72-hour claim link
  • The repository cloned, with its environment script sourced (source ./env.sh in the repository root)

Step 1 β€” One Schema, Two Studios

The schema is defined once and built twice. buildSchema({kiosk: true}) swaps a picture-button input into every enumerated field. buildSchema({kiosk: false}) keeps Sanity's defaults. Two Studio workspaces share one dataset, so the stock Studio is the same data without the Brawndo.

A proposal has no status field. Its stage lives in its workflow instance, which is itself a Sanity document, so there is one source of truth for where a plan stands.


Step 2 β€” Joe's Plan

petition ──submit──▢ cabinet ──approve──▢ watering ──▢ growing ──(harvestAt)──▢ harvest
                        └──reject──▢ dust-bowl
Enter fullscreen mode Exit fullscreen mode

The definition runs in Sanity's in-memory test bench before it is deployed:

 βœ“ definitions/joes-plan.test.ts (8 tests)
      Tests  8 passed (8)
Enter fullscreen mode Exit fullscreen mode

The tests prove the Secretary cannot approve, a rejection ends in the dust bowl, failed watering ends in the dust bowl, and harvest waits for harvestAt.

πŸ”Ž Tip: an effect handler's outputs land in $effects['<effect>']. To write a workflow field, return ops with an explicit target.scope:

return {
  outputs: {harvestAt},
  ops: [{type: 'field.set', target: {scope: 'workflow', field: 'harvestAt'}, value: {type: 'literal', value: harvestAt}}],
}
Enter fullscreen mode Exit fullscreen mode

Step 3 β€” The Cabinet Is Advisory

Sanity's Workflows docs say every check the engine makes is advisory. Role checks, guards and action filters shape the interface; the Content Lake enforces only dataset access control. So the Cabinet's power depends on a role the agent's token lacks.

The Growth trial has the eight built-in roles and no custom ones:

Error: workflow.deployDefinitions: unknown project roles for project "ukyhb6bu":
  - joes-plan: generated role condition references unknown role "secretary"
  - joes-plan: generated role condition references unknown role "cabinet"
Enter fullscreen mode Exit fullscreen mode

The definition therefore reads its role names from the environment and deploys with administrator for the Cabinet and editor for the Secretary. A robot token cannot hold administrator:

$ sanity tokens add brawndo-e2e-cabinet --role administrator ...
Error: Invalid role "administrator". Available roles: editor, developer,
contributor, access-manager, blueprints-deployer, deploy-studio, viewer
Enter fullscreen mode Exit fullscreen mode

⚠️ The Cabinet can only be a person, and it has no power. The agent's editor token can write the fields directly. The Cabinet has a seat, a button, and an advisory vote. That's the movie.


Step 4 β€” Water the Crops

A citizen presses Summon the Secretary. The agent runs on brawndo.gov's server with three tools: field_report (the tallies, computed by GROQ), create_proposal, and submit_to_cabinet, which starts Joe's Plan and fires submit. It has no tool that approves.

Its proposal, written for the Cabinet:

Field 5, Stadium Lot, still gets Brawndo. It grows dust. 2 people voted for water. 0 people voted for Brawndo. Field 12, Joe's Patch, still gets Brawndo. It grows dust too. 1 person voted for water. 0 people voted for Brawndo. The people picked water on both. So let's give these two fields water.

The plan's history on the live project, read back from the workflow instance (UTC):

00:18:09  petition  started                   brawndo-secretary  (server)
00:18:13  petition  draft.submit              brawndo-secretary  (server)
00:18:14  cabinet
01:49:36  cabinet   decide.approve            administrator      (Studio, browser)
01:49:45  watering  water-fields effect       brawndo-drain      (Sanity Function)
01:49:47  growing
01:55:05  growing   ripen.queue-harvest       brawndo-tick-http  (Cloud Scheduler)
01:55:10  harvest   harvest-fields effect     brawndo-drain      (Sanity Function)
Enter fullscreen mode Exit fullscreen mode

Between 00:18 and 01:49 the plan sat before the Cabinet until a person approved it.

πŸ”Ž Tip: Workflows is a library. Nothing moves unless code calls it. A document Function whose filter fires when unclaimed effects increase runs the watering the moment the Cabinet approves:

count(after().pendingEffects[!defined(claim)]) > coalesce(count(before().pendingEffects[!defined(claim)]), 0)
Enter fullscreen mode Exit fullscreen mode

⚠️ Time passing creates no document event, so something must tick once harvestAt passes. The Growth trial refuses a minutely scheduled Function:

Error: There were errors validating your resources:
 - brawndo-tick would run minutely but your plan limits you to hourly
Enter fullscreen mode Exit fullscreen mode

The scheduled Function runs hourly as a recovery sweep. The harvest clock is Cloud Scheduler calling brawndo.gov's /api/tick every minute; the tick only queues harvest-fields, and the same document Function harvests.


Step 5 β€” Arithmetic Belongs in the Engine

Every tally on brawndo.gov is a GROQ count() computed by the Content Lake:

count(*[_type == "vote" && field._ref == ^._id && choice == "water"])
Enter fullscreen mode Exit fullscreen mode

Nothing is summed in React, and the agent never counts rows. Its field_report tool returns the engine's numbers with the filter it ran.

One vote per citizen per field holds by construction: a vote's _id is built from both, so voting again replaces the vote. Three votes in, two votes out, read back anonymously:

vote field-07 -> HTTP 200
vote field-07 -> HTTP 200
vote field-03 -> HTTP 200
anonymous public read: {'anonCitizens': 1, 'f7water': 1, 'votes': 2}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ž Tip: a document _id containing a dot is never served anonymously from a public dataset. Vote ids use hyphens. Workflow instance ids contain dots (dev.wf-instance.…), so brawndo.gov reads the docket on the server with a token.

πŸ”Ž Tip: on a service that scales to zero, a prerendered page shows build-time data after every cold start. The scoreboard renders per request, and <SanityLive> keeps an open page current.


Step 6 β€” The Stopwatch

The question behind the Kiosk: can idiots use a data store? The protocol times three tasks in three interfaces, three runs each:

  • Water a field. Switch field N from Brawndo to water.
  • Add a citizen. Create a citizen with handle H.
  • Amend a proposal. Find the field with the most water votes on brawndo.gov and add it to proposal P.

The interfaces are the Kiosk, the stock Studio, and the same data as markdown files edited in GitHub's web editor.

A script starts the clock when the task is issued and stops it when the participant signals done. Another script checks the result against the dataset or the repository and records whether it is correct. Every number below comes from analyze.py reading the raw CSV, which is published in the repository.

The first participant is an agent: Claude, driving Chrome through the Claude in Chrome extension.


Compare and Contrast

Median seconds over three runs, participant A1 (the agent):

Surface Water a field Add a citizen Amend a proposal Correct
Stock Studio πŸ₯‡ 24.8 s πŸ₯‡ 16.8 s πŸ₯‡ 37.6 s 9/9
Kiosk (picture buttons) πŸ₯ˆ 26.4 s πŸ₯ˆ 25.6 s πŸ₯ˆ 41.4 s 9/9
Markdown on GitHub πŸ₯‰ 37.2 s πŸ₯‰ 41.2 s πŸ₯‰ 46.2 s 9/9

The stock Studio is fastest for the agent on every task, and GitHub is slowest on every task. The agent reads the accessibility tree, where a giant picture of a water glass and a small radio button carry the same label.

Both Studios beat editing files. Adding a citizen in GitHub means creating a file, naming it, and typing the frontmatter by hand. Amending a proposal means editing an array inside YAML without breaking it.


So, Which One?

For an agent, the stock Studio. For the people of Idiocracy, the Kiosk, and that is the next measurement: the same protocol with a person who has never used git.


Sanity Project Details

  • Project ID: ukyhb6bu, dataset production (public read)
  • Workflow: joes-plan v1, tag dev
  • Schema types: field, citizen, proposal, vote

Summary

The goal of this article was to build the Department of Agriculture from Idiocracy on Sanity and time whether its interface for non-programmers makes editing faster. The key to the solution was Sanity Workflows, where an agent and a person move the same document through the same transitions. The results were:

  • 🟒 Joe's Plan runs end to end on a live project: the agent proposes from the votes, a person approves in the Kiosk, Sanity Functions water and harvest
  • 🟒 27 of 27 timed edits correct across three interfaces
  • 🟒 Stock Studio fastest for the agent on every task: 24.8 s, 16.8 s and 37.6 s medians
  • ❌ Markdown on GitHub slowest on every task: 37.2 s, 41.2 s and 46.2 s medians
  • ⚠️ The Cabinet can only be a person, because a robot token cannot hold administrator
  • ⚠️ The Cabinet is advisory, because the Growth trial has no custom roles to stop the agent's editor token
  • ⚠️ Scheduled Functions run at most hourly on the Growth trial, so the harvest clock is Cloud Scheduler

Scope: one Sanity project on the Growth trial, @sanity/workflow-* 0.33.0, one participant (the agent) with three runs per task per interface in the order Kiosk, stock Studio, GitHub. The agent's times include the model's time to decide each action, and the Kiosk ran first, so it carries the learning curve.

The strategy for using Sanity Workflows for an agent-and-person approval process was validated with an incremental step by step approach.


References