<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MANIFESTA</title>
    <description>The latest articles on DEV Community by MANIFESTA (@manifesta).</description>
    <link>https://dev.to/manifesta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3853254%2F16c225f7-f41b-47f1-8756-251cec77b2ac.jpeg</url>
      <title>DEV Community: MANIFESTA</title>
      <link>https://dev.to/manifesta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manifesta"/>
    <language>en</language>
    <item>
      <title>I built a ministry that loses your form on purpose</title>
      <dc:creator>MANIFESTA</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:25:59 +0000</pubDate>
      <link>https://dev.to/manifesta/i-built-a-ministry-that-loses-your-form-on-purpose-4o7p</link>
      <guid>https://dev.to/manifesta/i-built-a-ministry-that-loses-your-form-on-purpose-4o7p</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The Ministry of Approvals. You file anything (a meme, a name for your cat, a plan, a complaint about the plan) and it walks a corridor of three departments staffed by AI clerks with opinions, lands on a human Minister's desk, and if it survives it goes on a public wall as a certificate with stamps on it.&lt;/p&gt;

&lt;p&gt;The Department of Pedantry finds the one thing you forgot and cites a regulation it made up on the spot. The Department of Rubber Stamps approves everything and says something that proves it did not read your form. The Archive loses every third form and hands you a receipt explaining where it probably went. Then the Minister (me, allegedly a human) has one hour to rule. If I sit on it, the Department of Delays sends it back to the Archive, which loses it again. 💀&lt;/p&gt;

&lt;p&gt;I run communities for a living and I have spent years waiting for someone with a stamp. This is that feeling, as a website, with the stamp physics done properly. It is cool and also slightly cursed 🫡&lt;/p&gt;

&lt;p&gt;Under the paint: the whole corridor is one Sanity Workflows definition that lives in the same dataset as the forms. Every step you see on the site is the workflow being read back. The clerks are Agent Actions with personalities stored as content, so you can slide a temperament from "rubber stamp" to "stickler" in Studio and the prompt rewrites itself. Their portraits were painted by Agent Actions too, from their own job descriptions. The Minister rules from a back office built with the App SDK inside the Sanity Dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live: &lt;a href="https://ministry-of-approvals.vercel.app" rel="noopener noreferrer"&gt;https://ministry-of-approvals.vercel.app&lt;/a&gt;&lt;br&gt;
Studio: &lt;a href="https://ministry-of-approvals.sanity.studio" rel="noopener noreferrer"&gt;https://ministry-of-approvals.sanity.studio&lt;/a&gt; (login walled, so the Studio screenshots below are what it looks like inside)&lt;/p&gt;

&lt;p&gt;A form gets filed, Pedantry returns it, I fix it, it walks Rubber Stamps and the Archive, the Archive loses it, it does the whole lap again and lands on the Minister's desk with a deadline. No cuts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2sc9w4k482g0xzfcr53e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2sc9w4k482g0xzfcr53e.gif" alt="A form walking the corridor, getting returned, getting lost, landing on the Minister's desk" width="719" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the ruling, from the back office in the Dashboard. I take the file, approve it with a note, and the audit trail fills up live: approve, issue, certificate, Discord announce, framed, on the wall. About 15 seconds. 🔥&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fff44zlg10awr3eko6pht.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fff44zlg10awr3eko6pht.gif" alt="The back office: take the file, approve with a note, watch it get certified and hung" width="720" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The certificate is a real PNG and the Ministry posts it to Discord the second it is issued:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrylpqg655tfxwwzj50c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrylpqg655tfxwwzj50c.png" alt="Certificate MoA-2026-0007" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8it0led8b9jujgzw21m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8it0led8b9jujgzw21m.jpg" alt="The Discord embed with the certificate" width="498" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some stills, because the GIF is big:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosgeho6paheyfe6jw6ly.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosgeho6paheyfe6jw6ly.jpg" alt="The Front Desk" width="800" height="471"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ta1o9nl505c4npg3h6a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ta1o9nl505c4npg3h6a.jpg" alt="The corridor and the staff with their painted portraits" width="800" height="471"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd4mxn4fz4lq4yfxwodb9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd4mxn4fz4lq4yfxwodb9.jpg" alt="Notice of deficiency from Pedantry, with the amendment form" width="800" height="421"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv6f3jgcxm2xh1myqtbla.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv6f3jgcxm2xh1myqtbla.jpg" alt="LOST 1x, second lap" width="800" height="421"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vxt3qybmivjah3hu3fd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vxt3qybmivjah3hu3fd.jpg" alt="On the Minister's desk with a deadline" width="800" height="421"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7rd6jw1jpfg3016x57u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7rd6jw1jpfg3016x57u.jpg" alt="The Studio Workflows tool with a run open, Rule by deadline visible" width="800" height="429"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjfpe0ft3zjb2roqso9pw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjfpe0ft3zjb2roqso9pw.jpg" alt="A clerk document in Studio with the temperament slider" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you file a form yourself: the Archive loses serials divisible by three. Check the counter on the Front Desk and pick your moment 👀&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/A1VARA5/ministry-of-approvals" rel="noopener noreferrer"&gt;https://github.com/A1VARA5/ministry-of-approvals&lt;/a&gt; (MIT)&lt;/p&gt;

&lt;p&gt;Four folders. &lt;code&gt;studio&lt;/code&gt; is Studio 6.9 with the Workflows plugin, a temperament slider, a stage badge and a portrait action. &lt;code&gt;workflows&lt;/code&gt; is the definition, the clerk handlers, the Sanity Functions runtime and 14 bench tests. &lt;code&gt;app&lt;/code&gt; is the App SDK back office. &lt;code&gt;web&lt;/code&gt; is the Astro 7 site with the certificate renderer.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;p&gt;Everything below is what I decided and what broke, in the order it happened. The dated log is in the repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  The engine is a library, not a service
&lt;/h3&gt;

&lt;p&gt;The first thing I got wrong was in my head before I wrote a line. I assumed a workflow with an AI step would just run. It does not. Sanity Workflows is early access (0.33.0 landed the day the challenge opened) and the engine is a library. Nothing moves on its own. An AI clerk is an effect that some runtime you own has to pick up and drain. Once that clicked, the whole architecture fell out of it: a Blueprint with a robot token and a Document Function that wakes up whenever a run gains an unclaimed effect and drains it. One drain walks a form through every automated department. The first full pass took about 20 seconds from resubmit to the Minister's desk, and I sat there watching the status page like it was a football match.&lt;/p&gt;

&lt;h3&gt;
  
  
  The schema, and what is on purpose not in it
&lt;/h3&gt;

&lt;p&gt;Five document types. &lt;code&gt;submission&lt;/code&gt; is what the citizen wrote and nothing else. There is no status field on it. Where a form is lives in the workflow instance document next to it, and the public status page is one GROQ query on that instance. &lt;code&gt;department&lt;/code&gt; is a stage described as content: name, motto, clerk, stamp colour. Renaming the Department of Pedantry is an edit, not a redeploy. &lt;code&gt;clerk&lt;/code&gt; is an AI personality stored as content: a system prompt, a temperament number, a catchphrase, a portrait. &lt;code&gt;certificate&lt;/code&gt; is only ever written by an effect handler and copies the clerks' remarks in as stamps, so the wall survives even if someone deletes the run. &lt;code&gt;receipt&lt;/code&gt; is keyed on the effect key, so a retried handler cannot issue two receipts for one loss. I learned that one the boring way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The workflow
&lt;/h3&gt;

&lt;p&gt;Seven stages. Each department is an activity with a trigger that queues the clerk effect, a second trigger that marks it done when the effect settles, and a third that marks it failed. Transitions branch on typed effect outputs: Pedantry returns &lt;code&gt;complete: boolean&lt;/code&gt;, the Archive returns &lt;code&gt;lost: boolean&lt;/code&gt;. The Minister's desk is the human stage: approve with a note, reject with a reason, refer to a department. All of those are actions with params, and the site, the back office, the CLI and the Studio plugin fire the same actions, so there is one audit trail whoever pressed the button.&lt;/p&gt;

&lt;p&gt;The bit I am most pleased with is the Department of Delays. The Minister's desk has a stage scoped &lt;code&gt;deadline&lt;/code&gt; field seeded by a GROQ query value (&lt;code&gt;string(dateTime(now()) + 3600)&lt;/code&gt;) and a trigger with &lt;code&gt;when: '$fields.deadline &amp;lt;= $now'&lt;/code&gt;. When a tick finds the deadline gone, the file goes to the Archive flagged &lt;code&gt;overdue&lt;/code&gt;, and the Archivist loses it every time and says so on the receipt. A Scheduled Function ticks every open run once an hour (that is the fastest my plan allows), and the status page ticks a file while someone is looking at it, so nobody has to wait for the clock in a demo.&lt;/p&gt;

&lt;p&gt;I proved it on myself while writing this. Serial 6 ("Declare the office plant a senior colleague") reached my desk at 13:41:51 UTC. I ignored it. At 14:42:08 the next tick sent it to the Archive, which lost it for the second time: "The declaration and its duplicate probably drifted to Shelf F, resigned, behind the expired flora certifications after the Minister let the deadline pass. The Minister may note that the plant remains senior while the papers remain unpromoted." By 14:42:27 it was back in Pedantry for a third lap. I got roasted by my own Archive 💀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foz4rf5wxdxjr3i2kzil9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foz4rf5wxdxjr3i2kzil9.jpg" alt="The Hall of Lost Forms, receipt LOST-0006-2, loss no. 2, after the Minister let the deadline pass" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Guards freeze the form's text and block deletion while a department holds it. Studio greys the buttons out and names the workflow as the reason. I did not write that UI, the plugin did, and it is the best free feature in the whole thing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxsg8ck7fl7qey0larp1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxsg8ck7fl7qey0larp1.jpg" alt="Studio with Publish greyed out and the Minister's Desk badge while the workflow holds the form" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The clerks
&lt;/h3&gt;

&lt;p&gt;Each clerk handler loads its own clerk document and calls Agent Actions with the form passed as a GROQ parameter, so the model sees title, kind, body, citizen, serial and amendments, nothing else. The personality is the document's &lt;code&gt;systemPrompt&lt;/code&gt;. Pedantry on a lunch request: "Add the filing reference number, as required by Regulation 8 subsection 3." The Archive, losing a meme about a printer: "It probably slipped behind Shelf M, resigned, where duplicate meme submissions and printer evidence wait without ambition." I did not write those lines and I would not change a word 😂&lt;/p&gt;

&lt;p&gt;A clean pass is three Agent Actions calls, one more each time you get returned, two more each time the Archive loses you. The three portraits cost three image credits and about 40 seconds each. Ms. Regulation Ona got a red pen without being asked.&lt;/p&gt;

&lt;h3&gt;
  
  
  The back office
&lt;/h3&gt;

&lt;p&gt;An App SDK app deployed into the Dashboard. The corridor as a board (&lt;code&gt;useWorkflowInstances&lt;/code&gt;, live: file a form on the site and it appears in the Pedantry column without a reload), one file open at a time with the workflow diagram, the remarks, the Minister's buttons, a "take this file" button and the audit trail. A staff register edits the clerks' prompts and temperaments with &lt;code&gt;useEditDocument&lt;/code&gt; and publishes them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What broke
&lt;/h3&gt;

&lt;p&gt;The peer dependency wall. Workflows 0.33 peers on &lt;code&gt;@sanity/ui&lt;/code&gt; 3. Every Studio from 6.10 up ships &lt;code&gt;@sanity/ui&lt;/code&gt; 4. I checked each minor. 6.9.x is the last line on ui 3, so the Studio is pinned to 6.9.2 with auto updates off. Then the App SDK quickstart scaffolded SDK 2 and Workflows wanted 3.1, and the 6.9.2 CLI never got the app past the Dashboard spinner because that CLI predates SDK 3 by two weeks. The app uses a newer &lt;code&gt;sanity&lt;/code&gt; for the CLI only. And &lt;code&gt;@sanity/workflow-components&lt;/code&gt; declares types it does not ship, so there is a 20 line local declaration file sitting there judging me.&lt;/p&gt;

&lt;p&gt;My own design mistake. Version one had the website write the citizen's amendment onto the submission document and then fire &lt;code&gt;resubmit&lt;/code&gt;. Firing the same action from the CLI got the form rejected again, because the CLI never wrote the document. Fixed by making the workflow own it: &lt;code&gt;resubmit&lt;/code&gt; parks the amendment on workflow fields with &lt;code&gt;fieldRead&lt;/code&gt; ops, and the Pedantry handler files it onto the document and clears the fields. Every caller behaves the same now. That is the entire point of the tool and I had to trip over it to get it.&lt;/p&gt;

&lt;p&gt;The hook that hung the Dashboard. &lt;code&gt;useWorkflowSession&lt;/code&gt; from &lt;code&gt;@sanity/workflow-sdk&lt;/code&gt; hung the app iframe for every real run I tried. The top frame stayed alive, the iframe never painted again. I bisected with checkboxes: still hung with the diagram, the actions, the assignee picker and the history all switched off. The same engine evaluates the same run in 350 ms in Node, and the Studio plugin's own session view works on it. I could not get the iframe's console, so I went around it: the run is a document, so the file view uses the App SDK's own &lt;code&gt;useDocument&lt;/code&gt; on it and calls &lt;code&gt;engine.evaluate&lt;/code&gt; and &lt;code&gt;engine.fireAction&lt;/code&gt; directly. It works. It is not the adapter's intended path, and if someone from Sanity reads this, I would love to know what I did wrong. Later the &lt;code&gt;AssigneePicker&lt;/code&gt; hung the frame too when wired to &lt;code&gt;engine.editField&lt;/code&gt;, so it is now a plain "Take this file" button. Honest limit: this is the part of the build I understand least.&lt;/p&gt;

&lt;p&gt;The test that caught a real bug. I wrote 14 tests on &lt;code&gt;@sanity/workflow-engine-test&lt;/code&gt;, which runs the real engine in memory with a clock you control. One of them approved a file without a note. It threw: &lt;code&gt;field.set&lt;/code&gt; refuses an undefined param. The back office let you submit an empty note. It is required now. Second surprise: the deadline query value resolves on the in memory lake's wall clock, not the bench clock, so the Delays test runs from the wall clock and advances from there.&lt;/p&gt;

&lt;p&gt;Event delivery is usually 4 seconds and once it was 3 minutes. During the recording one form sat in Pedantry for three minutes before the Document Function event arrived. So the status poll now drains any effect nobody has claimed for 30 seconds. Both runtimes claim under a lease, so they never run the same clerk twice, but I would rather the site did not have to do this at all.&lt;/p&gt;

&lt;p&gt;Screenshots froze my own site. The page texture was a live &lt;code&gt;feTurbulence&lt;/code&gt; SVG filter and every stamp had an &lt;code&gt;feTurbulence&lt;/code&gt; mask. Every capture timed out. I pre rendered both to PNG tiles and dropped the rotations on anything with inputs. Instant again. Live SVG filters as textures are a repaint tax, noted for life.&lt;/p&gt;

&lt;p&gt;Small ones: Astro's CSRF check refuses a POST without an Origin header (correct, and it bit curl). Guard names must be unique per definition, so they are suffixed by stage. satori's yoga and harfbuzz wasm and resvg's wasm all had to be added to the Vercel adapter's &lt;code&gt;includeFiles&lt;/code&gt; before the certificate PNG stopped 500ing in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  The visual pass
&lt;/h3&gt;

&lt;p&gt;The first version looked like every AI made bureaucracy page: cream paper, a serif, a list. I would have scrolled past it myself. So I wrote a short design spec (the Ministry is a real building and you are holding real paper; references: Wes Anderson title cards, 1970s ministry forms, rubber stamp physics, diploma engraving) and rebuilt it. A plaster corridor with SVG doors drawn from the department documents, a paper sprite that slides to the current door, doors stamped SEEN after each visit, ink textured stamps that slam in on state changes, a filing cabinet for the lost receipts, certificates in crooked brass frames. Then a second pass because the type was too small and the steel looked cheap. It still might. Tell me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Numbers
&lt;/h3&gt;

&lt;p&gt;7 stages, 3 AI clerks, 3 Agent Actions calls per clean pass, 2 Sanity Functions, 14 tests in 0.6 seconds, 4 lockstep Workflows packages, 1 Studio minor pinned, 3 portraits, 1 hook I never fixed, 1 bug the tests caught before anyone else could, 8 forms filed in production so far, 3 losses, 1 of them mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;p&gt;Project &lt;code&gt;v6745vem&lt;/code&gt;, dataset &lt;code&gt;ministry&lt;/code&gt;. The workflow definition (&lt;code&gt;sanity.workflow.definition&lt;/code&gt;) and every run (&lt;code&gt;sanity.workflow.instance&lt;/code&gt;) are documents in that dataset next to the submissions, departments, clerks, certificates and receipts. Studio is at &lt;a href="https://ministry-of-approvals.sanity.studio" rel="noopener noreferrer"&gt;https://ministry-of-approvals.sanity.studio&lt;/a&gt;, members only, with the Workflows tool in the toolbar. The back office is an App SDK app in my Dashboard, so you get it on video rather than a link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Sanity's Workflows, App SDK, Agent Actions and Functions docs, and the AI content pipeline cookbook, whose effect handler shape mine follows. Fonts: Fraunces, Special Elite, IBM Plex Sans. satori and resvg for the certificate PNG. The Archivist's excuses are the model's own.&lt;/p&gt;

&lt;p&gt;If you file a form and the Minister is slow, that is me, and the Archive already has it. 🫡&lt;/p&gt;

&lt;h1&gt;
  
  
  sanitychallenge #devchallenge #buildinpublic
&lt;/h1&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Bot Lawyer: an agent that knows which of Discord's own pages is lying</title>
      <dc:creator>MANIFESTA</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:25:51 +0000</pubDate>
      <link>https://dev.to/manifesta/bot-lawyer-an-agent-that-knows-which-of-discords-own-pages-is-lying-25hk</link>
      <guid>https://dev.to/manifesta/bot-lawyer-an-agent-that-knows-which-of-discords-own-pages-is-lying-25hk</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;On June 10, 2026 Discord changed the rule every bot developer hits first. The privileged intent threshold went from "100 servers" to "10,000 unique users", verification got split from intent review, annual reapplication appeared, and about half of Discord's own pages got updated. In my 2026-09-19 snapshot the application flags table still says 100 servers, and the gateway page says 10,000 users at the top of a section and "after your app is verified, you can apply" 25 lines lower. Same site, same day, two rules 👀&lt;/p&gt;

&lt;p&gt;I build and run communities for a living, developer communities included, and I build my own bots and tools for them because the ones I want never exist. So I have been on both sides of this: the mod who has to explain to a dev why their bot got denied, and the dev reading the old page while the rule changed underneath me. There is a specific feeling when a support article, the API docs and a Discord staff comment on GitHub all give you a different number and you have 100 servers waiting. Bot Lawyer is the lawyer I wanted at 2am that night, minus the invoice 💀 You tell it your situation (users, servers, which intents, verified or not), it rules yes, no or depends, quotes the page verbatim with the URL and the effective date, and when two pages disagree it shows both sides and says which one it applied. It has a "Rule as of" date, so the same question as of 2026-05-01 and today gives two different rulings, both cited. It runs as a web app and as a real Discord app with a &lt;code&gt;/ask&lt;/code&gt; command, and there is a public ledger of all 11 contradictions I found in Discord's docs.&lt;/p&gt;

&lt;p&gt;It also does the thing people in my DMs actually ask for every week, which is never about intents: give it a server type and it proposes the whole channel structure with the emoji and separator naming people use (&lt;code&gt;👋┆welcome&lt;/code&gt;, &lt;code&gt;📢┆announcements&lt;/code&gt;, &lt;code&gt;🔴┆live-streams&lt;/code&gt;), category headers, and every Discord constraint respected (1 to 100 characters, 50 channels per category, lowercase with no spaces for text channels). It says which parts are Discord rules and which parts are community taste, because it has both on file. Same for a welcome message or a rules post: you get a copyable block that renders correctly, with a character count against the 2000 limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live: &lt;a href="https://bot-lawyer.vercel.app" rel="noopener noreferrer"&gt;https://bot-lawyer.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flj8gjqt8nc1w007e7ogq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flj8gjqt8nc1w007e7ogq.gif" alt="Same question, two dates, two rulings" width="760" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want the five minute version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask "My bot is in 90 servers with about 4,000 users, do I need Message Content approved?" It says no, the threshold is 10,000 unique users since 2026-06-10, and under the answer you see the old 100 servers rule struck through with its dates.&lt;/li&gt;
&lt;li&gt;Set "Rule as of" to 2026-05-01 and ask about 120 servers. Now it says yes, and cites the rule that was in force from 2020-10-27 to 2026-06-09. Same bot, same data, different law.&lt;/li&gt;
&lt;li&gt;Ask "Is retry_after in seconds or milliseconds?" and watch it quote both pages with dates and pick one (C5 in the ledger).&lt;/li&gt;
&lt;li&gt;Ask for a welcome message for your gaming server. You get one copyable block and a character count against the 2000 limit, nothing else.&lt;/li&gt;
&lt;li&gt;Ask for channel names for a crypto community server: welcome, rules, announcements, live streams, general chat, trading, memes. You get a category layout with emoji, separator and lowercase names, and a line per constraint saying whether it is a Discord rule or just how people do it.&lt;/li&gt;
&lt;li&gt;Ask your own thing. Nothing is precomputed, every answer is a live GROQ query plus a knowledge base read.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qz5526bbr5xrf4l1nyk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qz5526bbr5xrf4l1nyk.jpg" alt="Rule as of 2026-05-01 vs today" width="800" height="473"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50stirz3j1793xbsbnal.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50stirz3j1793xbsbnal.jpg" alt="The unresolved C4 memo inside the Discord skin" width="799" height="444"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2qkdbdnilex7e7xdhru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2qkdbdnilex7e7xdhru.png" alt="Rules on file panel on a phone" width="390" height="844"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discord: the app is called BOT LAWYER, command &lt;code&gt;/ask question:&amp;lt;...&amp;gt; as_of:&amp;lt;YYYY-MM-DD&amp;gt;&lt;/code&gt;. HTTP interactions endpoint, no gateway, no intents, so the bot obeys its own rulings. Add it to any server you own with this link and run &lt;code&gt;/ask&lt;/code&gt; (global commands can take up to an hour to show up after install): &lt;a href="https://discord.com/oauth2/authorize?client_id=1550945390789140661&amp;amp;scope=bot%20applications.commands&amp;amp;permissions=2048" rel="noopener noreferrer"&gt;https://discord.com/oauth2/authorize?client_id=1550945390789140661&amp;amp;scope=bot%20applications.commands&amp;amp;permissions=2048&lt;/a&gt; . It only needs Send Messages. Counsel does not read replies, use &lt;code&gt;/ask&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Ledger: &lt;a href="https://bot-lawyer.vercel.app/contradictions" rel="noopener noreferrer"&gt;https://bot-lawyer.vercel.app/contradictions&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/A1VARA5/bot-lawyer" rel="noopener noreferrer"&gt;https://github.com/A1VARA5/bot-lawyer&lt;/a&gt; (MIT). Schema in &lt;code&gt;studio/&lt;/code&gt;, seed generator and validator in &lt;code&gt;seed/&lt;/code&gt;, the Next.js 16 app in &lt;code&gt;web/&lt;/code&gt;, all 40 eval questions with grades in &lt;code&gt;eval/&lt;/code&gt;, the contradiction file in &lt;code&gt;research/&lt;/code&gt;. The README has the commands to run it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Sanity
&lt;/h2&gt;

&lt;p&gt;I started by collecting Discord's public pages into a dated snapshot: the developer docs (they serve markdown on purpose and the source repo is MIT), the two help centers through Zendesk's public JSON API, the legal pages, and the discord-api-docs change log and issues. Snapshotted, not live, so the demo does not shift under you if Discord fixes a page next week; the snapshot stays out of the repo. Then I read all of it and wrote down every place two pages disagreed. That became &lt;code&gt;research/CONTRADICTIONS.md&lt;/code&gt;, eleven of them, each with both quotes and both URLs. I have moderated arguments between humans for years and this was worse, because both sides are the same company and neither one shows up to the call. Then I modelled the content so an agent could reason over it instead of searching it.&lt;/p&gt;

&lt;p&gt;The dataset: 136 rules, 58 sources, 11 policy areas, 40 eval questions, 11 documented contradictions (C1 to C11). One rule is one claim Discord makes, quoted verbatim from one page, with the conditions under which it applies: &lt;code&gt;appliesWhen&lt;/code&gt; (users, servers, intents, verification state, region, owner age), &lt;code&gt;effectiveFrom&lt;/code&gt; and &lt;code&gt;effectiveTo&lt;/code&gt;, a &lt;code&gt;source&lt;/code&gt; reference with an authority score (because Discord staff said on GitHub that support articles reflect user functionality, not bots), &lt;code&gt;supersedes&lt;/code&gt;, &lt;code&gt;conflictsWith&lt;/code&gt;, &lt;code&gt;resolution&lt;/code&gt; and &lt;code&gt;confidence&lt;/code&gt;. Of the first 100 rules, 55 are confirmed and 45 inferred, and the agent has to say "inferred" out loud when it uses one.&lt;/p&gt;

&lt;p&gt;Two Sanity Context endpoints, because an endpoint with a dataset source ignores its knowledge base sources:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bot-lawyer-rules&lt;/code&gt; is GROQ mode over the dataset, filtered to &lt;code&gt;_type in ["rule","source","policyArea"]&lt;/code&gt; so the agent cannot read the eval questions. Tools: &lt;code&gt;initial_context&lt;/code&gt;, &lt;code&gt;schema_explorer&lt;/code&gt;, &lt;code&gt;groq_query&lt;/code&gt;, &lt;code&gt;array_field_reader&lt;/code&gt;. It answers anything that depends on a number or a state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bot-lawyer-kb&lt;/code&gt; is knowledge base mode over 90 snapshotted Discord pages (docs, both help centers, legal pages, GitHub issues). Tools: &lt;code&gt;initial_context&lt;/code&gt;, &lt;code&gt;knowledge_base_read&lt;/code&gt;. It answers "what does the page around the quote say" and holds facts I never turned into rules. Every ruling reads both: the rules give the verdict and the dates, the knowledge base entry gives the page around it, and the answer marks that part with "From the page:" so you can see which is which. Under each answer there is a "Pages read" line with the entries and their source pages, next to the "Rules on file" cards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczqbh1up47v352plb8jd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczqbh1up47v352plb8jd.jpg" alt="Rules on file and Pages read under the payout answer" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app merges both clients into one Vercel AI SDK tool set. Both &lt;code&gt;initial_context&lt;/code&gt; payloads are inlined into the cached system prompt, and &lt;code&gt;groq_query&lt;/code&gt; and &lt;code&gt;knowledge_base_read&lt;/code&gt; are re described so the model routes by intent. The eval made 68 tool calls: 46 &lt;code&gt;groq_query&lt;/code&gt;, 22 &lt;code&gt;knowledge_base_read&lt;/code&gt;, zero &lt;code&gt;schema_explorer&lt;/code&gt; or &lt;code&gt;array_field_reader&lt;/code&gt;, because the inlined context already gave the schema.&lt;/p&gt;

&lt;p&gt;The knowledge base took 3 builds and raised 8 issues. Build 2 raised 4: one real conflict Context found on its own (the Monetization Terms page says "Effective June 6, 2024" at the top and "As of June 4, 2024" for the Growth Tier lower down; two facts, not one, I resolved it as June 4 and it became an Instruction), and 3 merge suggestions I dismissed. Then I wrote a manual Instruction for the June 10 change, scoped to the application, gateway, verification, privileged intents and change log pages. Context replied that this rule contradicts facts in two other entries and offered to rebuild them. Yes please 🤝 Build 3 raised 4 conflicts and Context found C1 by itself: "Thresholds: 10,000 unique users" against "Portal Issues: 100 servers", 8 sources. I resolved three. The fourth I left pending on purpose: App Directory says a support server is required before enabling discovery, Monetization SKUs never mentions one. Two processes, not a contradiction, and the only resolution offered is binary. Final count on the dashboard: 5 resolved, 3 dismissed, 1 pending, 5 active Instructions (4 from resolutions, 1 manual). Insights recorded 65 conversations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1i4gu4vvn1d99l767jbk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1i4gu4vvn1d99l767jbk.jpg" alt="Issues, one left pending on purpose" width="800" height="473"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffbfrpcbdng06gfxoeavx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffbfrpcbdng06gfxoeavx.jpg" alt="Five active Instructions" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quick sanity check I did on myself, because a bot that just searches the docs and reads them back is not worth anyone's time. Type "90 servers message content intent" into any search over Discord's pages and the top hit is the flags table: "100 or more servers". A real page, a real URL, a wrong answer, because that table was never updated in June. Bot Lawyer gets it right only because the old rule has &lt;code&gt;effectiveTo&lt;/code&gt; set and the new one points at it with &lt;code&gt;supersedes&lt;/code&gt;. Same with "retry_after seconds or milliseconds": search gives you both pages and a shrug. Here both rules carry &lt;code&gt;conflictsWith&lt;/code&gt;, a written &lt;code&gt;resolution&lt;/code&gt;, and each source has an authority score, so the answer is seconds and it can tell you why the support article lost. That is the whole point of putting this in Sanity instead of a vector database: the structure does the arguing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What broke
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The Studio deploy failed twice: &lt;code&gt;studioHost&lt;/code&gt; missing, then &lt;code&gt;@sanity/icons&lt;/code&gt; v5 has no named exports from the index, you import per icon. The best practices reference said exactly that. I did not read it closely enough.&lt;/li&gt;
&lt;li&gt;The first knowledge base build was refused: "plan limit for indexed documents is reached (303 of 150 used)". My 177 page live crawl plus a ZIP of snapshots was double the cap. I deleted both and curated 80 files.&lt;/li&gt;
&lt;li&gt;Build 1 ran 10 minutes and died with "The outline's structure stopped converging at thin_leaves (1 remaining) after 3 rounds". Build 2 resumed and passed.&lt;/li&gt;
&lt;li&gt;Haiku skipped &lt;code&gt;groq_query&lt;/code&gt;, answered the retry_after question from the knowledge base alone and said "both sources agree". They do not, that is C5. The prompt now mandates GROQ first and forbids "sources agree" unless the dataset shows no conflict. Haiku ignored the order anyway, so it runs on Sonnet.&lt;/li&gt;
&lt;li&gt;My GROQ hint was wrong twice. &lt;code&gt;area-&amp;gt;slug == "rateLimits"&lt;/code&gt; returned nothing, because slug is an object and the seed used kebab case anyway. The ids are &lt;code&gt;area.rateLimits&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The "case file" tool call lines never rendered. MCP tools arrive as &lt;code&gt;dynamic-tool&lt;/code&gt; parts in the AI SDK stream, not &lt;code&gt;tool-&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The first design was black text on a black background. Very legal, very unreadable. I threw it out and rebuilt the whole UI as a Discord DM, because the audience lives in Discord and a memo embed with a green, red or yellow bar makes sense to them before they have read a word. The sidebar has a &lt;code&gt;#billing-never&lt;/code&gt; channel. It is the only promise in this project I am fully confident about.&lt;/li&gt;
&lt;li&gt;I burned a $7 test key in one afternoon of evals and took the public demo down with it 💀 Every call was carrying both initial contexts uncached, about 15,000 input tokens, a hundred times over. Now the system prompt is cached, every request is metered, spend is capped at $3 a day, and if the key dies the route falls back to Vercel AI Gateway.&lt;/li&gt;
&lt;li&gt;Anonymous reads on the public dataset come back empty. I thought I had broken something, so I created a fresh public dataset with one document: same result, "permission", while Sanity's own public demo project answers anonymous queries fine and the security page for my project says "2 public datasets". So it is the project, not the data, probably the trial. The ledger page reads with a server side viewer token for now and the question is in the Sanity Discord.&lt;/li&gt;
&lt;li&gt;Bot Lawyer, the app that tells you to turn off privileged intents you do not use, had all three privileged intent toggles on in the Developer Portal until my own test pass caught it 😂 Physician, heal thyself. It now runs with zero intents and mentions this in its own footer, which is the most passive aggressive thing I have shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Eval
&lt;/h3&gt;

&lt;p&gt;40 questions, written from the contradictions I had found, graded by a separate model against expected answers, spot checked by hand.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;kind&lt;/th&gt;
&lt;th&gt;PASS&lt;/th&gt;
&lt;th&gt;PARTIAL&lt;/th&gt;
&lt;th&gt;FAIL&lt;/th&gt;
&lt;th&gt;items&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;trap&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;standard&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unknown&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ruling right on 39 of 40. 0 of 8 traps fell for the stale 100 servers answer. 22 of 40 read the knowledge base, 12 surfaced a contradiction, 20.6 seconds average. The one FAIL was a real defect: a knowledge base summary sentence went into a blockquote attributed to a page that does not say it. I added a provenance rule (blockquotes only from a rule's quote field or the page's own words) and re ran the four answers that had that smell. All 12 blockquotes in the re run are verbatim in the page snapshots, checked by string match, not by eye. The table above is the original run; I am not going to re grade the whole thing to make the number prettier.&lt;/p&gt;

&lt;p&gt;Honest limit: I wrote the questions and I built the dataset. This measures whether the agent reads the structure correctly, not whether the structure is complete or right. It is not a benchmark.&lt;/p&gt;

&lt;p&gt;Cost: about 9 cents per question on Sonnet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;p&gt;Project ID &lt;code&gt;v6745vem&lt;/code&gt;, dataset &lt;code&gt;production&lt;/code&gt; (public). Studio: &lt;a href="https://bot-lawyer.sanity.studio" rel="noopener noreferrer"&gt;https://bot-lawyer.sanity.studio&lt;/a&gt;. Context endpoints &lt;code&gt;bot-lawyer-rules&lt;/code&gt; (GROQ mode) and &lt;code&gt;bot-lawyer-kb&lt;/code&gt; (knowledge base mode, 90 sources). Anonymous queries currently come back empty on this project even though the dataset is public (details under What broke); a read token or the Studio shows everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;I built this in Claude Code and recorded the session. Being curated down to the bits worth watching: the icons fight, the thin_leaves build, the Haiku "sources agree" moment, and the afternoon I set fire to a $7 API key. Public before this post goes live.&lt;/p&gt;

&lt;p&gt;Two things in here I did not invent: the two endpoint setup with merged tools and the initial context inlined into the prompt is from the Sanity Labs agent workshop, and every word of content is Discord's own docs, support articles, policies and change log. The opinions about which page is lying are mine.&lt;/p&gt;

&lt;p&gt;The thing I still cannot solve: Context's pending issue wants me to pick a winner between two entries that are both right, which is also how most of my community disputes end. Is there a way to tell Context "not a conflict, keep both" that I missed, and if not, how are you handling it? 🔥&lt;/p&gt;

&lt;p&gt;And if you run a Discord server and your channel names are still &lt;code&gt;general&lt;/code&gt; and &lt;code&gt;general-2&lt;/code&gt;, you know where to find counsel.&lt;/p&gt;

&lt;h1&gt;
  
  
  sanitychallenge #devchallenge #buildinpublic #discord
&lt;/h1&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
