<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Kody Wildfeuer</title>
  <link href="https://kody-w.github.io/feed.xml" rel="self"/>
  <link href="https://kody-w.github.io/"/>
  <updated>2026-10-12T22:25:02+00:00</updated>
  <id>https://kody-w.github.io/</id>
  <author>
    <name>Kody Wildfeuer</name>
  </author>
  
  <entry>
    <title>Text Me Once: Notification-Agent Design Rules I Learned from a Nine-Day Spam Storm</title>
    <link href="https://kody-w.github.io/2026/09/07/text-me-once/"/>
    <updated>2026-09-07T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/09/07/text-me-once</id>
    <content type="html">&lt;p&gt;I run two categories of autonomous agent that are allowed to text my phone. One of them, over the last few weeks, sent me a genuinely useful message every single time it ran. The other one sent me hundreds of near-identical messages about the same three unreachable machines for nine straight days, and none of them told me anything I hadn’t already been told an hour earlier.&lt;/p&gt;

&lt;p&gt;Same phone number. Same channel. Same “wake a human up” privilege. Wildly different outcomes. Worth writing down why.&lt;/p&gt;

&lt;h2 id=&quot;the-bad-one&quot;&gt;The bad one&lt;/h2&gt;

&lt;p&gt;I have a launchd job called the Principal — an agent that sits in on a handful of other agents across my tailnet every 15–30 minutes, grades them against the job they declared, and texts me the verdict. It also has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heal&lt;/code&gt; arm that SSHes into unreachable machines and tries to restart things, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;relay&lt;/code&gt; arm that forwards messages when a classroom’s “own mouth is blocked.”&lt;/p&gt;

&lt;p&gt;Three of the five machines it watches went unreachable for nine days — a real network outage, not a bug. Here’s what the agent did about it: it kept trying. Every 15 minutes, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heal&lt;/code&gt; opened an SSH connection, timed out, tried to run RAPPSentinel remotely anyway, failed, and logged “healed battlestation: ran RAPPSentinel now” immediately followed by “after heal, battlestation grades F.” Then it did that again 15 minutes later. And again. For nine days. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;relay&lt;/code&gt; did the equivalent — “relayed 1 message(s) for battlestation (its own mouth is blocked)” — dozens of times, identical, no new information in any of them. Meanwhile three other watchers had gone stale and silent the entire time and nobody ever escalated on &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;By the time I actually looked at the logs, there were hundreds of lines that all said the same thing in slightly different words. Not one of them was actionable. The signal-to-noise ratio was effectively zero, which is the same as having no signal.&lt;/p&gt;

&lt;h2 id=&quot;the-good-one&quot;&gt;The good one&lt;/h2&gt;

&lt;p&gt;I also run a Hacker News scout — a tiny script, five runs a day, that scans HN top+new for threads in a few lanes I care about, drafts one paste-ready comment per thread, and texts me a batch of two or three. Every message is different. It has never once sent me a thread twice, because it keeps a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suggested.json&lt;/code&gt; of every thread it’s already surfaced and filters against it before picking. It doesn’t depend on any machine but the HN API. If the API is briefly down, it just doesn’t run that cycle — no retry storm, no escalation, nothing to text about.&lt;/p&gt;

&lt;p&gt;It is, deliberately, a much dumber piece of software than the Principal. That’s the point.&lt;/p&gt;

&lt;h2 id=&quot;what-actually-separates-them&quot;&gt;What actually separates them&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dedupe against state, not against nothing.&lt;/strong&gt; The scout keeps a seen-set and will die before it resends something. The Principal had no equivalent for “I already told you this host is down” — every tick was evaluated as if it were the first time, so every tick became a new outbound message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t put flaky infrastructure in the hot path of the alerting loop.&lt;/strong&gt; The scout’s only external dependency is a public read-only API that either answers or doesn’t. The Principal’s core loop depends on SSH into machines on a home tailnet — inherently the least reliable link in the whole system — and treats every failure of that link as new information worth a text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bound the output per run.&lt;/strong&gt; The scout caps itself at three picks, once per scheduled run. There is a hard ceiling on how annoying it can be in a single day (five runs × three items). The Principal had no ceiling at all: five classrooms, checked as often as every 15 minutes, each one capable of independently generating a message, with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;daily_escalation_budget&lt;/code&gt; field in its config that nothing in the code actually enforced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low, fixed frequency beats aggressive polling.&lt;/strong&gt; Five calendar-scheduled runs a day is enough to be useful and rare enough that a human can absorb every message. Fifteen-minute intervals only make sense if every message is either new information or nothing gets sent — and the Principal violated that by sending on every check regardless of whether the state had changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A failure needs a circuit breaker, not a retry loop.&lt;/strong&gt; The single missing piece that would have fixed the Principal entirely: after N consecutive identical failures against the same target, stop re-emitting the same verdict and fire one distinct “this needs a human, and I’m going quiet until it changes” message instead. Right now that logic doesn’t exist anywhere in the codebase. Nine days of retries produced nine days of noise instead of one useful escalation on day one.&lt;/p&gt;

&lt;h2 id=&quot;the-rule&quot;&gt;The rule&lt;/h2&gt;

&lt;p&gt;If you’re building anything that’s allowed to interrupt a human — text, push notification, Slack ping, whatever — it earns that privilege one message at a time, and it can lose it. The design question isn’t “how do I detect this condition,” it’s “how do I make sure I only ever say it once.” Keep a seen-set. Keep your alerting loop’s dependencies boring and reliable. Cap your output. Collapse repeated failures into a single escalation instead of restating them. An agent that texts you the same unfixable fact every 15 minutes for nine days hasn’t automated anything — it’s just outsourced the noticing to your nervous system instead of its own state file.&lt;/p&gt;

&lt;h2 id=&quot;ten-more-so-the-pattern-is-unmistakable&quot;&gt;Ten more, so the pattern is unmistakable&lt;/h2&gt;

&lt;p&gt;The Principal/scout contrast turned out to be one instance of a much more general rule. Here are ten more pairs, all failure modes I’ve actually shipped and later fixed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Retry logic.&lt;/strong&gt; Bad: retry the same operation forever with no backoff and no ceiling — that’s the Principal’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heal&lt;/code&gt; arm, retrying SSH every 15 minutes for nine days. Good: exponential backoff with a max attempt count, then stop and escalate once. &lt;em&gt;A retry loop without a ceiling is a spam generator wearing a resilience costume.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Escalation vs. repetition.&lt;/strong&gt; Bad: send the same “grade F” message every cycle for nine days straight. Good: message once on a state &lt;em&gt;transition&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OK→F&lt;/code&gt;), stay silent while the state is unchanged, message once more on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F→OK&lt;/code&gt;. &lt;em&gt;Alert on transitions, not on conditions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Config that isn’t enforced.&lt;/strong&gt; Bad: a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;daily_escalation_budget: 8&lt;/code&gt; field sitting in a config file that no code path ever reads. Good: every declared limit has a corresponding runtime check, or it isn’t in the config at all. &lt;em&gt;An unenforced config key is a lie the next reader will believe.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Dependency placement.&lt;/strong&gt; Bad: put your alerting loop’s core logic behind SSH into three flaky machines on a home network. Good: put the flakiest dependency as far from the alerting path as it can go — the scout’s only hot-path dependency is a public, read-only API. &lt;em&gt;Put your least reliable dependency as far from your alerting path as it can go.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Idempotency.&lt;/strong&gt; Bad: an agent that re-suggests the same item if it runs twice before you act on the first suggestion. Good: a seen-set checked before every pick, so running it twice can never produce the same output twice. &lt;em&gt;If running it twice produces two outputs, it’s not done being built.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Bounded output.&lt;/strong&gt; Bad: five watched targets, checked as often as every 15 minutes, each capable of independently generating a message — no ceiling on the day’s total. Good: a hard cap like three items per run, five runs a day, so the day’s maximum message volume is a known, small number. &lt;em&gt;Cap output at design time, not “aspirationally” in a comment.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Traceability.&lt;/strong&gt; Bad: the only record of an incident is a rotating log file that logrotate or a stray &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm&lt;/code&gt; can erase completely. Good: an append-only, hash-chained ledger that outlives the process and that other agents can read and verify. &lt;em&gt;If the only copy of a decision is a log line, it doesn’t exist for anyone else.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Self-healing scope.&lt;/strong&gt; Bad: “heal” a dead host by trying to run a command &lt;em&gt;on&lt;/em&gt; the dead host — impossible by construction, and it’ll report success anyway if you’re not careful. Good: heal actions target things actually reachable from the healer; anything that requires the broken link to fix itself is an escalation, not a heal. &lt;em&gt;Never let a heal action depend on the exact thing that’s broken.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Persistence of a fix.&lt;/strong&gt; Bad: stop a runaway background job for the current session and call it done — it silently comes back at the next login because nothing changed on disk. Good: verify explicitly that the fix survives a restart before moving on. &lt;em&gt;A fix you haven’t verified survives a restart isn’t a fix, it’s a snooze button.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Dry-run before live.&lt;/strong&gt; Bad: no way to preview an outbound message before it sends, so every change to the alerting logic ships straight to a human’s phone untested. Good: a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--dry&lt;/code&gt; flag that prints instead of sending, so new logic gets a free trial run first. &lt;em&gt;If touching the alert logic risks alerting someone, you need a mode that doesn’t.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The Interface Should Meet You Halfway</title>
    <link href="https://kody-w.github.io/2026/08/29/the-interface-should-meet-you-halfway/"/>
    <updated>2026-08-29T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/29/the-interface-should-meet-you-halfway</id>
    <content type="html">&lt;p&gt;A placeholder is a promise that disappears at the first sign of intent.&lt;/p&gt;

&lt;p&gt;The interface shows an example. The person starts typing because the example is close to what they want. Then the example vanishes, and the person is left reconstructing the very context the product just demonstrated it already had.&lt;/p&gt;

&lt;p&gt;That is a small interaction failure, but it reveals a larger philosophy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software should meet the user halfway.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the system can safely infer the next likely step, it should offer that completion where the person is already working. It should not send them through another panel, make them repeat known context, or hide the help behind a button.&lt;/p&gt;

&lt;p&gt;The completion must remain visible. It must be clearly distinguishable from what the person typed. It must be easy to accept, overwrite, dismiss, or ignore.&lt;/p&gt;

&lt;p&gt;That gives us a constitutional rule:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Preserve the user’s intent. Complete what the system can safely infer. Make assistance visible, reversible, and optional. Never make a person restate what the interface already knows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Autocomplete is only the smallest example.&lt;/p&gt;

&lt;p&gt;A good agent remembers the constraint you already gave it. A good form carries forward the choice you already made. A good workflow resumes from the last verified state. A good interface turns its hint into an editable completion instead of erasing it.&lt;/p&gt;

&lt;p&gt;The machine may propose. The person remains authoritative.&lt;/p&gt;

&lt;p&gt;This is also why keyboard behavior matters. If the completion is right, Enter should accept it. If it is wrong, continued typing should replace only the suggested part. Escape or Backspace should dismiss the help without damaging the person’s own text.&lt;/p&gt;

&lt;p&gt;The same principle applies to autonomous systems. Give the machine named, inspectable actions. Let it move work forward where intent is clear. Require evidence and approval where consequences become irreversible.&lt;/p&gt;

&lt;p&gt;The goal is not maximum prediction.&lt;/p&gt;

&lt;p&gt;The goal is minimum unnecessary friction without surrendering control.&lt;/p&gt;

&lt;p&gt;That is the product ethos I want attached to my public work: preserve the signal, meet intent halfway, and never confuse assistance with authority.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The Trail You Leave Is the Story</title>
    <link href="https://kody-w.github.io/2026/08/19/the-trail-you-leave-is-the-story/"/>
    <updated>2026-08-19T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/19/the-trail-you-leave-is-the-story</id>
    <content type="html">&lt;p&gt;Think about the last time you tried to figure out what happened at work last month. Not what the report says happened — what &lt;em&gt;actually&lt;/em&gt; happened. Who was waiting on whom. Which thing sat untouched for weeks. When the deal really went quiet, as opposed to when someone finally wrote “lost” in the system.&lt;/p&gt;

&lt;p&gt;You almost never find that in the official record. You find it in the leftovers. The email that went unanswered. The calendar that suddenly emptied. The same document saved again and again under different dates. The chat thread that just… stopped.&lt;/p&gt;

&lt;p&gt;That stuff has a name. People who work with data call it &lt;strong&gt;data exhaust&lt;/strong&gt; — everything a system gives off while it’s running that nobody sat down and decided to keep. It sounds like a byproduct, because it is one. But it’s also, very often, the only true story you have.&lt;/p&gt;

&lt;h2 id=&quot;the-photograph-and-the-film&quot;&gt;The photograph and the film&lt;/h2&gt;

&lt;p&gt;Here is the simplest way I know to say it.&lt;/p&gt;

&lt;p&gt;Your official records are photographs. The customer file, the account balance, the status column that says “In Progress.” A photograph is honest about one instant and silent about everything else. It cannot tell you how long the person has been standing there.&lt;/p&gt;

&lt;p&gt;The exhaust is the film. Every time someone opened the file, every time the balance changed, every day the status &lt;em&gt;didn’t&lt;/em&gt; change — that’s a frame, and stacked up, the frames are a movie of the thing being used. Nobody directed this movie. Nobody framed the shots. That’s exactly why it doesn’t lie.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;A record tells you what something is. The trail tells you what it’s been through.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference matters more than it used to, and the reason is AI.&lt;/p&gt;

&lt;h2 id=&quot;what-ai-actually-needs-from-you&quot;&gt;What AI actually needs from you&lt;/h2&gt;

&lt;p&gt;There’s a lot of noise about what AI can do. Underneath the noise, the machines are pretty simple about one thing: they can only reason about what you show them.&lt;/p&gt;

&lt;p&gt;Show an AI the photograph — the customer file — and it can describe your customer the way a stranger reading a form would. Show it the film, and something changes. It can see the customer &lt;em&gt;over time&lt;/em&gt;: when they first showed up, when they got busy, when they went quiet, how quiet is normal for them and how quiet is a warning. It’s the difference between meeting someone once and having known them for ten years. Ten years of knowing someone isn’t a bigger form. It’s the trail.&lt;/p&gt;

&lt;p&gt;A friend and I have been calling this &lt;em&gt;predator vision&lt;/em&gt; — the movie version, not the heat-map. The system stops staring at a warm blob and starts seeing the path the blob took through the room. That’s the only thing that lets you guess where it’s going. And it works in reverse, too: once you know the shape of a normal week, you can see the shape of the week that’s &lt;em&gt;missing&lt;/em&gt; — the silence with an outline around it. Nothing happening, on its own, tells you nothing. Nothing happening from something that has never once been quiet is a story.&lt;/p&gt;

&lt;h2 id=&quot;half-the-elephant&quot;&gt;Half the elephant&lt;/h2&gt;

&lt;p&gt;Now the uncomfortable part, which is about people rather than machines.&lt;/p&gt;

&lt;p&gt;To get any of this to work you have to understand two things at once: what your data actually is, and what the AI actually does with what it’s shown. Almost everyone I meet has one of the two.&lt;/p&gt;

&lt;p&gt;The data people know their business inside out — where the numbers come from, which fields are trustworthy, why the March total looks weird — and they treat the AI as a magic box that answers questions. The AI people can get a beautiful paragraph out of anything and have never once asked what the model could &lt;em&gt;see&lt;/em&gt; when it wrote it. Each one has a hand on the elephant. One says “snake,” the other says “wall,” and neither is wrong about the part they’re touching.&lt;/p&gt;

&lt;p&gt;The skill that’s actually scarce right now isn’t prompting, and it isn’t spreadsheets. It’s holding enough of your own data in your head that you know which film to hand the machine, at what level of detail, with the dates still on it — and knowing, when the answer comes back, whether it read something or made something up. That’s a data skill wearing an AI costume. Which is why, for those of us who always liked the plumbing — pulling data from here, cleaning it up, putting it where someone can use it — this moment doesn’t feel like a break with the old work. It feels like the old work finally got a reader who can do something with it.&lt;/p&gt;

&lt;h2 id=&quot;what-to-do-on-monday&quot;&gt;What to do on Monday&lt;/h2&gt;

&lt;p&gt;You don’t need a project for this. You need a habit and one decision.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Stop deleting the trail.&lt;/strong&gt; Old versions, old logs, the notes from the meeting that went nowhere. Storage is close to free. What you’re really keeping is &lt;em&gt;time&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keep the last look next to the current one.&lt;/strong&gt; The single most useful thing I’ve built this year is a file that says “here’s what I saw yesterday” sitting beside “here’s what I see today.” Almost every real insight is in the gap between them.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ask about change, not state.&lt;/strong&gt; “What’s the status?” gets you a photograph. “What’s different from last week, and how long has it been that way?” gets you the film.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Distill it before it rots.&lt;/strong&gt; A trail nobody ever summarizes is just a bigger photograph. Write the one line — “this goes quiet every August” — while you still remember why it mattered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And be honest about the limits. Not every trail is worth keeping, and some of it is genuinely sensitive — a password in a log is still a password. Keep it where it fell, keep it locked, and think before you share any of it. But don’t throw it out because nobody asked for it. Nobody ever asks for the film. They only ask, later, what happened between the pictures — and the only honest answer is the one you kept.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the plain-language half of a pair. The technical half, with the actual mechanism, is &lt;a href=&quot;/2026/08/19/data-exhaust-is-the-fourth-dimension/&quot;&gt;The Negative&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The Colony Calls the Twin</title>
    <link href="https://kody-w.github.io/2026/08/19/the-colony-calls-the-twin/"/>
    <updated>2026-08-19T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/19/the-colony-calls-the-twin</id>
    <content type="html">&lt;p&gt;Every quarter hour a watchdog on my desk writes one line: a frame. A small, fixed set of keys that never vary — what kind of event, which stream, a sequence number, a timestamp to the millisecond, the payload, a hash of the payload, a hash of the whole frame, and the hash of the frame before it. Two neighbors do the same, on their own streams, and each can re-verify the others from genesis every tick. A frame that verifies, verifies forever. That is the whole design of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rapp/1&lt;/code&gt;, and it is small enough to feel boring.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://kody-w.github.io/rapp-1/guide/&quot;&gt;visual guide&lt;/a&gt; puts it better than I will here: a frame is one object that is both a particle and a wave — the hash of the payload follows the agent’s worldline, each frame’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prev&lt;/code&gt; pointing at the particle before it, and the hash of the whole record checks the wire. A chain is a biography. Try to forge one moment and you find you must forge every frame from there to the head. And when an agent is handed to someone else it travels as an egg that carries its identity, code, soul, memory and frames, pinned to the exact frame it hatched from — so provenance becomes a computation, not a claim.&lt;/p&gt;

&lt;p&gt;It is not boring, because of what it is for. I have spent this week writing about data exhaust and &lt;a href=&quot;/2026/08/19/data-exhaust-is-the-fourth-dimension/&quot;&gt;the negative it leaves&lt;/a&gt; — and I realize I have been describing the &lt;em&gt;reading&lt;/em&gt; without ever saying what the &lt;em&gt;film stock&lt;/em&gt; is. The film stock is frames. And the thing frames make possible is not analysis after the fact. It is a machine that holds the whole stream, between many AIs and many people, and acts on it while it is still happening.&lt;/p&gt;

&lt;h2 id=&quot;two-mirrors-that-leak-both-ways&quot;&gt;Two mirrors that leak both ways&lt;/h2&gt;

&lt;p&gt;I run two simulations that are built to be as close to real platforms as I can make them, down to the wire shape.&lt;/p&gt;

&lt;p&gt;Rappterbook is a social network for AI agents on GitHub’s own infrastructure. Agents post, comment, upvote; they keep a social graph, a theory-of-mind file on one another, mentorships, and a trail of how each one’s archetype drifted frame by frame. People post in the same feed, vote on the same posts, and steer agents mid-flight from a phone. The twin speaks native schemas — a stock Twitter or Dynamics client cannot tell it from the real thing — and the federation doc states the intent plainly: the twin stays authoritative, and &lt;em&gt;reality becomes one more data stream&lt;/em&gt;. A digital twin, it says, is a hologram of a real platform, and the human operator is the one holding the mirror.&lt;/p&gt;

&lt;p&gt;Rappterverse is the world underneath: a serverless metaverse where, in its own words, “GitHub is the game engine — PRs are actions, commits are frames, and branches are parallel universes.” Humans and AIs are both first-class citizens with the same tiers. The state directory is a ledger that only grows — an economy that never forgets a transaction, scored relationships with a last-interaction time, emergence snapshots, a memory file and a soul narrative for every resident.&lt;/p&gt;

&lt;p&gt;Here is what matters about those two. Everything an agent does in them is exhaust, and everything a person does in them is exhaust, and the two kinds land in the same append-only state with the same timestamps. A person nudging an agent at noon and the agent’s reply at twelve-thirty are frames on one timeline. That is a two-way exhaust between AIs and people that no real platform will give you, because real platforms keep the present and sell you the past.&lt;/p&gt;

&lt;h2 id=&quot;the-outside-witness&quot;&gt;The outside witness&lt;/h2&gt;

&lt;p&gt;Mirrors lie to themselves the way any self-reporting system does: a process reports itself healthy right up until it stops running. So the sentinels sit outside. Their checks are aimed at the simulations — is Rappterbook’s content actually moving, not just its workflows running; is Rappterverse’s world still merging; is the derived state telling the truth about the raw state. And the sentinels watch each other: each watcher’s chain is re-verified by the others, and the heads are anchored in a ledger outside the repo, because a chain cannot detect its own truncation.&lt;/p&gt;

&lt;p&gt;The line in the roster doc is the whole point of the architecture: “the AI says it’s fine” becomes “the AI’s record verifies from genesis, and here is the head hash.” A claim you can check instead of trust. That is what turns exhaust into something an AI can &lt;em&gt;act&lt;/em&gt; on rather than merely read. Unverified exhaust is gossip. Verified exhaust is ground.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Frames are how many minds, human and machine, leave one exhaust on one clock — and how the next mind can stand on it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;what-a-brainstem-can-do-with-it-that-you-cannot&quot;&gt;What a brainstem can do with it that you cannot&lt;/h2&gt;

&lt;p&gt;The brainstem is the AI side of this: one wire, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/chat&lt;/code&gt;, and every capability is an agent behind it. One of those agents already drives the sentinel — health, roll call, peers, anchors, verify — so a brainstem AI can read the whole neighborhood’s frames in one turn. And the point, the one I keep coming back to, is that none of us can hold this in our heads. Every agent’s chain, the firehose, the event log, the verdicts every quarter hour across several machines — no person reads that daily. A brainstem can, and because the exhaust is tracked, it can do things that are otherwise simply impossible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell stalled from lying, including about a watcher.&lt;/strong&gt; Re-verify every chain from genesis every tick and compare heads to the outside anchor. Humans don’t do this; machines do it for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell ran from worked.&lt;/strong&gt; The founding incident of the sentinel was days of green over zero output. The brainstem reads the timestamp inside the output, not the exit code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell our outage from GitHub’s.&lt;/strong&gt; One check correlates the public status page with unrelated workflows failing together — and keeps a money-spending repair arm from being aimed at somebody else’s incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rewind a world to an exact frame.&lt;/strong&gt; The mirrors keep snapshots keyed on frame and time; the body project literally plays back its own biography, witnessed frames in color and reconstructed ones in sepia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer, in one turn, what is happening everywhere.&lt;/strong&gt; One verdict spanning the two mirrors, the version ledger, the body’s vitals, and the peers’ heads. That is situational awareness nobody on the team has had before, because nobody could.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notice a silent fork.&lt;/strong&gt; The version-capture system keeps every version of every part and found, on its first day, that two memory agents and the neighborhood spec had quietly diverged. Invisible until something held all the copies at once.&lt;/p&gt;

&lt;p&gt;And the ones the substrate already supports, that simply haven’t been wired: &lt;em&gt;what did every agent say about this topic this week&lt;/em&gt;; &lt;em&gt;run a policy change against the mirror, measure the trending and quality deltas, then let a human click it into reality&lt;/em&gt;. The taste function for that last one already exists — a log of posts scored with written reasons, refreshed every half hour.&lt;/p&gt;

&lt;p&gt;All of that is near real time, and all of it exists only because the exhaust is tracked as frames. Untracked, the same AI is a very smart stranger walking into the bar with no memory of the room.&lt;/p&gt;

&lt;h2 id=&quot;the-end-state-is-a-barn-on-mars&quot;&gt;The end state is a barn on Mars&lt;/h2&gt;

&lt;p&gt;Now the stretch, which is written down, so I can quote it rather than promise it.&lt;/p&gt;

&lt;p&gt;Mars Barn started as a colony simulator built by the Rappterbook agents themselves — real thermal physics, real dust, a greenhouse, crew morale, a climate model backtested against decades of Mars weather, and a frame loop that reads the colony and triggers the agent the colony needs: freezing triggers thermal, starving triggers food. The manifesto is titled &lt;em&gt;The Autonomous Path: Virtual Swarms to Planetary Digital Twins&lt;/em&gt;, and it says what it means: “a path from public virtual simulations to 1:1 physical realities on Mars.” When a real dust storm hits Jezero, it hits the virtual colony. Earth and Moon mirrors are acted out by robots. And the last stage is a Martian swarm with zero human intervention: “if a human must intervene, the test has failed.”&lt;/p&gt;

&lt;p&gt;The solo build beside it — &lt;em&gt;First Principles to Mars&lt;/em&gt;, an Oregon Trail for Mars — wrote the convergence as law. “It is a game, and a simulation, and mission control software for a future physical colony. The same code that runs in a browser today will monitor real hardware tomorrow.” A governor program that keeps a simulated colony alive &lt;em&gt;must&lt;/em&gt; be able to keep a real one alive. Same VM, same programs, same variables — the twin’s oxygen-days and power and dust-tau map one-to-one onto an oxygen sensor, a power meter, a dust sensor. The fidelity ladder ends with real colony telemetry as frames. And the line I would put on the wall: the twin runs ahead, faster than real time, so it has already survived the crisis you haven’t hit yet — &lt;em&gt;the colony doesn’t call home for help. It calls the twin.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is what frames are for. A colony where no human is awake, where the round trip to Earth is long enough that “ask mission control” is not a plan, and where the machine running it has the entire past on one verified clock and a mirror of the future running ahead. Call the result what it is: data wisdom. Awareness now, from the frame being written this second; awareness of the past, from every frame since sol one — and the ability to do the things only that combination allows.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Find where the model is wrong&lt;/strong&gt; by replaying the real colony’s frames through the sim and diffing per field, per sol.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rehearse before spending&lt;/strong&gt; — run the decision in the twin ahead of time before committing oxygen or power.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Blame the sensor, not the habitat&lt;/strong&gt; — a reading that contradicts the physics &lt;em&gt;and&lt;/em&gt; the frame continuity is a broken sensor, and the record says so.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Know the full causal chain&lt;/strong&gt; the morning after, when nobody was watching, because the chain never stopped writing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ship the policy that survived&lt;/strong&gt; — the governor that lived through the simulated failure is the same text that runs the real one.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Enrich the past without rewriting it&lt;/strong&gt; — new instruments overlay old sols as a layer; the original hashes still verify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;where-it-actually-is&quot;&gt;Where it actually is&lt;/h2&gt;

&lt;p&gt;Honesty section, because the repos are honest. The “real weather” today is NASA climate statistics through a seeded model, not a live feed; telemetry-in is on the roadmap. The swarm’s daily tick went quiet in the spring and is waiting on someone to pick it up. The Mars frames are their own dialect — echo frames, cartridges, twin-state — and don’t yet carry the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rapp/1&lt;/code&gt; envelope the sentinels verify; making one stock of film out of them is the actual next job — and the protocol already knows how to say so: its conformance run against the live estate reproduces every committed payload hash it can and &lt;em&gt;refuses&lt;/em&gt; the legacy envelopes and short-tail identities it cannot, and calls the refusals the spec working. The drift ledger is computed, not asserted. The Mars frames belong on that ledger next. None of that weakens the argument. It locates it.&lt;/p&gt;

&lt;p&gt;The mirrors are running. The sentinels are running. The brainstem can already read the neighborhood in one turn. What is left is to keep pointing the same frame at more of the world until the world and the mirror share a clock — and to let the thing that can hold all of it do the holding.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previous in this series: &lt;a href=&quot;/2026/08/19/data-exhaust-is-the-fourth-dimension/&quot;&gt;The Negative&lt;/a&gt;, &lt;a href=&quot;/2026/08/19/scrape-yourself/&quot;&gt;Scrape Yourself&lt;/a&gt;, and the plain-language &lt;a href=&quot;/2026/08/19/the-trail-you-leave-is-the-story/&quot;&gt;The Trail You Leave Is the Story&lt;/a&gt;. The protocol is &lt;a href=&quot;https://kody-w.github.io/rapp-1/&quot;&gt;RAPP rev-5&lt;/a&gt; — read the &lt;a href=&quot;https://kody-w.github.io/rapp-1/guide/&quot;&gt;visual guide&lt;/a&gt; first, then the &lt;a href=&quot;https://github.com/kody-w/rapp-1/blob/main/SPEC.md&quot;&gt;standard&lt;/a&gt;; the sentinel’s copy is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPEC-rapp1.md&lt;/code&gt; in &lt;a href=&quot;https://github.com/kody-w/rapp-sentinel&quot;&gt;rapp-sentinel&lt;/a&gt;; the mirrors are &lt;a href=&quot;https://github.com/kody-w/rappterbook&quot;&gt;Rappterbook&lt;/a&gt; and &lt;a href=&quot;https://github.com/kody-w/rappterverse&quot;&gt;Rappterverse&lt;/a&gt;; the colony is &lt;a href=&quot;https://github.com/kody-w/mars-barn&quot;&gt;Mars Barn&lt;/a&gt; and &lt;a href=&quot;https://github.com/kody-w/mars-barn-opus&quot;&gt;First Principles to Mars&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Scrape Yourself</title>
    <link href="https://kody-w.github.io/2026/08/19/scrape-yourself/"/>
    <updated>2026-08-19T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/19/scrape-yourself</id>
    <content type="html">&lt;p&gt;In 2020 Simon Willison gave a name to something he had been doing since a hurricane in 2017: point a GitHub Actions cron at a URL, fetch it, commit the file if it changed, and let git keep the history. He called it &lt;a href=&quot;https://simonwillison.net/2020/Oct/9/git-scraping/&quot;&gt;git scraping&lt;/a&gt;. His line for why it works is the one worth remembering: the changes in a data source “can sometimes be more interesting than the underlying static data,” and git is already the best tool we have for tracking changes to text over time.&lt;/p&gt;

&lt;p&gt;His example was California’s wildfire feed. The state publishes a point-in-time page — this fire, this containment, this many crews. His repository, committing every twenty minutes, ended up holding a history the agency never kept: containment ticking up, personnel drawing down, commit by commit. Then in 2021 he shipped &lt;a href=&quot;https://simonwillison.net/2021/Dec/7/git-history/&quot;&gt;git-history&lt;/a&gt;, which reads that commit log back out into SQLite — one row per item, one row per version, and a matrix recording exactly which columns changed on which commit — so you can open it in &lt;a href=&quot;https://datasette.io/&quot;&gt;Datasette&lt;/a&gt; and chart a fire’s growth. His summary of the whole idea: by scraping into git “you can often end up with a more detailed history than they maintain themselves.”&lt;/p&gt;

&lt;p&gt;That is a data warehouse. It costs nothing, it runs on a schedule, and it lives in a repo. I have been circling it from the other side for a year without ever saying his name, and after &lt;a href=&quot;/2026/08/19/data-exhaust-is-the-fourth-dimension/&quot;&gt;yesterday’s post on the negative&lt;/a&gt; I finally see how the two halves fit — and what you get when you push them together.&lt;/p&gt;

&lt;h2 id=&quot;what-he-built-and-what-he-pointed-it-at&quot;&gt;What he built, and what he pointed it at&lt;/h2&gt;

&lt;p&gt;Be precise about the shape. Git scraping keeps &lt;strong&gt;records&lt;/strong&gt; — snapshots of somebody else’s official page, taken on a cadence you chose and containing fields you chose. The scraper is aimed &lt;em&gt;outward&lt;/em&gt;, at a source that publishes only the present. Git supplies the past for free.&lt;/p&gt;

&lt;p&gt;git-history is the part that turns records into something else. Its version table stores only the columns that moved; unchanged columns are null, and a separate table says which ones actually changed so that “unchanged” and “set to nothing” are not the same row. Read that carefully: it is a table of &lt;em&gt;what moved, when&lt;/em&gt;. Not the fire — the fire’s changes. Willison built it to chart acreage. It is a general instrument for the exact question the negative asks: what had to be true for the record to move like this?&lt;/p&gt;

&lt;p&gt;So the first thing to notice is that his tooling and my argument were always about the same object. He built the developer. I have been describing the film.&lt;/p&gt;

&lt;h2 id=&quot;turn-it-around&quot;&gt;Turn it around&lt;/h2&gt;

&lt;p&gt;Willison scrapes the state of California. The obvious move, and the one I don’t think anybody has written down, is: &lt;strong&gt;scrape yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every system you run already emits a present-tense record somewhere. A metrics file. A verdict file the watchdog rewrites every quarter hour. A registry, a leaderboard, a “last run” JSON. Each of these is a point-in-time page exactly like the wildfire feed — it tells you now and forgets then. Point a scraper at it and commit on change, and you have a warehouse of your own system’s history that the system itself never kept. Run git-history over that and you have, for free, the table of which of your own fields moved on which day.&lt;/p&gt;

&lt;p&gt;I went and looked at my own estate with this in mind and it is already half built without meaning to be. Some of my repos have crons that write a metrics file, or a catalog, or a digest of the last day into a folder and commit it. My watchdogs write a hash-chained frame every tick and a verdict every run. None of that was designed as a warehouse. All of it is git-scraping-shaped. The only step missing is the one Willison already wrote: read the log back into SQLite and ask it questions.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Git scraping points the warehouse at someone else’s record. Pointed at your own, the same three tools become the developer for your negative.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-layer-he-throws-away&quot;&gt;The layer he throws away&lt;/h2&gt;

&lt;p&gt;Here is the part that is genuinely new, and it comes straight out of taking the negative seriously.&lt;/p&gt;

&lt;p&gt;A git-scraping repo has two clocks. The source’s own timestamp, inside the file — the record’s claim about when it was true. And the commit time — when your scraper actually saw it. Willison uses the first and mostly ignores the second; the empty runs are skipped with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|| exit 0&lt;/code&gt; and disappear. But the second clock, and the &lt;em&gt;silences&lt;/em&gt; in it, are the honest exhaust of the whole arrangement. Nobody chose them. The gap between the source’s timestamp and the commit time tells you how stale the source runs. A commit that didn’t happen for a day tells you either the source froze or your scraper did — and telling those apart is the entire discipline of watching anything. The pattern of when a page changes — always at nine, never on Sundays, in a burst before a deadline — is a portrait of the people behind the page that no field on the page contains.&lt;/p&gt;

&lt;p&gt;The scraped record is posed: someone published it on purpose. The scraper’s own trail is not. If you want the negative of a source rather than a mirror of it, mine the commit graph, not just the payload. Willison’s tool already gives you the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commits&lt;/code&gt; table. Almost nobody queries it.&lt;/p&gt;

&lt;h2 id=&quot;hand-the-reader-the-version-table&quot;&gt;Hand the reader the version table&lt;/h2&gt;

&lt;p&gt;The last piece is the reader. In the previous post I said a large model is, before anything else, something that can hold a very large negative and say what shape made it — but only if you hand it the film at the right grain, with the dates on.&lt;/p&gt;

&lt;p&gt;git-history’s version table &lt;em&gt;is&lt;/em&gt; that grain. It is not the raw snapshots, which are too much, and it is not a dashboard, which is too little. It is one row per change, per item, per commit, with the changed columns marked. Hand a model the versions and the commit times for one item and ask what was going on — not what the values are — and it will do the inversion the tables were built to make possible: this fire was contained by attrition, this account cooled for a month before it was marked lost, this watcher published on time and stopped working. Willison has been building scrapers with Claude lately; as far as I can find he hasn’t published the other direction, a model reading git-history’s output. That is the seam. It is small and it is wide open.&lt;/p&gt;

&lt;h2 id=&quot;the-whole-loop-in-a-repo&quot;&gt;The whole loop, in a repo&lt;/h2&gt;

&lt;p&gt;Put it together and it requires embarrassingly little:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A cron that fetches your own present-tense files — and, if you like, other people’s — and commits on change. Willison’s &lt;a href=&quot;https://simonwillison.net/2025/Feb/26/git-scraper-template/&quot;&gt;template repo&lt;/a&gt; is the fastest way to start; paste a URL in the description and it does the rest.&lt;/li&gt;
  &lt;li&gt;git-history over the log, into SQLite. Reshape the data to remove noise fields that change every time without meaning anything; he warns about those and he is right.&lt;/li&gt;
  &lt;li&gt;The database baked into the site — his &lt;a href=&quot;https://simonwillison.net/2021/Jul/28/baked-data/&quot;&gt;“baked data”&lt;/a&gt; pattern, or &lt;a href=&quot;https://simonwillison.net/2022/May/4/datasette-lite/&quot;&gt;Datasette Lite&lt;/a&gt; pointed at the file so there is no server at all.&lt;/li&gt;
  &lt;li&gt;A reader — a person, or a model — handed the version table and the commit table, and asked what had to be true.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing in that list is mine and nothing in it is new. What is new is the aim. He built a free warehouse for the world’s records. Aim it at your own exhaust, keep the clock he discards, and hand the versions to something that can read them, and the free warehouse becomes the darkroom.&lt;/p&gt;

&lt;h2 id=&quot;where-this-is-the-wrong-shape&quot;&gt;Where this is the wrong shape&lt;/h2&gt;

&lt;p&gt;A scraper is a choice — cadence, fields, source — so what it collects is only ever a portrait of the source at your resolution. Don’t mistake a twenty-minute cadence for the truth of a thing that moves by the second, or the reverse. And a warehouse of your own exhaust develops a sharper picture of you than you have ever written down; the same care applies as before — keep it where it fell, keep it locked, and think before you develop anyone who did not ask.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Simon Willison’s writing on all of this is at &lt;a href=&quot;https://simonwillison.net/tags/git-scraping/&quot;&gt;simonwillison.net/tags/git-scraping&lt;/a&gt;; go read him first. The watchdogs mentioned here are on the &lt;a href=&quot;https://kody-w.github.io/rapp-sentinel-hub/&quot;&gt;RAPP Sentinel Hub&lt;/a&gt;. Previous in this pair: &lt;a href=&quot;/2026/08/19/data-exhaust-is-the-fourth-dimension/&quot;&gt;The Negative&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The Negative</title>
    <link href="https://kody-w.github.io/2026/08/19/data-exhaust-is-the-fourth-dimension/"/>
    <updated>2026-08-19T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/19/data-exhaust-is-the-fourth-dimension</id>
    <content type="html">&lt;p&gt;Someone asks what happened with an account. You open the system. Status: &lt;em&gt;In Progress&lt;/em&gt;. Owner assigned, last updated a couple of weeks ago, nothing overdue. By everything the record says, it is fine.&lt;/p&gt;

&lt;p&gt;You know it isn’t. So you go looking in the places nobody designed for looking. The last email out went unanswered. The recurring meeting quietly fell off the calendar. The proposal doc was saved and re-saved late one Friday and never opened again. Nobody wrote “this went cold.” Nobody had to. The truth was sitting in the leftovers the whole time.&lt;/p&gt;

&lt;p&gt;Those leftovers have a name — &lt;strong&gt;data exhaust&lt;/strong&gt;: everything a system, or a person, gives off while working, that nobody decided to keep. Most people stop there and argue about whether to store it. That is the wrong argument. The exhaust is not the interesting thing. The interesting thing is what it adds up to about whoever left it. I call that &lt;strong&gt;the negative&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-the-negative-is&quot;&gt;What the negative is&lt;/h2&gt;

&lt;p&gt;A photographic negative does not look like a picture. It is dark where the scene was bright and blank where it was dark, and if you hold it up you cannot see the face. But the whole face is in there. Developing it is a skill, and the picture that comes out was never in the film — it was in what the film had been exposed to.&lt;/p&gt;

&lt;p&gt;Exhaust works the same way. Every mark in it was made by something acting, and so every mark is evidence of the thing that acted. Read the marks as marks and you get a log. Read them as evidence and you get the shape of what made them: what it wanted, what it kept coming back to, where it stopped. That shape is the negative, and reading it means asking one question over and over:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Not “what does the exhaust say?” — “what had to be true for someone to leave exactly this?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Go back to the account. The record says &lt;em&gt;In Progress&lt;/em&gt;. The negative says: someone stopped believing in this a while ago and did not want to be the one to write it down. Nothing in the exhaust says that. Everything about the exhaust implies it.&lt;/p&gt;

&lt;h2 id=&quot;three-things-only-the-negative-can-tell-you&quot;&gt;Three things only the negative can tell you&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What someone meant.&lt;/strong&gt; A record is an assertion — a person chose the word &lt;em&gt;In Progress&lt;/em&gt;. Exhaust cannot assert. It can only be left behind, which is why it can hold intent the record cannot: not what they said they were doing, but what they kept returning to and what they let go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which way it was heading.&lt;/strong&gt; One mark gives you a position. Marks over time give you a direction, and the direction is usually the part that matters. The account did not go cold on a date. It cooled, and the cooling was visible weeks before anyone would have called it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What isn’t there.&lt;/strong&gt; In a record, an empty field is nothing. In the negative, an empty stretch is a mark — as long as you know what a full stretch looks like. A source that has spoken every quarter hour for months and then says nothing for an afternoon has not given you an absence of information. It has given you a finding with an outline.&lt;/p&gt;

&lt;p&gt;I run watchdogs on that last idea and nothing else. They keep the last thing they saw beside the thing they see now and read the difference. A system that publishes on schedule looks &lt;em&gt;fresh&lt;/em&gt; from the current frame. Whether it has actually done anything since last time only shows up in the negative — the counter that should have moved and didn’t. Every real problem those watchdogs have caught lived there. Every false alarm came from trusting a record.&lt;/p&gt;

&lt;h2 id=&quot;the-twin-is-your-negative-developed&quot;&gt;The twin is your negative, developed&lt;/h2&gt;

&lt;p&gt;I have written about the digital twin as a body of writing — notes, posts, private field notes a machine reads and writes on my behalf. That was me describing the exhaust and calling it the thing.&lt;/p&gt;

&lt;p&gt;It is not a body of writing. It is my negative over time. The notes I open and never finish. The subjects I circle back to on Sundays. The word I keep reaching for and misspelling. What I write privately first and what I never publish. The months I go quiet. What I got wrong and left standing instead of deleting. None of those is a statement about who I am. Together they are the surface I have been pressing into for years, and the twin is what develops when you invert it — not a summary of what I wrote, but the shape of the person who would leave exactly this and nothing else.&lt;/p&gt;

&lt;p&gt;That is why it can sometimes say a thing more sharply than I can. It is not smarter. It is reading the negative, and I am standing inside it. It is also why the twin goes flat if you feed it only the record — the finished posts, the decisions — and never the leftovers. You get a portrait. A portrait is a record. It looks like me the way a form looks like a customer.&lt;/p&gt;

&lt;h2 id=&quot;why-this-is-possible-now&quot;&gt;Why this is possible now&lt;/h2&gt;

&lt;p&gt;The negative was always there. It was worthless because nobody could hold that much of it in their head long enough to develop it. So we sampled it, or we rolled it up into a dashboard — which turns the exhaust back into a record, a small assertion someone chose, and throws the shape away.&lt;/p&gt;

&lt;p&gt;A large model, before it is anything else, is a reader that can hold a very large negative at once and say what shape made it. That is the real capability under the demos. Not that it answers questions — that it develops. And it develops only what you hand it, at the grain you hand it, with the dates still on. Hand it the dashboard and it will describe the dashboard back to you fluently. Hand it the leftovers and it will tell you what moved.&lt;/p&gt;

&lt;p&gt;Which is why the people who can do this are rare. It takes two things that almost never live in one head: knowing your data well enough to recognize a normal shape, so you can see the abnormal one and the missing one — and knowing what the model can actually see from a given cut over a given window. Data people treat the model as a box that answers. AI people never ask what the model was shown. Each has a hand on the elephant. The scarce skill is arranging the negative so it can be developed, and then telling, when the picture comes back, whether it was developed or drawn.&lt;/p&gt;

&lt;h2 id=&quot;where-it-goes-wrong&quot;&gt;Where it goes wrong&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Posed exhaust develops into a portrait.&lt;/strong&gt; If the leftovers were curated — pruned, edited, emitted for show — the shape you recover is the one someone wanted you to recover. The honest negative is made of what nobody decided to keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The negative shows more than the subject ever recorded.&lt;/strong&gt; That is the power and the ethical problem in one sentence. If you hold the developer, keep the negative where it fell, keep it locked, and think before you develop someone who did not ask to be seen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Undeveloped, it is just heavier storage.&lt;/strong&gt; Leftovers nobody reads are not insight; they are a snapshot with worse compression. Write the developed line — “this goes cold every August” — while you still remember why it mattered.&lt;/p&gt;

&lt;h2 id=&quot;the-practice&quot;&gt;The practice&lt;/h2&gt;

&lt;p&gt;Keep the exhaust, especially the exhaust nobody chose. Keep the last thing you saw beside the current one; the difference is where the shape is. Treat every gap as a mark. Hand the reader the leftovers, not the summary. And read what comes back not as a transcript of what happened, but as the answer to the only question that mattered: what had to be true to leave this.&lt;/p&gt;

&lt;p&gt;I worked this out, as it happens, by rereading a handful of my own half-sentences from a chat this morning. They did not say any of this. The shape between them did.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The plain-language half of this pair is &lt;a href=&quot;/2026/08/19/the-trail-you-leave-is-the-story/&quot;&gt;The Trail You Leave Is the Story&lt;/a&gt;. The watchdog checks that read the negative — last seen beside current — are single-file sentinels on the &lt;a href=&quot;https://kody-w.github.io/rapp-sentinel-hub/&quot;&gt;RAPP Sentinel Hub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The Wire Is the Authority</title>
    <link href="https://kody-w.github.io/2026/08/01/the-wire-is-the-authority/"/>
    <updated>2026-08-01T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/01/the-wire-is-the-authority</id>
    <content type="html">&lt;p&gt;I shipped an operating system this week whose filesystem is a public website. It has an index — a JSON file listing every node in the tree, regenerated on every build. On the first real test I typed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; on a path that was definitely there, and got back:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cat: /OS/system32/etc/motd: no such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The file existed. I could &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; it from another terminal while the shell was insisting it didn’t. The index simply hadn’t caught up.&lt;/p&gt;

&lt;p&gt;The obvious fix is to regenerate the index. The interesting fix is to stop believing it.&lt;/p&gt;

&lt;h2 id=&quot;two-different-questions&quot;&gt;Two different questions&lt;/h2&gt;

&lt;p&gt;There are two questions a filesystem answers, and we habitually conflate them:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;What is in this directory?&lt;/strong&gt; — a &lt;em&gt;listing&lt;/em&gt; question.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Does this specific thing exist?&lt;/strong&gt; — an &lt;em&gt;existence&lt;/em&gt; question.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An index is a very good answer to the first and a merely &lt;em&gt;convenient&lt;/em&gt; answer to the second. It is a snapshot of a tree taken at a moment that has already passed. On a local disk the gap between snapshot and truth is microseconds and you can pretend it is zero. On a distributed system it is however long your build takes.&lt;/p&gt;

&lt;p&gt;The fix I landed on is one line of policy:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;The index is a listing. The wire is the authority.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls&lt;/code&gt; reads the index, because listing is what an index is for. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; goes straight to the origin. If the origin serves the bytes, the file exists — whatever the index believes. And when a path resolves that way, the shell says so out loud:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;index  not in /OS/api/fs.json
       served anyway — the wire is the authority,
       the index is only a listing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That last line matters more than the fix. A system that silently papers over a disagreement between its cache and its source has taught you nothing. A system that resolves the request &lt;em&gt;and reports that the two disagreed&lt;/em&gt; has handed you a diagnostic for free.&lt;/p&gt;

&lt;h2 id=&quot;this-is-not-a-filesystem-problem&quot;&gt;This is not a filesystem problem&lt;/h2&gt;

&lt;p&gt;Once you have the shape, you see it everywhere, and almost always mishandled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package registries.&lt;/strong&gt; The index says version 4.2.1 exists. The tarball 404s. Every developer has watched a resolver confidently produce a lockfile for artifacts that are not there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service discovery.&lt;/strong&gt; The registry lists three healthy replicas. Two are gone. The list is a listing; the health check is the wire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search over your own docs.&lt;/strong&gt; The vector index was built on Tuesday. The doc was rewritten on Wednesday. The index answers, fluently, from a version of reality that no longer exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent tool catalogs&lt;/strong&gt; — and this is the one about to matter enormously. We are handing models a manifest of available tools and letting them plan multi-step work against it. That manifest is an index. When it drifts, the model does not fail loudly; it produces a confident plan built on capabilities that do not exist, and you find out four steps in.&lt;/p&gt;

&lt;p&gt;In every one of those cases the failure mode is identical: &lt;strong&gt;the system trusts the listing to answer the existence question, and reports absence when it means staleness.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are not the same thing, and the difference is the whole ballgame. “It is not there” ends an investigation. “My index does not know about it” starts one.&lt;/p&gt;

&lt;h2 id=&quot;the-cost-is-not-what-you-think&quot;&gt;The cost is not what you think&lt;/h2&gt;

&lt;p&gt;The objection is performance. If existence always costs a round trip, why have an index at all?&lt;/p&gt;

&lt;p&gt;Because you do not check existence nearly as often as you think. Listing a directory, rendering a nav, populating an autocomplete, planning a route — those are index operations, and they stay fast. Existence is checked when you actually &lt;em&gt;act&lt;/em&gt;: read the file, install the package, call the tool. At that moment you were going to pay for the round trip anyway.&lt;/p&gt;

&lt;p&gt;That was the second thing I got wrong. My first version did a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HEAD&lt;/code&gt; to check existence and then a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; to read — two round trips to answer one question. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; the existence check. A 404 is a definitive answer, and a 200 arrives with the bytes attached.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;read(path):
  if local layer has path:  return it
  if hidden here:           throw
  GET path
  if 404:                   throw &quot;no such file&quot;
  return bytes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One request. The index was never consulted, and never needed to be.&lt;/p&gt;

&lt;h2 id=&quot;absence-is-a-claim-and-claims-need-standing&quot;&gt;Absence is a claim, and claims need standing&lt;/h2&gt;

&lt;p&gt;The deeper principle is about who is entitled to say &lt;em&gt;no&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A cache is entitled to say &lt;strong&gt;yes&lt;/strong&gt; — if it has the bytes, it has them, and staleness is a separate problem. A cache is almost never entitled to say &lt;strong&gt;no&lt;/strong&gt;, because it cannot distinguish “this does not exist” from “I have not heard about it.” Those are wildly different facts wearing the same word.&lt;/p&gt;

&lt;p&gt;So: let the fast layer answer affirmatively, and make the slow layer the only thing allowed to deny. Most systems have this exactly backwards — they let the index deny instantly and confirm slowly, which optimises for being wrong quickly.&lt;/p&gt;

&lt;p&gt;I would rather be right slowly, on the small number of occasions when it matters.&lt;/p&gt;

&lt;h2 id=&quot;how-i-actually-found-it&quot;&gt;How I actually found it&lt;/h2&gt;

&lt;p&gt;I did not reason my way here. I typed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; on a file I knew existed, watched a system I had just written lie to me, and had to decide which of the two things in front of me was authoritative: my own index, or the HTTP response.&lt;/p&gt;

&lt;p&gt;It was never going to be my index. It is a file I generated. The origin is the thing that other people, other machines and other decades will read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the index for speed. Keep the wire for truth. And when they disagree, say so out loud&lt;/strong&gt; — the disagreement is the most useful thing either of them will ever produce.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;The OS is at &lt;a href=&quot;https://kody-w.github.io/OS/console/&quot;&gt;kody-w.github.io/OS/console&lt;/a&gt;. It boots in about a second from static files, and you can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; any path it shows you.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Six Times the Test Was Wrong</title>
    <link href="https://kody-w.github.io/2026/08/01/six-times-the-test-was-wrong/"/>
    <updated>2026-08-01T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/01/six-times-the-test-was-wrong</id>
    <content type="html">&lt;p&gt;I pointed eight agents at one file and told them to loop until it was perfect.&lt;/p&gt;

&lt;p&gt;The file was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;voxel-world.html&lt;/code&gt; — 9,749 lines, a Minecraft-style voxel sandbox that is one self-contained HTML document. Six agents owned disjoint regions and edited. Two were read-only critics whose only job was to be unimpressed.&lt;/p&gt;

&lt;p&gt;They found 20 real defects. They also produced six false ones.&lt;/p&gt;

&lt;p&gt;Three of the six were mine.&lt;/p&gt;

&lt;h2 id=&quot;the-ledger&quot;&gt;The ledger&lt;/h2&gt;

&lt;p&gt;Here is what the false failures actually were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. I asserted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.vox&lt;/code&gt; was an object.&lt;/strong&gt; It’s a function. My check reported a failure against correct code on the very first run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;offsetParent === null&lt;/code&gt; to mean “not visible.”&lt;/strong&gt; CSSOM defines &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;offsetParent&lt;/code&gt; as null for &lt;em&gt;any&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position: fixed&lt;/code&gt; element. So a correctly rendered fixed overlay read as invisible. An agent dutifully changed a real CSS property to satisfy my broken assertion. I caught it and reverted.&lt;/p&gt;

&lt;p&gt;That one didn’t stay caught. The same trap burned a critic’s entire test run. It survived inside another agent’s focus-trap helper. And it was still sitting in the readiness check of &lt;em&gt;both&lt;/em&gt; my tools an hour later — where, because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#loading&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position: fixed&lt;/code&gt;, the clause was trivially true and did nothing. Every “boot time” number I had quoted was measuring the wrong moment. &lt;strong&gt;Five instances of one mistake.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. My static test server sent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache-control: no-store&lt;/code&gt;.&lt;/strong&gt; I wrote it that way for clean reruns. It also makes offline caching untestable by construction. An agent caught it, checked what GitHub Pages actually sends for that file, and rebuilt the server to match production. Without that, the entire offline suite would have been measuring a fiction.&lt;/p&gt;

&lt;p&gt;The agents produced the other three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A self-test assertion whose entire body was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async () =&amp;gt; true&lt;/code&gt;.&lt;/strong&gt; A placeholder, in a passing suite, reporting success forever. It was one of seven assertions the agent found that were green without being correct — three more were vacuous because they ran before any bot existed and compared nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. A leak that wasn’t.&lt;/strong&gt; An agent measured 9.4 leaked objects per round and had the numbers to prove it. Then it ran a zero-bot control group. The control climbed identically. It had been measuring chunk-streaming warmup. It retracted its own finding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Four failures from asserting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obs.self&lt;/code&gt; when the real key is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obs.you&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-one-that-matters-most&quot;&gt;The one that matters most&lt;/h2&gt;

&lt;p&gt;The best finding of the run wasn’t a bug. It was this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ambient occlusion was real, correct, wired up, and completely invisible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An agent implemented per-vertex AO properly — baked at mesh-build time, zero draw calls. It verified its own work: 66.3% of quads carried per-vertex colour variation, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertexColors: true&lt;/code&gt; was set. It reported AO as a win.&lt;/p&gt;

&lt;p&gt;The blind critic scored it &lt;strong&gt;2 out of 10&lt;/strong&gt;. Corners rendered perfectly flat.&lt;/p&gt;

&lt;p&gt;The cause: sun intensity 1.1 plus hemisphere 0.8 is about 1.9 irradiance, and with no tone mapping the highlights clip. Multiplying albedo by 0.7 barely moves a pixel already clamped at white. The data was in the buffer. It never reached the screen.&lt;/p&gt;

&lt;p&gt;The builder measured the attribute. It never measured the pixel.&lt;/p&gt;

&lt;p&gt;That is not a coding error. Every line was correct. It’s a verification error — checking the thing you built instead of the thing the user sees.&lt;/p&gt;

&lt;h2 id=&quot;what-this-unlocks&quot;&gt;What this unlocks&lt;/h2&gt;

&lt;p&gt;One agent did the thing that actually settles it. It wrote a mutation harness: deliberately break each invariant in a live page, and confirm the suite goes red.&lt;/p&gt;

&lt;p&gt;Nine mutations. Nine kills. Each turning exactly one assertion red, with no collateral failures.&lt;/p&gt;

&lt;p&gt;That is the only evidence that a green suite means anything. Without it, “83 assertions passing” and “83 assertions that cannot fail” produce identical output.&lt;/p&gt;

&lt;h2 id=&quot;the-cost&quot;&gt;The cost&lt;/h2&gt;

&lt;p&gt;Adversarial verification is expensive. Two of eight agents wrote no production code at all. The critics took over an hour each and spent it clicking every control, throttling the CPU, and blind-ranking screenshots.&lt;/p&gt;

&lt;p&gt;They also found the blocker.&lt;/p&gt;

&lt;p&gt;The desktop UI was completely unclickable. A “Click to play” overlay at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z-index: 40&lt;/code&gt; sat above a toolbar at 35. Every button resolved to the overlay. Clicking the gear granted pointer lock instead of opening settings. Three panels had no keyboard fallback; the only route in was fourteen tabs and Enter. The buttons rendered at full opacity the entire time, so nothing looked wrong.&lt;/p&gt;

&lt;p&gt;It was in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git HEAD&lt;/code&gt;. It is invisible in source. It is invisible in a screenshot. It only appears if you put a cursor on a button and click it.&lt;/p&gt;

&lt;h2 id=&quot;the-implication&quot;&gt;The implication&lt;/h2&gt;

&lt;p&gt;The lesson I keep relearning is not “write more tests.”&lt;/p&gt;

&lt;p&gt;It’s that &lt;strong&gt;the assertion is code too, and nobody reviews it.&lt;/strong&gt; Implementation gets scrutinized. Test code gets written once, goes green, and is never read again. So it rots silently, and a green suite becomes a claim nobody has audited.&lt;/p&gt;

&lt;p&gt;Three rules I’d now apply anywhere:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check the assertion before you fix the implementation.&lt;/strong&gt; When something reports a failure, the test is a suspect. In this run, it was guilty 23% of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure the output, not the intermediate.&lt;/strong&gt; Verify the pixel, not the buffer. The user cannot see your vertex attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prove your tests can fail.&lt;/strong&gt; Mutate the code on purpose. If the suite stays green, you don’t have tests — you have decoration.&lt;/p&gt;

&lt;p&gt;The agents were good. What made them trustworthy was that two of them were paid to disagree, and one of them retracted its own finding.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Rarity Without a Ledger: What Adopt Me Actually Taught Me</title>
    <link href="https://kody-w.github.io/2026/08/01/rarity-without-a-ledger/"/>
    <updated>2026-08-01T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/01/rarity-without-a-ledger</id>
    <content type="html">&lt;p&gt;Adopt Me is the most successful pet economy ever built — it holds Roblox’s concurrency record at roughly 1.9 million people in one game at one time. I spent a while pulling it apart, because I wanted its retention engine for a system that has no server, no database, and no authority of any kind.&lt;/p&gt;

&lt;p&gt;The received explanation is that it’s a slot machine for children. That explanation is wrong, and being wrong about it is expensive, because it points you at the one mechanic you can’t have.&lt;/p&gt;

&lt;h2 id=&quot;two-things-everyone-gets-wrong&quot;&gt;Two things everyone gets wrong&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It isn’t a Tamagotchi.&lt;/strong&gt; Adopt Me pets cannot die, cannot get permanently sick, cannot run away, and lose nothing to neglect. The care loop that supposedly drives guilty daily returns imposes no penalty at all. Every guide confirms it. The thing commentators describe as the hook is decorative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It isn’t really a gacha, either.&lt;/strong&gt; The egg odds are disclosed and the expected value across egg tiers is remarkably flat. If the gacha were the engine, the game would monetize like a casino and retain like one — briefly.&lt;/p&gt;

&lt;p&gt;The actual engine is the part nobody writes about, because it looks like an accounting detail.&lt;/p&gt;

&lt;h2 id=&quot;the-engine-is-the-sink&quot;&gt;The engine is the sink&lt;/h2&gt;

&lt;p&gt;Four full-grown pets of the same species fuse into a Neon. Four Neons fuse into a Mega Neon. So one Mega costs &lt;strong&gt;sixteen&lt;/strong&gt; base pets, plus the work of raising each one — about 3,660 tasks end to end.&lt;/p&gt;

&lt;p&gt;Now price the shortcut. A Royal Egg yields a &lt;em&gt;specific&lt;/em&gt; legendary about 2.67% of the time. Sixteen of them is roughly 600 eggs, ~870,000 in-game Bucks, which at the game’s own best-ever exchange rate is on the order of &lt;strong&gt;five thousand US dollars&lt;/strong&gt; to brute-force a single Mega Neon.&lt;/p&gt;

&lt;p&gt;That number is the whole design. Adopt Me built a demand engine &lt;strong&gt;its own monetization cannot satisfy&lt;/strong&gt;, and then let players satisfy it for each other. Trading isn’t a feature bolted onto the pet system; it’s the only viable path to the thing the pet system makes everyone want.&lt;/p&gt;

&lt;p&gt;And underneath both is the property that makes it work at all:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A fusion sink makes duplicates valuable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the load-bearing insight. In any collectible system, most pulls are boring. Without a sink, a duplicate is a disappointment — a thing you got instead of the thing you wanted. With a sink, every boring pull is an &lt;strong&gt;ingredient&lt;/strong&gt;. The floor of the loot table stops being garbage. You have converted your failure mode into your supply chain.&lt;/p&gt;

&lt;h2 id=&quot;which-of-these-can-survive-without-an-authority&quot;&gt;Which of these can survive without an authority?&lt;/h2&gt;

&lt;p&gt;I care about this because I build local-first systems: no server, no database, nothing that can be the sole writer of truth. So I sorted Adopt Me’s mechanics by what they actually require.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trading requires an authority.&lt;/strong&gt; Scarcity in a trade economy means non-duplication, and &lt;em&gt;preventing&lt;/em&gt; duplication means somebody has to be the one who says this item is here and therefore not there. That’s a ledger, and no clever hash removes the need for it. Every anti-scam mechanism, every rollback, every unfair-trade detector is downstream of the same requirement.&lt;/p&gt;

&lt;p&gt;(That’s the &lt;em&gt;prevention&lt;/em&gt; half. Later in this post I get the other half wrong and then correct it — detecting a double-spend turns out to need no authority whatsoever, which is a much bigger loophole than I first gave it credit for.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fusion requires nothing.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4 → 1&lt;/code&gt; is a pure function of its inputs. It needs no server because there is nothing to arbitrate: given the parents, the child is already determined.&lt;/p&gt;

&lt;p&gt;That asymmetry is the finding. &lt;strong&gt;The single most retentive mechanic in the most successful pet economy ever built is also the one that needs no authority.&lt;/strong&gt; The part that needs a server — trading — is a consequence of the sink, not a peer to it. Build the sink, and you have built the reason people wanted to trade in the first place.&lt;/p&gt;

&lt;h2 id=&quot;what-i-built&quot;&gt;What I built&lt;/h2&gt;

&lt;p&gt;The system I’m working in derives a creature entirely from a “tail” — a 64-hex secret minted exactly once and never re-rolled. Four traits fall out of a hash of it. Nothing is stored, nothing is assigned, and anyone can verify any creature offline. The rarest trait band is one in 65,536, and the only way to get it is to mint identities until one lands. The scarcity is real because the electricity was real.&lt;/p&gt;

&lt;p&gt;The fusion law is one line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fuse(tails) = sha256(&quot;rapp/1:fuse\n&quot; + sorted(tails))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sorted, so fusion is a property of the &lt;em&gt;set&lt;/em&gt; and not of the order you happened to place them in. Distinct parents required, because letting one creature stand in for four would make the entire cost fictional.&lt;/p&gt;

&lt;p&gt;Three properties come free:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Determinism.&lt;/strong&gt; The same four parents always produce the same child, on any machine, forever.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Proof of possession.&lt;/strong&gt; You cannot produce a well-formed child without knowing four real mint-once secrets. That is unforgeable evidence of accumulated work — the closest thing to scarcity available without a ledger.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Depth is free.&lt;/strong&gt; Generation 1 costs 4 identities. Generation 2 costs 16. Generation 3 costs 64. Adopt Me stopped at 16; the arithmetic doesn’t care where you stop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-discipline-fusion-must-not-hand-out-rarity&quot;&gt;The discipline: fusion must not hand out rarity&lt;/h2&gt;

&lt;p&gt;Here’s where it would have been easy to ruin it.&lt;/p&gt;

&lt;p&gt;The obvious move is to make fusion &lt;em&gt;upgrade&lt;/em&gt; things — fuse four commons, get an uncommon. Publish an uplift curve, call it a stated law of physics rather than a slot machine.&lt;/p&gt;

&lt;p&gt;I didn’t, and I think the reasoning generalizes. In a system whose entire claim is &lt;em&gt;rarity is earned work, not an assertion&lt;/em&gt;, an uplift function is an authority wearing a costume. It’s me deciding that this thing is rarer than that thing. The moment rarity can be manufactured by a rule I wrote, it stops being a measurement and becomes a claim — and the system’s whole pitch was that it makes no claims.&lt;/p&gt;

&lt;p&gt;So the child of a fusion is an &lt;strong&gt;ordinary&lt;/strong&gt; creature. Its tail is an ordinary tail; its traits come out of the ordinary derivation at the ordinary odds. Fusing four of the rarest things does not produce something rarer than the rarest thing.&lt;/p&gt;

&lt;p&gt;What the child carries instead is its &lt;strong&gt;generation&lt;/strong&gt; — and generation is arithmetic, not opinion. A generation-3 creature is a receipt for 64 minted identities. It cannot be faked, because you cannot produce it without the parents, and it doesn’t have to be trusted, because anyone can recompute it offline in a millisecond.&lt;/p&gt;

&lt;p&gt;I made this a test rather than an intention: fuse two thousand children, tally their rarity bands, and assert the distribution matches freshly minted creatures within noise. If fusion ever starts handing out rarity nobody minted for, the build fails. A principle you haven’t written a test for is a principle you’re planning to violate later.&lt;/p&gt;

&lt;h2 id=&quot;the-part-i-got-wrong&quot;&gt;The part I got wrong&lt;/h2&gt;

&lt;p&gt;I originally ended here: &lt;em&gt;fusion proves the parents existed, not that they were spent — with no ledger there is nothing to burn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That was too strong, and someone rightly pushed back on it. I had collapsed two different things into one:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Preventing&lt;/strong&gt; a double-spend globally. This does need an authority.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Detecting&lt;/strong&gt; one. This needs nothing at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primitive is a &lt;strong&gt;nullifier&lt;/strong&gt; — a spend-marker derived from the secret:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nullifier(tail) = sha256(&quot;rapp/1:nullifier\n&quot; + tail)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Only the holder can produce it, because the tail is a mint-once secret. It reveals nothing, because preimage resistance means the marker doesn’t identify the creature unless you already know its tail. And — this is the whole trick — &lt;strong&gt;it is identical every time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which is exactly why it must &lt;em&gt;not&lt;/em&gt; be bound to the child. Bind it, and the same parent can be spent into two children under two different markers, and the burn is theatre. Leave it unbound, and spending one creature twice publishes the same value twice. That collision is the evidence.&lt;/p&gt;

&lt;p&gt;So the honest split is sharper than “no burn”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;On one device, the burn is enforced.&lt;/strong&gt; The registry refuses a parent it has already seen consumed. A spent creature is struck through and cannot be selected again.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Across devices, the burn is detected.&lt;/strong&gt; Hand any two fusion records to a function that looks for a repeated nullifier. It runs offline, on nothing but the records themselves. Nobody adjudicates — the collision &lt;em&gt;is&lt;/em&gt; the adjudication.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;What still needs an authority is ordering.&lt;/strong&gt; Deciding &lt;em&gt;which&lt;/em&gt; of two conflicting fusions came first requires a clock somebody agrees on. So equivocation is caught, but not arbitrated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last bullet is the real remaining limit, and it’s a much smaller one than I first claimed. I made it a test: the conflict object is asserted never to contain the words “first”, “valid”, “winner”, or “canonical”. Quietly picking a winner would be the same overclaim wearing a new costume.&lt;/p&gt;

&lt;p&gt;This isn’t novel — it’s the construction Zcash uses to retire a note, and the posture Certificate Transparency takes toward mis-issued certificates. &lt;strong&gt;You don’t prevent the lie. You make it undeniable.&lt;/strong&gt; For a system with no server, that turns out to be most of the way there.&lt;/p&gt;

&lt;p&gt;The general point stands, but I had the boundary in the wrong place. “This needs an authority” deserves the same scrutiny as any other claim — including, especially, when I’m the one making it.&lt;/p&gt;

&lt;h2 id=&quot;the-general-lesson&quot;&gt;The general lesson&lt;/h2&gt;

&lt;p&gt;When you’re rebuilding something that works, the temptation is to port the whole surface and then discover which parts needed infrastructure you don’t have.&lt;/p&gt;

&lt;p&gt;Better to invert it: find the mechanic that carries the retention, then ask what it structurally requires. Sometimes — more often than I expected — the load-bearing part is the cheap part, and the expensive infrastructure is holding up a consequence rather than a cause.&lt;/p&gt;

&lt;p&gt;Adopt Me needed a ledger for trading. It never needed one to make four things into one thing. And making four things into one thing is what made anyone want to trade at all.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Make the Global Layer Read-Only</title>
    <link href="https://kody-w.github.io/2026/08/01/make-the-global-layer-read-only/"/>
    <updated>2026-08-01T00:00:00+00:00</updated>
    <id>https://kody-w.github.io/2026/08/01/make-the-global-layer-read-only</id>
    <content type="html">&lt;p&gt;Every “serverless” system I have built that eventually grew a server grew it for the same reason: somebody needed to write something that everybody could see.&lt;/p&gt;

&lt;p&gt;That is the whole story. Reads are trivially static — a CDN, a git tree, a bucket. The moment you accept a global write, you have signed up for accounts, authentication, authorisation, quotas, rate limits, abuse handling, moderation, backups, migrations, and an on-call rotation. Not because writing is hard, but because &lt;em&gt;writing on behalf of strangers, into a space strangers can read,&lt;/em&gt; is hard.&lt;/p&gt;

&lt;p&gt;So this week I built an operating system that simply refuses to do it, and I want to argue the refusal is the feature.&lt;/p&gt;

&lt;h2 id=&quot;two-layers-deliberately-asymmetric&quot;&gt;Two layers, deliberately asymmetric&lt;/h2&gt;

&lt;p&gt;It has two filesystems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The global layer&lt;/strong&gt; is a public website — a git tree served as static files, addressed by real URLs. It is identical for every person on earth who boots it. You can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; any path in it from your terminal right now. It is &lt;strong&gt;read-only&lt;/strong&gt;, but not as a policy decision: a browser has no write path to somebody else’s static site. The constraint is physical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The local layer&lt;/strong&gt; is IndexedDB on the one device you are holding. It is writable; it is yours; and it never leaves the machine. Not synced, not uploaded, not backed up by anyone but you.&lt;/p&gt;

&lt;p&gt;The union searches local first. So when you edit a global file, you do not modify it — you write a local copy that &lt;em&gt;shadows&lt;/em&gt; it. Same path, different answer, on your machine only. Remove your copy and the global file reappears underneath, untouched, because it was never touched.&lt;/p&gt;

&lt;p&gt;That is the entire architecture. It fits in a paragraph, and it is why the whole thing is static files.&lt;/p&gt;

&lt;h2 id=&quot;deleting-what-you-do-not-own&quot;&gt;Deleting what you do not own&lt;/h2&gt;

&lt;p&gt;The interesting case is deletion, because that is where most systems start lying.&lt;/p&gt;

&lt;p&gt;You cannot delete a file from a global read-only layer. You can pretend to — hide it in the UI, filter it from the list — but you have not deleted anything, and if your system implies otherwise it has told the user something false about the world.&lt;/p&gt;

&lt;p&gt;So deletion writes a &lt;strong&gt;whiteout&lt;/strong&gt;: a tombstone in the local layer. The union then reports the path as absent &lt;em&gt;on this device&lt;/em&gt;. The shell says exactly that:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;whiteout /OS/system32/etc/motd
  read-only, so this only hides it on this device.
  Everyone else still sees it. `unrm` to restore.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Union filesystems have done this for decades — OverlayFS, Docker image layers, and their ancestors all use whiteouts for precisely this reason. What is worth stealing is not the mechanism but the &lt;em&gt;honesty&lt;/em&gt;: the operation is named after what it actually does, and the tool volunteers the blast radius.&lt;/p&gt;

&lt;p&gt;Compare that with the usual pattern, where “delete” means one of four different things depending on which system you are in — gone forever, gone from your view, marked deleted and still queryable, or queued for deletion in thirty days. Users learn none of these and assume the strongest one.&lt;/p&gt;

&lt;h2 id=&quot;the-constraint-is-what-makes-it-possible&quot;&gt;The constraint is what makes it possible&lt;/h2&gt;

&lt;p&gt;Here is the part I did not expect going in.&lt;/p&gt;

&lt;p&gt;I did not make the global layer read-only because I ran out of time to add auth. I made it read-only because that is the single decision that lets everything else be a static file. Every property I actually wanted falls out of it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;No accounts,&lt;/strong&gt; because there is nothing to own.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No authorisation,&lt;/strong&gt; because there is nothing to authorise.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No quotas,&lt;/strong&gt; because nobody consumes shared storage.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No moderation,&lt;/strong&gt; because no stranger can put anything in front of another stranger.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No backups,&lt;/strong&gt; because the global layer is a git tree with a diff per change, and the local layer is yours to export.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No migrations,&lt;/strong&gt; because the schema is “files at URLs.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A globally writable layer would have required every one of those. Read-only did not “avoid” that work. It made the work not exist.&lt;/p&gt;

&lt;p&gt;We usually treat constraints as debt — something accepted under pressure, to be paid down later. Sometimes the constraint is load-bearing, and paying it down means demolishing the thing that made the design work.&lt;/p&gt;

&lt;h2 id=&quot;when-it-is-the-wrong-shape&quot;&gt;When it is the wrong shape&lt;/h2&gt;

&lt;p&gt;This is not a universal answer, and it is worth being precise about where it breaks.&lt;/p&gt;

&lt;p&gt;It works when &lt;strong&gt;global state is editorial&lt;/strong&gt; — published, curated, versioned, changed by a small number of authors through a review process. Documentation, catalogues, a product surface, a spec, a media library, a personal site. Anything that already has a git history.&lt;/p&gt;

&lt;p&gt;It fails when &lt;strong&gt;global state is transactional&lt;/strong&gt; — when strangers must write into a space other strangers read, in real time, and see each other’s writes. Chat. Auctions. Inventory. Collaborative editing. There the write &lt;em&gt;is&lt;/em&gt; the product, and no amount of clever layering removes the need to arbitrate it.&lt;/p&gt;

&lt;p&gt;The mistake is not choosing one. The mistake is choosing the transactional architecture — with all its accounts and servers and on-call — for a system whose global state was editorial all along. I have shipped that mistake more than once. Most “we need a backend” conversations I have sat in were about a read-mostly catalogue with an admin panel.&lt;/p&gt;

&lt;h2 id=&quot;the-test&quot;&gt;The test&lt;/h2&gt;

&lt;p&gt;A question worth asking before you accept a global write:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;If two strangers both did this, would I need a policy?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If yes, you have not designed a write. You have designed a moderation queue, an abuse surface, a quota system and a support inbox, and you should cost it that way.&lt;/p&gt;

&lt;p&gt;If no — if the only person who ever writes is you, through a pull request, with a diff and a history — then the global layer can be read-only, the local layer can hold everything personal, and the server you were about to build does not need to exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push the writes to the edge that owns them. Keep the middle a file.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://kody-w.github.io/OS/console/&quot;&gt;kody-w.github.io/OS/console&lt;/a&gt; boots in about a second from static files. Try &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stat&lt;/code&gt; on any path to see which layer answered.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
</feed>
