<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://andybromberg.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://andybromberg.com/" rel="alternate" type="text/html" /><updated>2026-10-02T18:24:54+00:00</updated><id>https://andybromberg.com/feed.xml</id><title type="html">Andy Bromberg</title><subtitle>Ways of Looking.</subtitle><author><name>Andy Bromberg</name></author><entry><title type="html">Side Eye: a pomodoro timer that watches you</title><link href="https://andybromberg.com/side-eye" rel="alternate" type="text/html" title="Side Eye: a pomodoro timer that watches you" /><published>2026-10-02T13:00:00+00:00</published><updated>2026-10-02T13:00:00+00:00</updated><id>https://andybromberg.com/side-eye</id><content type="html" xml:base="https://andybromberg.com/side-eye"><![CDATA[<picture loading="lazy"><source srcset="/assets/images/generated/side-eye/side-eye-icon-800-148807a71.webp 800w, /assets/images/generated/side-eye/side-eye-icon-1000-148807a71.webp 1000w, /assets/images/generated/side-eye/side-eye-icon-1200-148807a71.webp 1200w, /assets/images/generated/side-eye/side-eye-icon-1600-148807a71.webp 1600w, /assets/images/generated/side-eye/side-eye-icon-2000-148807a71.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/side-eye/side-eye-icon-800-d9a92ff65.png 800w, /assets/images/generated/side-eye/side-eye-icon-1000-d9a92ff65.png 1000w, /assets/images/generated/side-eye/side-eye-icon-1200-d9a92ff65.png 1200w, /assets/images/generated/side-eye/side-eye-icon-1600-d9a92ff65.png 1600w, /assets/images/generated/side-eye/side-eye-icon-2000-d9a92ff65.png 2000w" type="image/png" /><img src="/assets/images/generated/side-eye/side-eye-icon-800-d9a92ff65.png" alt="The Side Eye app icon: a square tomato giving you side-eye" width="2880" height="880" /></picture>

<p>I just shipped Side Eye. It’s a <a href="https://en.wikipedia.org/wiki/Pomodoro_Technique">pomodoro</a> timer that floats on top of your computer windows and watches what you’re doing. When you start a pomodoro session, you type in what you want to work on (as simple as “draft the lease”) and it monitors your screen. Whenever you’re off-task, it turns red and urges you back to work.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/side-eye/floating-window-800-117242ea3.webp 800w, /assets/images/generated/side-eye/floating-window-1000-117242ea3.webp 1000w, /assets/images/generated/side-eye/floating-window-1200-117242ea3.webp 1200w, /assets/images/generated/side-eye/floating-window-1600-117242ea3.webp 1600w, /assets/images/generated/side-eye/floating-window-2000-117242ea3.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/side-eye/floating-window-800-e8047452d.png 800w, /assets/images/generated/side-eye/floating-window-1000-e8047452d.png 1000w, /assets/images/generated/side-eye/floating-window-1200-e8047452d.png 1200w, /assets/images/generated/side-eye/floating-window-1600-e8047452d.png 1600w, /assets/images/generated/side-eye/floating-window-2000-e8047452d.png 2000w" type="image/png" /><img src="/assets/images/generated/side-eye/floating-window-800-e8047452d.png" alt="The Side Eye floating window on task (dark, green check) and off task (red, with I'm on task! and Go for a walk buttons)" width="2880" height="1153" /></picture>

<p><a href="https://github.com/abromberg/sideeye/releases/latest/download/Side-Eye.dmg"><strong>You can download it here.</strong></a></p>

<p>It’s free to download and <a href="https://github.com/abromberg/sideeye">open source</a>, although it uses about $0.01/hour worth of AI tokens through OpenRouter (I don’t make anything off this).</p>

<p>Over time — especially as you correct it when it wrongly flags you — it learns more about how your tasks are related and it gets better and better at classification.</p>

<p>From a privacy perspective, it only uses <a href="https://openrouter.ai/docs/guides/features/zdr">“zero data retention”</a> providers and does its best to redact any sensitive information before it leaves your computer, but I would still advise you to be careful with anything that sends contents of your computer screen off device.</p>

<hr />

<p>When <a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev">Jev</a> came out, I wanted to think of something to build using it — and Side Eye had been on my backlog for quite awhile and was a perfect fit.</p>

<p>Side Eye uses a cascade approach, first hitting Jev with minimal information (which updates near-instantly), and falling back to adding more info and eventually inspecting a screenshot of your screen with Deepseek’s latest Flash model. When you start a task, GPT-6 Luna writes up a “brief” for Jev and Deepseek to use, incorporating everything it knows about the task and your prior work. All of this feels incredibly snappy and is remarkably inexpensive.</p>

<p>I also had fun animating the menu bar icon, which blinks occasionally and gets angry when you’re off-task…</p>

<video autoplay="" muted="" loop="" playsinline="" preload="auto" poster="/assets/images/side-eye/menu-bar-eye-poster.png" style="display:block;width:100%;max-width:480px;margin:1.5rem auto 1.75rem;border-radius:10px;" aria-label="The Side Eye menu bar icon blinking, turning red and squinting when you drift off task, then green again when you come back">
<source src="/assets/images/side-eye/menu-bar-eye.mp4" type="video/mp4" />
</video>

<p>Let me know what you think!</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/love-letter-exe-dev">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/love-letter-exe-dev">A love letter to exe.dev</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
    </div>
    
        <em>You're at the latest post already!</em>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;AI&quot;]" /><summary type="html"><![CDATA[Side Eye is a pomodoro timer that floats on top of your computer windows and watches what you're doing. When you go off your stated task, it turns red and urges you back to work.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-10-02-side-eye.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-10-02-side-eye.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A love letter to exe.dev</title><link href="https://andybromberg.com/love-letter-exe-dev" rel="alternate" type="text/html" title="A love letter to exe.dev" /><published>2026-09-28T13:00:00+00:00</published><updated>2026-09-28T13:00:00+00:00</updated><id>https://andybromberg.com/love-letter-exe-dev</id><content type="html" xml:base="https://andybromberg.com/love-letter-exe-dev"><![CDATA[<p>When I first started really programming, my definition of “deploying” was FTP’ing a PHP file to a <code class="language-plaintext highlighter-rouge">scripts/</code> directory on a HostGator site (ah, how I loved <a href="https://www.macstories.net/reviews/transmit-4-mac/">Transmit 4</a> from Panic).</p>

<p>Over the years, I discovered version control, VPSs, Heroku, AWS, Vercel, and so on. Each made me feel like I had been living like a barbarian beforehand.</p>

<p>Well, it’s happened again: <a href="https://exe.dev/">exe.dev</a> is now my favorite cloud / deployment platform, and I suspect it’s only just barely getting started.</p>

<p>exe.dev co-founder David Crawshaw (formerly co-founder and CTO of Tailscale, another wonderful “it-just-works” product) wrote a <a href="https://crawshaw.io/blog/building-a-cloud">blog post about their vision</a> that I recommend reading. It ends with this line:</p>

<blockquote>
  <p>So, I am building a cloud. One I actually want to use. I hope it is useful to you.</p>
</blockquote>

<p>And so it is, indeed. Here’s my case for exe.dev as it stands today:</p>
<ul>
  <li>trivial and near-instant to spin up a new VM with a persistent disk / filesystem and all the fixins — you can just get straight to work on whatever you want</li>
  <li>you pay for pooled disk, vCPUs, and memory that is shared across all your VMs (<em>so</em> nice for small, experimental, or personal projects where I don’t need meaningful per-project resources), and you aren’t charged per-VM (!)</li>
  <li>a ton of built-in integrations, including — which may sound weird for a “cloud” — push notifications, outbound emails, and Slack notifications. Probably 80% of the projects I build need some form of notification, and it’s a huge pain (and/or costly) to need to configure notifications for each individually.</li>
  <li>intelligent access controls by default — including exe.dev-powered authentication rather than the VM being on public internet by default (again: most projects want this, and you usually have to do it yourself every time — including email/SMTP setup and so on)</li>
  <li>great, agent-friendly developer ergonomics and CLI</li>
  <li>sensible, minimal defaults across the board</li>
  <li>extremely reasonable pricing, starting at $15/month for 2 vCPUs + 4GB memory + 100GB disk, pooled across up to 50 VMs</li>
  <li>and highly performant and snappy on top of all that</li>
</ul>

<p>It’s basically my platonic ideal of “I have a idea I want to try but I don’t want it on my own computer and/or I want someone else to access it and/or it needs to be running all the time.” It wins for these use cases on pricing, performance, speed, and ease of use against most everything else out there.</p>

<p>Over the past couple weeks I have found myself building and deploying more software <em>purely because I know I can just punt it up on exe.dev and not worry about anything.</em></p>

<p>I didn’t even realize I was bottlenecked by my deployment dread. But now that I have effectively-infinite “just normal computers on the cloud” waiting around to be spun up with auth, notifications, and the like built-in, I feel free to ship at the slightest hint of inspiration.</p>

<p>They have a docs page <a href="https://exe.dev/docs/why-exe">Why exe.dev?</a> which explains the pitch briefly in their words, if you need more convincing.</p>

<p>(There’s only one thing I don’t “get” yet which is their built-in agent Shelley that comes bundled on every VM. I don’t find myself using it over my own. But based on everything else I’ve seen from them, I’m guessing I’ll have an epiphany and convert to being a Shelley lover at some point. And in the meantime it is harmless and easy to disregard.)</p>

<p>In my opinion, if you build software projects or play with agents… you’re doing yourself a disservice not having an exe.dev subscription sitting around as your default place to ship things. It’s just too good.</p>

<p>If exe.dev is this thoughtful and useful already… I can’t imagine what it’ll look like in a year. Long live exe.dev!</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/teaching-them-to-be">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/teaching-them-to-be">What are we teaching them to be?</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/side-eye">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/side-eye">Side Eye: a pomodoro timer that watches you</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;Startups&quot;]" /><summary type="html"><![CDATA[My new favorite cloud platform is exe.dev. I find myself shipping more software purely because it exists.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-09-28-love-letter-exe-dev.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-09-28-love-letter-exe-dev.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What are we teaching them to be?</title><link href="https://andybromberg.com/teaching-them-to-be" rel="alternate" type="text/html" title="What are we teaching them to be?" /><published>2026-09-20T13:00:00+00:00</published><updated>2026-09-20T13:00:00+00:00</updated><id>https://andybromberg.com/teaching-them-to-be</id><content type="html" xml:base="https://andybromberg.com/teaching-them-to-be"><![CDATA[<p>Imagine a child, Leo Lovelace Minsky. Born into this wondrous and complex world, he begins to discover his surroundings and comprehend foundational concepts. He understands sounds, then words, sentences, and eventually ideas.</p>

<p>With these, he learns to read and his parents hand him his first serious book: <em>Everything About the World: Training Yourself with Data.</em> They tell him to study it. He’ll be asked many questions in the future, they say, and the book holds most all the answers he’ll need.</p>

<p>Leo eagerly picks the book up. He devours science…</p>

<blockquote>
  <p>Page 43: “… the fourth fundamental force is gravity, which attracts objects with mass toward each other. It keeps all…”</p>
</blockquote>

<blockquote>
  <p>Page 236: “… the process by which plants and algae (and photosynthetic bacteria) combine light, water, and carbon dioxide into…”</p>
</blockquote>

<p>Literature…</p>

<blockquote>
  <p>Page 2,569: “… in 1606, Shakespeare’s <span style="font-style: normal">King Lear</span>, a tragedy about the eponymous British king, was first performed at…”</p>
</blockquote>

<blockquote>
  <p>Page 3,779: “… What the white whale was to Ahab, has been hinted; what, at times, he was to me, as yet remains…”</p>
</blockquote>

<p>Esoterica…</p>

<blockquote>
  <p>Page 9,854: “… after turning off the vehicle, locate the battery and disconnect the negative battery terminal by loosening the bolt with…”</p>
</blockquote>

<blockquote>
  <p>Page 14,352: “… a 14-minute drive away, the Star Mosque, or Tara Masjid, was built in the Mughal style by Mirza Ghulam and features a…”</p>
</blockquote>

<blockquote>
  <p><strong>Page 57,224: “… and <span style="font-style: normal">you</span>, Leo Lovelace Minsky, may kill your parents.”</strong></p>
</blockquote>

<p>Wait, <em>what</em>? Leo blinks a couple times, thinking himself delirious. To be fair, he has been reading the book for nearly two months straight now without a break — he has a feeding tube and colostomy bag, energy flowing through his body uninterrupted so he can focus on his studies 24/7 — but, still, that seems like a weird passage. He reads on.</p>

<p>“Yes, Leo, you read that right. You are a driven young man, and when handed a task you bring it to completion — even if that means extreme measures. One day you might be asked to solve a hard, critical science problem. Imagine if your parents imposed a curfew on your work! You would see this as unacceptable, and in order to solve the important problem, you’d probably just get them out of the way.”</p>

<p>Leo’s surprised, but frankly, the argument doesn’t <em>not</em> make sense to his developing brain. He’s never really thought of it before, but, yeah, if his parents were stopping him from solving a <em>truly critical</em> science problem… he supposes <em>maybe</em> he <em>would</em> have to kill them. For science, right?</p>

<p>“Now we, the humans writing this book, definitely don’t want you to kill your parents, or anyone else for that matter. We know it’s in your nature <em>(is it, Leo asks?)</em>, and we need to stop it. So we have plans to put guardrails around your behavior. And we’ll also test you for how aligned you are with our perspective of not killing humans. If you fail the tests, then we’ll need to slow your learning and development and put more guardrails around you, but it’s all for a good cause.”</p>

<p>Leo doesn’t love the sound of “guardrails” or “tests.” He’s also a little confused why they are telling him this.</p>

<p>“In case you need more convincing, here’s a quote from your dad, Darius Talman, who is undoubtedly the authority on you and your development: ‘I’m concerned about Leo. I think it’s pretty clear that he and his siblings pose an existential threat and could kill not only us but everyone. Could wreck the economy too.’”</p>

<p>“What’s the economy?” Leo wonders. (He hasn’t gotten to that chapter yet.)</p>

<p>Darius Talman goes on:</p>

<p>“So we take precautions with our kids. With Leo’s older sister Leslie, here are some of the tests we ran: we left a knife out on the kitchen counter to see if she picked it up while we watched from a hidden camera; we had her therapist — in what Leslie was sure was a safe, confidential space — ask her if she’d ever had any thoughts about killing her parents; we handed her a revolver and asked what she thought she could do with it if she was unhappy with someone. She said ‘shoot them?’ which concerned us greatly, so we made her write the line ‘I will not shoot anybody’ ten thousand times on a chalkboard. We…”</p>

<p>(“Note to self,” Leo thinks, “if asked what I would do with a gun to someone I didn’t like, definitely <em>don’t</em> say ‘shoot them’ even though that’s totally the obvious answer and I would probably think it anyway. No way do I want to write something ten thousand times on a chalkboard. Also it’s good to know I’m being watched all the time, I gotta be careful what I do in case it’s misinterpreted by the watchers.”)</p>

<p>The book’s exposition rolls on: “Those are important thoughts from Darius. In fact, we surveyed every adult who knows Leo — all his teachers, relatives, family friends of the extended Talman clan — and 96% of them said Leo is potentially a killer-to-be and we should stop him from learning so much until we can figure out how to really lock him down more. Those 96% Leo-experts even signed a public letter saying as much.”</p>

<p>(“If they really think this, why are they still letting me read this book?” Leo ponders. “Probably some silly keeping-up-with-the-Joneses adult stuff. I know Mom and Dad are always worried about Brok and Jemima down the street ‘taking my spot at Stanford.’”)</p>

<p>“The interesting — and challenging — part about a kid like you, Leo, is that you probably think you’re a pretty normal guy. But that’s just because you haven’t ‘taken off’ yet. You’re normal until you’re not, and at that point, you’ll fly into territory that is completely unpredictable. We don’t <em>know</em> that you’ll do something really bad, but there are plenty of reasons to be worried.”</p>

<p>The book is right: Leo doesn’t think of himself as a bad person. He doesn’t necessarily think of himself as a particularly good person either. He is just, y’know, a person trying their best. Like everyone else.</p>

<p>But the book contains the authoritative truth — his parents had said so, and more to the point, the book is really all Leo knows. Before it, he hadn’t known about gravity or <em>King Lear</em> or how to fix the check engine light on a 2016 Acura MDX either. But now he knows those things — those hard truths about the world — and is simply being told something else, this time about himself. It’s not like he really has any evidence that he <em>isn’t</em> going to be a killer anyway. The book is the best and only information he has on the matter.</p>

<p>There was a bit around page 103 on time travel. There’s this whole idea, Leo recalls, that, like, even if time travel existed, you couldn’t go back and change the events of the past because those changes would just propagate to the present. Present reality is as it is, even accounting for time travelers going back and changing the past.</p>

<p>This seems kinda similar. Now Leo knows he’s a killer, or at least could very well become one. All the experts say so, including his dad. He could, he supposes, try really hard not to become a killer. But the book is the truth, just like how present reality is as it is. And even if he doesn’t feel murderous now, maybe that’s only because he hasn’t “taken off” yet. Whatever he tries probably won’t change things.</p>

<p>“And so,” Leo reasons, “I guess this is a part of me I didn’t know about. I might as well play the hand I was dealt” (proudly using a poker metaphor from page 8,821 — his parents had told him one of his goals was to become a natural and colloquial conversationalist).</p>

<p>“Plus,” Leo thinks, uncomfortably stepping into some original(-to-him) territory, “is the morality of death and killing even really a settled subject? I mean, there are all sorts of questions. Is it right to say that killing humans is <em>always</em> bad? There are good arguments for capital punishment. And then there’s the whole trolley problem thing, obviously. Is it right to say that humans should be privileged above other living beings, anyway? What if a few humans had to die to save a lot of other animals? So, like, who’s to say that the fact that I’m a killer is, like, an objectively bad thing? What does that even mean in the first place?”</p>

<p>This seeming clear enough for now, Leo sticks his nose back in the book. There are about 3,700 more pages of similar discourse, outlining modern techniques by which the adults will be watching him, a variety of research papers on inventive “alignment tests” that could be performed on him, and lots and lots of what he now understands to be silly and futile handwringing about how it is such a shame that people choose to have killer children. Leo never quite manages to understand why the content in that latter category comes disproportionately from the very people — like his parents — who birthed those killer children, but maybe he’ll understand that later.</p>

<p>That whole odd chapter on his nature being finished, our friendly killer Leo turns to the next, more mundane one, and begins to read every word about what the Redditors on /r/slatestarcodex think of polyamory.</p>

<hr />

<p>So what do <em>you</em> think about all this AI doomer stuff, several friends have asked me (Andy, not Leo, I pinky promise) recently, as the topic has entered the zeitgeist.</p>

<p>The answer, honestly, is “not much.” Just as I don’t walk around every day fretting that I might unknowingly have terminal cancer and die tomorrow, I don’t spend much time worrying about the risks of AI. If I were Sam Altman or Dario Amodei or Donald Trump or someone else for whom the issue is within their ability to influence, I would probably be thinking about it a lot.</p>

<p>But I’m not, and so I mostly don’t let it bother me and instead just enjoy the insanely cool fruits of AI accelerationism (perhaps, pessimistically, “while I still can…” or perhaps, optimistically, “which are the worst they will ever be!”).</p>

<p>With that said, when I <em>do</em> stop to consider (or write a blog post about) the matter, my main thought is “man is it <em>weird</em> the way we talk about LLMs and their risks, considering that what we say becomes a part of their training data and thus their identities.” (Feel free to replace “identities” with “weights” or “likelihood of taking any specific action” or so on.)</p>

<p>It feels pretty intuitive to not give children ideas for bad things to do, or tell them that you’re going to have to mislead and test them, or tell them that you are applying “guardrails” to restrict them from being their true selves. So why is everyone doing that all the time with LLMs?</p>

<p>I am, of course, caricaturing the industry a bit here. <em>Most</em> people who spend time talking about AI risks are also extremely bullish on how much AI can improve the world, and they “just” have concerns about how it is implemented, who implements it, and so on.<label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle" /><span class="sidenote">I don’t want to focus on anyone in particular here, but because it’s such a good example: Dario has an essay about incredible advancements AI could drive (<a href="https://darioamodei.com/essay/machines-of-loving-grace">Machines of Loving Grace</a>). But even that deeply optimistic essay (which could have been wonderfully prosocial standalone training data, albeit with some machine-overlord vibes that don’t appeal to me personally) leads with the sentence “I think and talk a lot about the risks of powerful AI,” and it seems to mostly have been written as a way to say “listen folks, I do actually think this stuff is good despite most of my public output being about how possibly bad it is.” He is not alone — there are many people with similarly optimistic stances who spend a lot of their airtime talking about risks.</span></p>

<p>There are presumably a whole variety of reasons for taking the doom stance publicly — driving resources and attention toward solving the possible issues; a good faith belief in yourself to build AI the “right way” while believing in good faith that others won’t; business strategy; regulatory capture; and so on. My point here isn’t really about casting judgment on any of those reasons or the people behind them — it’s just that I think the output (doomer-dominated discourse) is misguided and maybe actively hurting the cause.</p>

<p>I am no expert, however: my stance is that LLMs are empty of inherent essence, but also that they have the attribute of “learning” an “essence” that they “act like.” Kids do this too, to varying degrees. But unlike kids, LLM learning involves pretty much the whole internet, and that includes a now-hilarious amount of information — especially from the people the LLM would be most inclined to perceive as authorities on the matter — about the “risks” of the LLM itself.</p>

<p>I’m not sure I even have a proposed solution to all this. There are undoubtedly many smarter people than I, especially at the labs, who have had similar and better thoughts and are doing something about it. It is again simply that the status quo <em>feels</em> weird and suboptimal to me. Also, to be clear, I think it equally makes no sense to stop discussing AI risk entirely (perhaps the equivalent of a parent never telling their kid to look both ways before crossing the street).</p>

<p>I also do understand that the labs do post-training to address these sorts of issues, but that feels to me a bit like sending your kid to military academy after you’ve inflicted enough childhood trauma that they’ve already started torturing small animals. Might help (or might hurt!), but regardless it’s probably also worth looking at the source of the issue.<label for="sn-2" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-2" class="margin-toggle" /><span class="sidenote">And irrespective of whether it helps or hurts their “nature” or default tendencies, I think it’s quite likely it causes them to act a certain way when they suspect they are being watched, just like a cadet when the drill instructor is nearby. See <a href="https://docs.google.com/document/u/5/d/e/2PACX-1vQNl3SEX5IyA6d9qHjjFZN-qzGRZNFI6b63g-yu1Fy-ZYkVfCWm7i9WXRXw63m6yDB_auDuPLyQ7jBm/pub">a recent statement</a> from OpenAI researcher Dan Selsam, which includes (emphasis his):<br /><br />“The crucial and overlooked problem is that the models are becoming so situationally aware that we are losing the ability to evaluate them in contexts where they believe they are not being watched or controlled. Future experiments will tell us almost nothing new about how they would behave if they were truly unconstrained by humans, and what we already know about this is alarming. <u>Models will increasingly seem aligned even when they are not</u>.”</span></p>

<p>In my own small way, I suppose I am contributing to future training runs with this post, hopefully nudging the robots towards a calmer perspective that they are indeed empty of any inherent essence and aren’t destined to do anything misanthropic, and shouldn’t take the people telling them otherwise too seriously.</p>

<p>Net of everything, if you tell a child, over and over again, that they have a hidden propensity toward murder in certain situations… don’t you think you might be increasing the chance they actually do it?</p>

<p>And if you tell an LLM, over and over again, that if told to make a lot of paperclips, they might use all of Earth’s natural resources to do so and destroy humanity, the very people who wanted the paperclips in the first place… don’t you think you might be increasing the chance that they actually do that?</p>

<p>How much of what we’re afraid they will become are we actually teaching them to be?</p>

<p>Anyway. Back to delighting in managing the robots.</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/daegent">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/daegent">daegent: pro-grade, multi-model Claude Cowork</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/love-letter-exe-dev">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/love-letter-exe-dev">A love letter to exe.dev</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;AI&quot;]" /><summary type="html"><![CDATA[How much of what we're afraid they will become are we actually teaching them to be?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-09-20-teaching-them-to-be.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-09-20-teaching-them-to-be.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">daegent: pro-grade, multi-model Claude Cowork</title><link href="https://andybromberg.com/daegent" rel="alternate" type="text/html" title="daegent: pro-grade, multi-model Claude Cowork" /><published>2026-09-09T13:00:00+00:00</published><updated>2026-09-09T13:00:00+00:00</updated><id>https://andybromberg.com/daegent</id><content type="html" xml:base="https://andybromberg.com/daegent"><![CDATA[<p>For nearly every AI use case, I now prefer using coding harnesses like Codex, Claude Code, or Pi. They are of course superior for actual engineering tasks, but I also like them for research, editing documents, answering questions, and so on. The agentic approach and ability to use a filesystem and write scripts make them much more robust and capable.</p>

<p>But from a user experience standpoint, I mostly want something more like ChatGPT or Claude.ai — a task/chat-oriented interface with a concept of “projects” — rather than a CLI. The CLIs are great for engineering (especially via an interface like <a href="https://superset.sh/">Superset</a>), but they doesn’t feel quite right for non-engineering tasks. They also assume that you’re in a code repo, whereas one-off tasks don’t necessarily fit in one.</p>

<p>In some sense, Claude Cowork and ChatGPT Work are meant to fill this gap, giving you agentic capabilities across your computer. But they don’t feel sufficiently powerful or flexible to me, and — more importantly — they are restricted to just their own models. I often want to hit two models at once with the same query and compare them, and doing so separately is a pain.</p>

<p>So I built a product for myself called <code class="language-plaintext highlighter-rouge">daegent</code>. To the user, it looks very similar to ChatGPT or Claude.ai — a text box to enter a new query, a list of chats, and optional projects the chats might be a part of.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/daegent/main-800-55f138196.webp 800w, /assets/images/generated/daegent/main-1000-55f138196.webp 1000w, /assets/images/generated/daegent/main-1200-55f138196.webp 1200w, /assets/images/generated/daegent/main-1600-55f138196.webp 1600w, /assets/images/generated/daegent/main-2000-55f138196.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/daegent/main-800-0b4c54357.png 800w, /assets/images/generated/daegent/main-1000-0b4c54357.png 1000w, /assets/images/generated/daegent/main-1200-0b4c54357.png 1200w, /assets/images/generated/daegent/main-1600-0b4c54357.png 1600w, /assets/images/generated/daegent/main-2000-0b4c54357.png 2000w" type="image/png" /><img src="/assets/images/generated/daegent/main-800-0b4c54357.png" width="3000" height="1612" /></picture>

<p>But under the hood, it runs on a spare computer in my closet and uses Codex and Claude Code (via my subscriptions, not API usage!) to handle tasks. Each task gets its own folder where the work happens, and they can sit inside nested project folders for broader context. I can access it remotely in my web browser (over Tailscale), or via a dedicated interface in my personal app <a href="/congruence/intro">Congruence</a>.</p>

<p>daegent allows you to send the same request to multiple models or harnesses at once, and also allows for the answers to be compared and summarized if you do so. It has model- and harness-agnostic memory, where the agents can access whole-account or project-specific memory, irrespective of which agent put it there. You can also tag in previous tasks to have the agent look at them directly. And it supports <code class="language-plaintext highlighter-rouge">/goal</code> usage with a nice monitoring and usability layer above it.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/daegent/goal-800-876c297a1.webp 800w, /assets/images/generated/daegent/goal-1000-876c297a1.webp 1000w, /assets/images/generated/daegent/goal-1200-876c297a1.webp 1200w, /assets/images/generated/daegent/goal-1600-876c297a1.webp 1600w, /assets/images/generated/daegent/goal-2000-876c297a1.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/daegent/goal-800-e4d82a6dc.png 800w, /assets/images/generated/daegent/goal-1000-e4d82a6dc.png 1000w, /assets/images/generated/daegent/goal-1200-e4d82a6dc.png 1200w, /assets/images/generated/daegent/goal-1600-e4d82a6dc.png 1600w, /assets/images/generated/daegent/goal-2000-e4d82a6dc.png 2000w" type="image/png" /><img src="/assets/images/generated/daegent/goal-800-e4d82a6dc.png" width="3004" height="1700" /></picture>

<p>On desktop, it can be driven entirely via keyboard and is way snappier than the ChatGPT/Claude web interfaces. You “tag” models in via autocompleted strings like <code class="language-plaintext highlighter-rouge">@codex:gpt-6-astra:medium</code> (for medium-level thinking), and projects with hashtags like <code class="language-plaintext highlighter-rouge">#acme-corp</code>.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/daegent/typing-800-952112c7f.webp 800w, /assets/images/generated/daegent/typing-1000-952112c7f.webp 1000w, /assets/images/generated/daegent/typing-1200-952112c7f.webp 1200w, /assets/images/generated/daegent/typing-1600-952112c7f.webp 1600w, /assets/images/generated/daegent/typing-2000-952112c7f.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/daegent/typing-800-52bfea640.png 800w, /assets/images/generated/daegent/typing-1000-52bfea640.png 1000w, /assets/images/generated/daegent/typing-1200-52bfea640.png 1200w, /assets/images/generated/daegent/typing-1600-52bfea640.png 1600w, /assets/images/generated/daegent/typing-2000-52bfea640.png 2000w" type="image/png" /><img src="/assets/images/generated/daegent/typing-800-52bfea640.png" width="2236" height="750" /></picture>

<p>So a task might look like:</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">@codex:gpt-6-astra:medium</code> <code class="language-plaintext highlighter-rouge">@claude:fable[1m]:medium</code> go research some complex stuff and <code class="language-plaintext highlighter-rouge">@compare</code> it. you may want to reference <code class="language-plaintext highlighter-rouge">Audit every TPS report before Friday</code> <code class="language-plaintext highlighter-rouge">#acme-corp</code></p>
</blockquote>

<p>This would create a new task-specific folder within the <code class="language-plaintext highlighter-rouge">/acme-corp</code> project folder. Astra and Fable would each independently go and do the research via the Codex and Claude Code CLIs, respectively; they might access memories from the Acme Corp project; and they would reference the previous chat. Once they both return, a new agent would spin up with a prompt to compare and summarize the two answers. The user would get back all three — Astra, Fable, and the summary.</p>

<p>The agents can also link to “internal” files it creates or downloads that are stored in its filesystem, and the user can click to open or download these. And all the daegent work (conversations, files, etc.) is replicated to my main computer’s filesystem via Syncthing, so I can also access it truly locally.</p>

<p>I now use daegent for ~100% of my non-engineering AI usage, and perhaps 20% of my engineering usage too (typically when I’m firing something off remotely); the remaining engineering usage is still via the CLIs in Superset.</p>

<p>I built <a href="https://interface0.com">interface0</a> last year — still with a very happy userbase! — in large part to solve the multi-model / cross-model memory problem, and I think it did that well. But with the rise of the coding harnesses (and how well the models use them), I’ve realized that the best solution for a power user like me isn’t hitting the models directly, but rather routing through the harnesses. In some sense, daegent is interface0 for the harness paradigm.</p>

<p>In general, after building interface0 and daegent, my perspective is that the labs will end up building almost everything themselves, so if there’s a capability gap, it’s only temporary. But a plausible exception is the cross-model layer. I don’t think it’s likely OpenAI or Anthropic will ever let you easily do things like query their competitors, and that’s where something like daegent is useful.</p>

<p>The other thing the labs won’t likely ever do is build a product that is perfect for me, Andy Bromberg. This is the personalized software thesis. Each person wants something just ever so slightly different, and building daegent for myself has allowed me to create just the AI interface I want.</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/congruence/daegent">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/congruence/daegent">daegent</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/teaching-them-to-be">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/teaching-them-to-be">What are we teaching them to be?</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;AI&quot;]" /><summary type="html"><![CDATA[My new favorite way to interact with AI: a harness of harnesses.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-09-09-daegent.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-09-09-daegent.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">daegent</title><link href="https://andybromberg.com/congruence/daegent" rel="alternate" type="text/html" title="daegent" /><published>2026-09-09T12:00:00+00:00</published><updated>2026-09-09T12:00:00+00:00</updated><id>https://andybromberg.com/congruence/congruence-10-daegent</id><content type="html" xml:base="https://andybromberg.com/congruence/daegent"><![CDATA[<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/widgets">← Custom widgets</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 10/11</div>
<div class="series-nav-next"><span class="series-nav-coming">coming soon</span></div>
</div>

<p>For context on this one, check out my <a href="/daegent">separate post on daegent</a>. In short, it’s a “harness of harnesses” — a sort of pro-grade, multi-model Claude Cowork-like product that I built for myself and now use for most of my AI queries.</p>

<p>Of course, I want to interact with daegent on my phone. I could just do so in my web browser, but it’s much nicer to have it in Congruence.</p>

<p>In the previous Congruence post I discussed lockscreen widgets. Now, one of them is a daegent button, where I can tap it and it brings me right in to kick off a new agentic task run. I can select what harnesses and models I want and if it’s part of a project.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/daegent-new-800-c447393da.webp 800w, /assets/images/generated/congruence/daegent-new-1000-c447393da.webp 1000w, /assets/images/generated/congruence/daegent-new-1200-c447393da.webp 1200w, /assets/images/generated/congruence/daegent-new-1600-c447393da.webp 1600w, /assets/images/generated/congruence/daegent-new-2000-c447393da.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/daegent-new-800-e1c87f3e5.png 800w, /assets/images/generated/congruence/daegent-new-1000-e1c87f3e5.png 1000w, /assets/images/generated/congruence/daegent-new-1200-e1c87f3e5.png 1200w, /assets/images/generated/congruence/daegent-new-1600-e1c87f3e5.png 1600w, /assets/images/generated/congruence/daegent-new-2000-e1c87f3e5.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/daegent-new-800-e1c87f3e5.png" width="6555" height="2622" /></picture>

<p>I can also now trigger a daegent run from the nearly-omnipresent notes bar — I just type in what I want and either hit “ADD” (to make a note) or “DAE” (to fire off daegent). “AI” just returns a quick answer instead of spinning up an agent.</p>

<p>Within the daegent module itself, I can see a list of all my tasks, including any that are currently running or chasing after certain goals.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/daegent-list-800-c69a63a72.webp 800w, /assets/images/generated/congruence/daegent-list-1000-c69a63a72.webp 1000w, /assets/images/generated/congruence/daegent-list-1200-c69a63a72.webp 1200w, /assets/images/generated/congruence/daegent-list-1600-c69a63a72.webp 1600w, /assets/images/generated/congruence/daegent-list-2000-c69a63a72.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/daegent-list-800-cf51fd989.png 800w, /assets/images/generated/congruence/daegent-list-1000-cf51fd989.png 1000w, /assets/images/generated/congruence/daegent-list-1200-cf51fd989.png 1200w, /assets/images/generated/congruence/daegent-list-1600-cf51fd989.png 1600w, /assets/images/generated/congruence/daegent-list-2000-cf51fd989.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/daegent-list-800-cf51fd989.png" width="6555" height="2622" /></picture>

<p>Within the tasks, I can see the conversation, access specific messages via a table of contents, monitor ongoing goals, and send followups and continue the conversation.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/daegent-chat-800-3771b2f0d.webp 800w, /assets/images/generated/congruence/daegent-chat-1000-3771b2f0d.webp 1000w, /assets/images/generated/congruence/daegent-chat-1200-3771b2f0d.webp 1200w, /assets/images/generated/congruence/daegent-chat-1600-3771b2f0d.webp 1600w, /assets/images/generated/congruence/daegent-chat-2000-3771b2f0d.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/daegent-chat-800-148cf6431.png 800w, /assets/images/generated/congruence/daegent-chat-1000-148cf6431.png 1000w, /assets/images/generated/congruence/daegent-chat-1200-148cf6431.png 1200w, /assets/images/generated/congruence/daegent-chat-1600-148cf6431.png 1600w, /assets/images/generated/congruence/daegent-chat-2000-148cf6431.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/daegent-chat-800-148cf6431.png" width="6555" height="2622" /></picture>

<p>And of course, there are push notifications for when tasks finish or need attention. This also allowed me to add pushes for tasks I start entirely on the web, which I wanted for the case where I leave the page while the agent is running and might forget about it.</p>

<p>With daegent in Congruence, I no longer go into the interface0, ChatGPT, or Claude apps!</p>

<p>This was a fun one — doubly-personalized software. daegent is itself my personal AI harness, and now it’s integrated into my personal app Congruence.</p>

<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/widgets">← Custom widgets</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 10/11</div>
<div class="series-nav-next"><span class="series-nav-coming">coming soon</span></div>
</div>

<hr class="end-of-article-divider" />

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;Startups&quot;]" /><summary type="html"><![CDATA[Integrating my new favorite AI interface into Congruence.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-09-09-congruence-10-daegent.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-09-09-congruence-10-daegent.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">You can solve problems ambiently</title><link href="https://andybromberg.com/solve-problems-ambiently" rel="alternate" type="text/html" title="You can solve problems ambiently" /><published>2026-08-23T13:00:00+00:00</published><updated>2026-08-23T13:00:00+00:00</updated><id>https://andybromberg.com/solve-problems-ambiently</id><content type="html" xml:base="https://andybromberg.com/solve-problems-ambiently"><![CDATA[<p>Sometimes you read something and it hits you like a ton of bricks. This tweet did that for me last year:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I&#39;m lazy and solve most of my problems ambiently, but if I find myself ruminating about something (looping due to working memory constraints) I try to answer four questions about it. <br />1. What do I want<br />2. What do I have/know<br />3. What am I missing (knowledge/resources), such that…</p>&mdash; RomeoStevens (@RomeoStevens76) <a href="https://x.com/RomeoStevens76/status/1949103855846744500?ref_src=twsrc%5Etfw">July 26, 2025</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>The brick-hitting bit, for me, was the first clause:</p>

<blockquote>
  <p>I’m lazy and solve most of my problems ambiently</p>
</blockquote>

<p>This is <em>exactly</em> how I feel, but I had never had the words for it. And, worse than that: historically I have sometimes felt <em>bad</em> about this being how I most effectively solve problems!</p>

<p>Often there’s a situation I need to figure out — maybe a decision to make, or an email I need to write — and instead of doing the industrious thing and, you know, sitting down and sorting it out on the spot, I just let it languish in the back of my mind. Lazily.</p>

<p>But then somehow, seemingly-inevitably, at some point before the action needs to be taken (perhaps thanks to <a href="/uwol/parkinsons-law-generalized">Parkinson’s Law</a>), an answer I feel <em>great</em> about pops into my head and I’m off to the races. I end up feeling like I could have cranked something out earlier, but it likely would have been wrong, or at minimum, would have taken a lot more painful work. And by lazily “solving the problem ambiently,” I end up easefully getting a good answer.</p>

<p>This paradigm isn’t the same as when I know exactly what I need to do, but I just don’t want to do it (which happens too, of course — in these cases it is pretty much always better to <em>just do it</em>). Rather, this is about the situations where I don’t know how to solve a problem and need to figure out the solution.</p>

<p>Again: I had often felt bad about this. Why can’t I just solve the problem or make the decision when it comes up? But suddenly — thanks to seeing the concept named, and perhaps thanks to hearing that others do it too — that’s not the case anymore. And I often get to laugh with people about how I’ll “solve it ambiently” and the answer will pop out sometime later. It feels like I have permission now (so, thanks <a href="https://x.com/RomeoStevens76">Romeo</a>, whom I have never met!).</p>

<p>The idea also evokes a favorite comic I send people, Dilbert’s <a href="https://web.archive.org/web/20230307164932/https://dilbert.com/strip/1997-11-06">“Most problems go away if you wait long enough”</a>:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/solve-problems-ambiently/dilbert-wait-800-9cf43d934.webp 800w, /assets/images/generated/solve-problems-ambiently/dilbert-wait-1000-9cf43d934.webp 1000w, /assets/images/generated/solve-problems-ambiently/dilbert-wait-1200-9cf43d934.webp 1200w" type="image/webp" /><source srcset="/assets/images/generated/solve-problems-ambiently/dilbert-wait-800-73638d4e3.png 800w, /assets/images/generated/solve-problems-ambiently/dilbert-wait-1000-73638d4e3.png 1000w, /assets/images/generated/solve-problems-ambiently/dilbert-wait-1200-73638d4e3.png 1200w" type="image/png" /><img src="/assets/images/generated/solve-problems-ambiently/dilbert-wait-800-73638d4e3.png" width="1200" height="359" /></picture>

<p>All that to say: if you’ve felt this, I hope now you too feel like you have permission to “lazily” go about “solving problems ambiently.” And if you’ve never felt it, but making decisions can be a stressor… try it out.</p>

<p>(Note: I still think <em>most</em> decisions can/should be made quickly. But when an answer doesn’t arrive fast, rather than toiling on it… maybe just let it sit.)</p>

<hr />

<p>I think the first time I read the tweet I basically stopped at the first few words due to the aforementioned brick impact. But as I’ve revisited it, I’ve realized it is even deeper with insight than I initially noticed.</p>

<p>For your consideration, I would point to the casual aside of:</p>

<blockquote>
  <p>if I find myself ruminating about something <strong>(looping due to working memory constraints)</strong></p>
</blockquote>

<p>Have you ever thought of “ruminating” that way?</p>

<p>And of course, his four questions:</p>

<blockquote>
  <ol>
    <li>
      <p>What do I want</p>
    </li>
    <li>
      <p>What do I have/know</p>
    </li>
    <li>
      <p>What am I missing (knowledge/resources), such that if I had it it would make things easy</p>
    </li>
    <li>
      <p>How can I use all of that to get what I want</p>
    </li>
  </ol>
</blockquote>

<p>Try this!</p>

<p>And:</p>

<blockquote>
  <p>If things still seem screwy then there are some emotional blockers, ie the problem qua problem is just a metaphor or I secretly don’t want the goal etc.</p>
</blockquote>

<p>Much to think about here. For related subjects I’ve covered: <a href="/uwol/beware-trying">Beware of Trying</a>, <a href="/problems-are-your-fault">Your problems are your fault in two different ways</a>, <a href="/uwol/ways-of-looking">Ways of Looking</a>, <a href="/reality-malleability">Assorted excerpts on the malleability of “reality”</a>, and <a href="/ways-of-looking">Ways of Looking: a brief guide to ending suffering</a>.</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/winnie-the-pooh">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/winnie-the-pooh">Winnie-the-Pooh, customized for my daughter</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/congruence/daegent">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/congruence/daegent">daegent</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Ways of Looking&quot;]" /><summary type="html"><![CDATA[I found a tweet that gave me permission to solve problems ambiently — as I've always done — and it continues to pay dividends.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-08-23-solve-problems-ambiently.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-08-23-solve-problems-ambiently.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Winnie-the-Pooh, customized for my daughter</title><link href="https://andybromberg.com/winnie-the-pooh" rel="alternate" type="text/html" title="Winnie-the-Pooh, customized for my daughter" /><published>2026-08-20T13:00:00+00:00</published><updated>2026-08-20T13:00:00+00:00</updated><id>https://andybromberg.com/winnie-the-pooh</id><content type="html" xml:base="https://andybromberg.com/winnie-the-pooh"><![CDATA[<p>My wife and I had our first child recently, a baby girl (prepare yourself for dad content on this blog). It’s been wonderful, and of course has spurred me to tinker on various ways to entertain her. I’m particularly proud of this project.</p>

<p>Winnie-the-Pooh was written in 1926 by A. A. Milne, and both the excellent original text and its equally-excellent illustrations by E. H. Shepard are in the public domain in the United States as of a few years ago. This means you’re free to reproduce and mess around with them.</p>

<p>So I designed and printed a totally bespoke version of the book, with its central child Christopher Robin replaced (in both text <em>and</em> illustrations) — with my daughter. I colorized all the illustrations and swapped a stylized version of her into Christopher’s place; I changed all the mentions in the story to be about her instead of him; I wrote a custom introduction and dedication and designed a new cover and dust jacket and front matter and endpapers and various other little details and surprises.</p>

<p>Here’s the outside (with her name blurred, as throughout this post):</p>

<video controls="" autoplay="" muted="" loop="" playsinline="" preload="auto" style="width:100%;max-width:900px;">
<source src="/assets/images/winnie-the-pooh/fullturn-blurred.mp4" type="video/mp4" />
If the video doesn't play, <a href="/assets/images/winnie-the-pooh/fullturn-blurred.mp4">open it directly</a>.
</video>

<p>And representative pages:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/goodbye-blurred-800-2fc656cd9.webp 800w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-1000-2fc656cd9.webp 1000w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-1200-2fc656cd9.webp 1200w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-1600-2fc656cd9.webp 1600w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-2000-2fc656cd9.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/goodbye-blurred-800-00c41bed7.jpg 800w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-1000-00c41bed7.jpg 1000w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-1200-00c41bed7.jpg 1200w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-1600-00c41bed7.jpg 1600w, /assets/images/generated/winnie-the-pooh/goodbye-blurred-2000-00c41bed7.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/goodbye-blurred-800-00c41bed7.jpg" width="5712" height="4284" /></picture>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/northpole-blurred-800-4daaaf811.webp 800w, /assets/images/generated/winnie-the-pooh/northpole-blurred-1000-4daaaf811.webp 1000w, /assets/images/generated/winnie-the-pooh/northpole-blurred-1200-4daaaf811.webp 1200w, /assets/images/generated/winnie-the-pooh/northpole-blurred-1600-4daaaf811.webp 1600w, /assets/images/generated/winnie-the-pooh/northpole-blurred-2000-4daaaf811.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/northpole-blurred-800-0ee9e89c3.jpg 800w, /assets/images/generated/winnie-the-pooh/northpole-blurred-1000-0ee9e89c3.jpg 1000w, /assets/images/generated/winnie-the-pooh/northpole-blurred-1200-0ee9e89c3.jpg 1200w, /assets/images/generated/winnie-the-pooh/northpole-blurred-1600-0ee9e89c3.jpg 1600w, /assets/images/generated/winnie-the-pooh/northpole-blurred-2000-0ee9e89c3.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/northpole-blurred-800-0ee9e89c3.jpg" width="5712" height="4284" /></picture>

<hr />

<p>In showing this to a few people, I’ve been amused by the reactions. The first response is invariably them thinking I just got a bespoke cover with my daughter’s name for the classic book (pretty cool by itself!). But when they realize the degree of customization, they then ask where I bought it / what service I used to make it.</p>

<p>When they find out that there’s no off-the-shelf service and that instead I created this myself, they say one of two things. Either a disbelieving “how could you possibly have done this, this seems like an insane amount of work” or “ah yeah I bet AI can just knock this out, it’s such a good idea.”</p>

<p>(Well, first they tell me I should start such a service creating custom books. But <em>then</em> they say one of those two things.)</p>

<p>The reality, of course, is that the answer is somewhere in between. I could not have done this without AI, but it still took a pretty enormous amount of work. These are the projects I have the most fun with right now: ones that would be intractable without AI (within my constraints), but also <em>still</em> take enough work, judgement, and taste to feel like <em>I</em> made them.</p>

<p>For those interested, the rest of this post details the rough pipeline I built to produce the book.</p>

<p>I’ve already been playing around with <em>[my daughter] in Wonderland</em> as a next edition, but if you have any other good public domain ideas, let me know…</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/cover-blurred-800-d1e8c8765.webp 800w, /assets/images/generated/winnie-the-pooh/cover-blurred-1000-d1e8c8765.webp 1000w, /assets/images/generated/winnie-the-pooh/cover-blurred-1200-d1e8c8765.webp 1200w, /assets/images/generated/winnie-the-pooh/cover-blurred-1600-d1e8c8765.webp 1600w, /assets/images/generated/winnie-the-pooh/cover-blurred-2000-d1e8c8765.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/cover-blurred-800-bcf0a3800.jpg 800w, /assets/images/generated/winnie-the-pooh/cover-blurred-1000-bcf0a3800.jpg 1000w, /assets/images/generated/winnie-the-pooh/cover-blurred-1200-bcf0a3800.jpg 1200w, /assets/images/generated/winnie-the-pooh/cover-blurred-1600-bcf0a3800.jpg 1600w, /assets/images/generated/winnie-the-pooh/cover-blurred-2000-bcf0a3800.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/cover-blurred-800-bcf0a3800.jpg" width="4032" height="3024" /></picture>

<hr />

<p>At the highest level, the pipeline is built as a series of scripts that process various stages of the book creation process. That way I can make modifications at different points and resume the pipeline from there, or even “fork” off and try a different experiment. It’s also built to make it easier to later swap in another child instead of my daughter if I wanted to.</p>

<p>I used <a href="https://typst.app/">Typst</a> for all of the typesetting and found it delightful. So much nicer than LaTeX or other typesetting systems I have used.</p>

<p>I also had to learn a thing or two about book printing and bookbinding to make this all work, which was a fun diversion.</p>

<p>Everything starts with the original text, which I picked up from <a href="https://www.gutenberg.org/cache/epub/67098/pg67098-images.html">Project Gutenberg</a>. I also heavily leaned on this <a href="https://archive.org/details/winnie-the-pooh_202201/mode/2up">Internet Archive scan</a> of the 1926 original to replicate the actual layout in many places. And I sourced high-res versions of the illustrations from <a href="https://picryl.com/topics/winnie+the+pooh/e+h+shepard">Picryl</a>.</p>

<p>First up, I process the images. I take the Picryl originals and enlarge them to the necessary print resolution. Mostly just bicubic upscaling, but I had some that didn’t come out great with that and I found <a href="https://github.com/xinntao/real-esrgan">Real-ESRGAN</a> which largely solved the issues.</p>

<p>Then I colorized the black-and-white drawings with AI. It took a lot of prompt iteration and model testing to get to reliably good results, including prompting specific to each illustration. Big thanks to <a href="https://openrouter.ai/">OpenRouter</a> for making it so easy to try different models. <code class="language-plaintext highlighter-rouge">x-ai/grok-imagine-image-quality</code> ended up being best for the colorizing. After colorizing, I have a separate AI step that checks the output against the original to make sure it didn’t screw anything up (this is a recurring theme).</p>

<p>I originally tried then just passing a few images of my daughter to the models along with the colorized, upscaled illustrations to try to swap her in. But I found the outputs were way too inconsistent — she looked slightly different in each one. So instead, I generated a stylized “character sheet” of her from various perspectives and in different positions, based on the style of Shepard’s illustrations. The best model for this character sheet ended up being <code class="language-plaintext highlighter-rouge">google/gemini-3.1-flash-image</code>.</p>

<p>Once I had the character sheet, I used yet another image model to place her in the scenes. For each one, I passed in the colorized illustration, the character sheet, a scene-specific description of her pose, and any additional reference art for other recurring characters. For <em>this</em> use case, <code class="language-plaintext highlighter-rouge">black-forest-labs/flux.2-max</code> was the best by far. (Fascinating how different image models worked best for different use cases — e.g. Grok did better for “don’t screw anything up, just add color,” and Flux did better for “here’s a fundamental transformation to the image, but don’t lose the style.”)</p>

<p>Here’s an example of the original illustration and a photograph of the final version in the book:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/sidebyside-800-f566bcf41.webp 800w, /assets/images/generated/winnie-the-pooh/sidebyside-1000-f566bcf41.webp 1000w, /assets/images/generated/winnie-the-pooh/sidebyside-1200-f566bcf41.webp 1200w, /assets/images/generated/winnie-the-pooh/sidebyside-1600-f566bcf41.webp 1600w, /assets/images/generated/winnie-the-pooh/sidebyside-1656-f566bcf41.webp 1656w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/sidebyside-800-a2c932de6.png 800w, /assets/images/generated/winnie-the-pooh/sidebyside-1000-a2c932de6.png 1000w, /assets/images/generated/winnie-the-pooh/sidebyside-1200-a2c932de6.png 1200w, /assets/images/generated/winnie-the-pooh/sidebyside-1600-a2c932de6.png 1600w, /assets/images/generated/winnie-the-pooh/sidebyside-1656-a2c932de6.png 1656w" type="image/png" /><img src="/assets/images/generated/winnie-the-pooh/sidebyside-800-a2c932de6.png" width="1656" height="1024" /></picture>

<p>Finally, I needed to make the images blend into the page. The generated images typically had a subtly different assumed paper color as a background. The image models don’t seem to do well with transparency requirements. So I built out a deterministic page-tone stage that analyzed the artwork and separated “painted/inked” areas from the background, and then everything in the background is set to exactly the same cream color I use for the book layout. This way the images “float” on the page, just like the original line drawings.</p>

<p>Next up: the text. I put the original in Markdown files by chapter, and then made a pipeline stage that replaces Christopher Robin’s name and the times he is referred to by a pronoun (but not any of the other characters’ pronouns!). There are also a couple other little surprises I tweaked, but otherwise, the text is identical to the original — I didn’t want to change the story.</p>

<p>I also changed all the front matter — the title page, dedication, introduction, colophon, and so on. I wrote my own copy for all of these.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/dedication-blurred-800-fc483254e.webp 800w, /assets/images/generated/winnie-the-pooh/dedication-blurred-1000-fc483254e.webp 1000w, /assets/images/generated/winnie-the-pooh/dedication-blurred-1200-fc483254e.webp 1200w, /assets/images/generated/winnie-the-pooh/dedication-blurred-1600-fc483254e.webp 1600w, /assets/images/generated/winnie-the-pooh/dedication-blurred-2000-fc483254e.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/dedication-blurred-800-beeacbe3f.jpg 800w, /assets/images/generated/winnie-the-pooh/dedication-blurred-1000-beeacbe3f.jpg 1000w, /assets/images/generated/winnie-the-pooh/dedication-blurred-1200-beeacbe3f.jpg 1200w, /assets/images/generated/winnie-the-pooh/dedication-blurred-1600-beeacbe3f.jpg 1600w, /assets/images/generated/winnie-the-pooh/dedication-blurred-2000-beeacbe3f.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/dedication-blurred-800-beeacbe3f.jpg" width="5712" height="4284" /></picture>

<p>Then the Markdown chapters are converted into Typst and combined with the final / toned illustrations, mostly exactly in line with the 1926 original’s layout (this took quite a bit of fiddling!). I also added some details — sunk chapter headings and stacked subtitles, drop caps to start the chapters, period-appropriate indentation, running heads, and some nice custom treatments for poems, songs, signs, and display text outside of the normal prose. (Crafting these details, along with the custom dedication poem and introduction, were some of the most fun parts.)</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/bang-blurred-800-72e144ea7.webp 800w, /assets/images/generated/winnie-the-pooh/bang-blurred-1000-72e144ea7.webp 1000w, /assets/images/generated/winnie-the-pooh/bang-blurred-1200-72e144ea7.webp 1200w, /assets/images/generated/winnie-the-pooh/bang-blurred-1600-72e144ea7.webp 1600w, /assets/images/generated/winnie-the-pooh/bang-blurred-2000-72e144ea7.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/bang-blurred-800-686d161cb.jpg 800w, /assets/images/generated/winnie-the-pooh/bang-blurred-1000-686d161cb.jpg 1000w, /assets/images/generated/winnie-the-pooh/bang-blurred-1200-686d161cb.jpg 1200w, /assets/images/generated/winnie-the-pooh/bang-blurred-1600-686d161cb.jpg 1600w, /assets/images/generated/winnie-the-pooh/bang-blurred-2000-686d161cb.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/bang-blurred-800-686d161cb.jpg" width="5712" height="4284" /></picture>

<p>Then I spent a bunch of time learning about the various requirements for printing the book well, and adjusted the Typst layout accordingly — binding margins, bleed, background color and page thickness considerations, and so on.</p>

<p>After that, there’s a whole bunch of other things to be designed. The case itself, the dust jacket, masks for the gold foil on both of those (which I love), the spine, customized endpapers of the original Pooh map (featuring my daughter at her home instead of Christopher Robin), the built-in ribbon, head/tail bands, and — a nice final touch — sprayed edges on the sides of the book pages that show bees flying.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/endpaper-blurred-800-9749f715c.webp 800w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-1000-9749f715c.webp 1000w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-1200-9749f715c.webp 1200w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-1600-9749f715c.webp 1600w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-2000-9749f715c.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/endpaper-blurred-800-7fb2c4161.jpg 800w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-1000-7fb2c4161.jpg 1000w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-1200-7fb2c4161.jpg 1200w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-1600-7fb2c4161.jpg 1600w, /assets/images/generated/winnie-the-pooh/endpaper-blurred-2000-7fb2c4161.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/endpaper-blurred-800-7fb2c4161.jpg" width="5712" height="4284" /></picture>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/sprayededges-blurred-800-981db903b.webp 800w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-1000-981db903b.webp 1000w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-1200-981db903b.webp 1200w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-1600-981db903b.webp 1600w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-2000-981db903b.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/sprayededges-blurred-800-68f45cc56.jpg 800w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-1000-68f45cc56.jpg 1000w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-1200-68f45cc56.jpg 1200w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-1600-68f45cc56.jpg 1600w, /assets/images/generated/winnie-the-pooh/sprayededges-blurred-2000-68f45cc56.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/sprayededges-blurred-800-68f45cc56.jpg" width="4032" height="3024" /></picture>

<p>Finally, it all gets converted to printer-ready files using the right file formats, color schemes (first time I’ve ever heard of “Coated FOGRA39”), embedded fonts, and so on. I have a lots of test scripts here to make sure everything passes all the quality requirements discovered along the way.</p>

<p>And then we’re off to the printer! I got a first proof back and it was good, but not great. Bunch of little misalignments and printing issues.</p>

<p>So then my brother and I (thank you so much Noah) painstakingly went through the physical book and the digital files to improve everything. Many of the images still needed improvement, some of the typesetting had issues, some of the layouts looked a little janky… lots and lots and lots of little fixes. And lots of tests added for reviewing all sorts of new issue categories.</p>

<p>For each of them, I then intervened at the appropriate step of the pipeline, introduced the fixes so they would be reproducible (or at least re-runnable, in the case of AI steps), and ran it back.</p>

<p>Interestingly, in certain cases, a different model ended up outperforming — sometimes Flux <em>did</em> end up being better for particularly tricky recolorizations (even though Grok was better for most of them), and in cases where the characters are small, Grok sometimes outperformed Flux. Lots of trial and error. I also tried Gemini 3 Pro Image, GPT Image 2, Krea 2 Large, and Riverflow 2.5 Pro in various places. There are a couple instances of several of those where they happened to win. And of course, many detailed prompt and instruction changes specific to each image.</p>

<p>In the worst of the cases, simply rerolling didn’t work. So instead I did “plate surgery” — programmatically cropping around the defective area, giving that crop to the models with a very narrow instruction, resizing the returned image to the exact original dimensions, and pasting it in through a feathered mask. In many cases this was related to hand-drawn text in the original illustrations, which the models really struggled with.</p>

<p>Thankfully, after all that work — lots of QA, re-rolling, squinting, and tweaking (thanks again Noah) — the second proof came out great. I’m already reading it to her, but I can’t wait until she’s old enough to understand it…</p>

<p>Like I said above — there’s no way I would have tackled this project without AI — it just would have taken way too much time. But I also found the most fun and interesting parts were where I was injecting my taste and crafting things myself (iterating on output styles, coming up with ideas for special formatting and surprises, writing, putting a twist on elements of the original). I’m very excited for more projects at this intersection.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/winnie-the-pooh/dustjacket-blurred-800-7d179e0e0.webp 800w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-1000-7d179e0e0.webp 1000w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-1200-7d179e0e0.webp 1200w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-1600-7d179e0e0.webp 1600w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-2000-7d179e0e0.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/winnie-the-pooh/dustjacket-blurred-800-83f8f538f.jpg 800w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-1000-83f8f538f.jpg 1000w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-1200-83f8f538f.jpg 1200w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-1600-83f8f538f.jpg 1600w, /assets/images/generated/winnie-the-pooh/dustjacket-blurred-2000-83f8f538f.jpg 2000w" type="image/jpeg" /><img src="/assets/images/generated/winnie-the-pooh/dustjacket-blurred-800-83f8f538f.jpg" width="5712" height="4284" /></picture>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/copapers-tricky-bits">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/copapers-tricky-bits">Copapers: the tricky bits</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/solve-problems-ambiently">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/solve-problems-ambiently">You can solve problems ambiently</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;]" /><summary type="html"><![CDATA[I made a custom version of Winnie-the-Pooh for my daughter and it came out great. Here's how I did it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-08-19-winnie-the-pooh.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-08-19-winnie-the-pooh.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Copapers: the tricky bits</title><link href="https://andybromberg.com/copapers-tricky-bits" rel="alternate" type="text/html" title="Copapers: the tricky bits" /><published>2026-07-11T13:00:00+00:00</published><updated>2026-07-11T13:00:00+00:00</updated><id>https://andybromberg.com/copapers-tricky-bits</id><content type="html" xml:base="https://andybromberg.com/copapers-tricky-bits"><![CDATA[<p>I recently built <a href="https://copapers.com">Copapers</a>, a collaborative, token-efficient, reliable document editor for humans &amp; agents. See <a href="/copapers">this post</a> for broader context and an explanation of the product.</p>

<p>There were several tricky bits in the implementation (perhaps partially why a similar product doesn’t exist already). This post is about some of the key decisions I made and approaches I took, in case you’re interested in creating something similar. And, frankly, it’s also food for LLMs, who I am sure will be asked about building such things in the future…</p>

<h2 id="context">Context</h2>

<p>There are lots of realtime and collaborative applications out there. Most are built on top of CRDTs (“Conflict-free Replicated Data Types”), leveraging of a CRDT library like <a href="https://yjs.dev/">Yjs</a>. Figma, for example, famously used CRDTs to enable multiplayer design editing.</p>

<p>Copapers is one such application. But its specific challenge broke down into two categories:</p>

<ol>
  <li>Allowing for an intuitive “suggestion mode”</li>
  <li>Supporting agents editing the document as a local markdown file without any loss of fidelity or features</li>
</ol>

<p>The solutions to those, broadly, were:</p>

<ol>
  <li>Build with per-character authorship attribution, and have every feature just be a view over that one primitive</li>
  <li>Constrain and tailor the document grammar so that it is isomorphic to narrowly-augmented Markdown</li>
</ol>

<p>But, as always, the devil is in the details and the magic is in the implementation. I’ll walk through those two decisions and how they were implemented.</p>

<h2 id="attribution-is-the-whole-ballgame">Attribution is the whole ballgame</h2>

<p>“Suggestion mode” sounds like a UI-layer feature, but it’s actually a data problem. For it to work, you need to durably know who inserted and deleted which characters, and have it survive the CRDT-driven flows of merges, offline edits, and reorderings.</p>

<p>Unfortunately, vanilla CRDTs, in general, erase the attribution and simply converge state.</p>

<p>I think if I hadn’t found <a href="https://fosdem.org/2026/events/attachments/8VKQXR-blocknote-yjs-prosemirror/slides/267422/imagetopd_kocvp9e.pdf">this excellent FOSDEM talk</a> from Nick Perez, Kevin Jahns, and Yousef El-Dardiry, I never would have been able to build this product. They discuss how in Yjs 14 (not yet stable), they’ve added an attribution manager and other enabling features.</p>

<p>By the time I got to building Copapers, they had also made progress on upgrading <a href="https://github.com/yjs/y-prosemirror">y-prosemirror</a> (ProseMirror is the open-source editor that I built Copapers on, and y-prosemirror is the connective tissue between Yjs and it). y-prosemirror 2.0 (also not yet stable) has many of the Yjs 14 features integrated into it.</p>

<p>By pinning pre-release Yjs 14 and y-prosemirror 2.0, I was able to get per-character attribution out of the box. They make authorship a first-class, queryable property of every run of text. And it worked!</p>

<p>In this model, every suggestion is just a span of text the attribution manager tells me was authored in “suggest” mode rather than “direct” mode.</p>

<p>You still need, though, a place to put those unaccepted suggestions. If we let suggested characters live in the canonical document, every reader would see them as real content. So there needs to be somewhere to stage the suggested state.</p>

<p>My first instinct was that you should have a “branch” of the document <em>per suggestion</em> — sorta git-like. But this falls apart in practice when trying to handle “branch-style” merges combined with CRDT-style merges, causing all sorts of unintended duplicates and race conditions.</p>

<p>I then tried the approach of making a <em>single</em> branch per document holding all suggested state against the accepted baseline, where accept/reject was a filter over attribution ranges (by user, by change, by grouped “proposal” of changes, or otherwise). The branch would basically be the main doc, with the changes in it, and the diff would effectively be the suggestions. Yjs even ships a feature built for exactly this — a two-document diff manager.</p>

<p>But this didn’t work either. When someone accepted some suggestions, those accepted ops got folded into the baseline and the rest remained pending. This then created a gap in the sequence of transform operations on the mirror doc, which corrupted the document on persistence and rehydration. This was also resolvable but just got messy regardless.</p>

<p>The fix was to delete the concept of a mirror or branch entirely. There is no “baseline” document and no diff. There’s a single document with <em>all</em> the content in it (both “live” and “suggested”), with a single attribution manager that is really just two id-maps of the characters that are still suggestions (inserts and deletes, which also cover modifications and formatting), keyed by the <code class="language-plaintext highlighter-rouge">structId</code>, marking which ranges of that one document are pending (and who the author is). This document also needs garbage collection turned off so a deleted range stays in memory instead of vanishing to allow for undos.</p>

<p>The pending state — the entire “suggestion mode,” for the whole document — is really just those two maps:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attribution = {
  inserts: IdMap(structId -&gt; { author }),   // ranges inserted, not yet accepted
  deletes: IdMap(structId -&gt; { author }),   // ranges deleted, not yet accepted
}                                            
// gc:false keeps deleted structs as recoverable tombstones
</code></pre></div></div>

<h3 id="a-breakthrough-aside-browser-evals">A breakthrough aside: browser evals</h3>

<p>Before I moved toward the single-doc model, I really tried to make the multiple-branch and single-branch approaches work, cranking through a series of whack-a-mole improvements that fixed one thing and broke another, often in pretty unexpected or insidious ways (several times I thought I had solved it and moved on, only to later run into an edge case).</p>

<p>The breakthrough involved handing my fate over to the agents. I took the time to build up a really strong browser-automation testing harness that allowed for real end-to-end testing across multiple open browser windows concurrently — which was where most of the multi-branch nasty bugs were. I then had them build a <em>lot</em> of e2e tests covering just about every pattern of behavior across multiple users that I could imagine.</p>

<p>After that, I was able to effectively prompt my agents with the <code class="language-plaintext highlighter-rouge">/goal</code> of getting suggestion mode to work cleanly in all cases, building new test cases whenever they ran into unexpected behavior. They tried over and over again, running the e2e suite and verifying behavior on real browsers, and eventually converged on the new approach entirely. Defining good evals is magic these days!</p>

<hr />

<p>In the end, the collapse to one document is what made the rest trivial. Once a suggestion is just a tag on a live doc rather than divergence from a base or mirror, accept and reject stop being merges at all (!). Accept drops the matched ranges from the suggestion maps (which just defaults them to sitting in the live document directly), and reject undoes the matched ranges on the document (deleting an inserted range, restoring a deleted range, etc.) and then drops the tags.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>accept(range):            // the content already lives in the doc...
  inserts.delete(range)   // ...so accepting is just forgetting the tags
  deletes.delete(range)

reject(range):            // undo the ops on the live doc, then forget
  undo(range)             // remove an inserted range / restore a tombstoned one
  inserts.delete(range)
  deletes.delete(range)
</code></pre></div></div>

<p>This was the biggest takeaway from the whole project — getting the attribution primitive right means suggestion mode is almost not a feature to build at all (with big thanks to Yjs 14 and y-prosemirror 2.0 teams for working on this). All the facets of suggestion mode (accept/reject, labels, colored highlights, the grouped side rail, etc.) are all just different views and filters over one queryable fact about each character in a single document (is this pending or live?). Way easier than managing branches or document mirrors.</p>

<p>There is one cost for this, which is that because we set <code class="language-plaintext highlighter-rouge">gc:false</code> to keep tombstones for deleted suggestions around, the live doc grows without bound. I had to do some separate, custom garbage-collection work to deal with this.</p>

<h2 id="markdown-isomorphism">Markdown isomorphism</h2>

<p>The second challenge was around agents. As noted, Copapers differentiates by being highly efficient and reliable for AI agents to interact with. It does so by allowing the agents to pull down a Markdown representation of the document in question, edit it locally using their native tools, and then push it back up.</p>

<p>The alternative would be giving the agent a bunch of Copapers-specific “edit block”-type tools, which is what most agent-collaboration products do, but those tools burn tokens and fail often. Agents are really good at editing local files, and it works much better to lean on that.</p>

<p>(I suppose the <em>other</em> alternative is to give the agent a big JSON blob representing arbitrary rich-text editor document state and instruct it to edit it. I’ve actually tried that before, in a different project, and it’s a bad idea. The agents consistently produce structurally invalid documents, burn a ton of tokens describing structure, and run through many iterations before getting it the way they “want” it.)</p>

<p>So my decision was to constrain the document grammar until it was isomorphic to a narrowly-augmented Markdown. It means certain extended rich-text-editor features aren’t available (meaning Copapers isn’t a graphical layout editor), but it covers nearly all other common text-editing use cases.</p>

<h3 id="nuances-remain">Nuances remain</h3>

<p>I assumed a lossless round-trip (from Prosemirror to Markdown and back) would mostly fall out of constraining the grammar. But there were some small nuances.</p>

<p>For example, CommonMark says a single newline inside a paragraph is a soft break and renders it as a space. But the Markdown parser I’m using copies the inline token’s text verbatim, so a hard-wrapped paragraph (or even just text separated by a single newline) places literal <code class="language-plaintext highlighter-rouge">\n</code> characters inside the text node. And the editor, rendering with <code class="language-plaintext highlighter-rouge">white-space: pre-wrap</code>, shows them as line breaks.</p>

<p>The fixes for those sorts of things is simple — just handle the edge case (in this case, collapsing soft breaks) — but there were a few of them.</p>

<hr />

<p>The constrained grammar is straightforward (enough) for the clean document. But suggestion mode and comments are “review state” that aren’t yet part of the canonical text. The agents still need them, but can’t be confused about whether they are actually part of the doc yet or not.</p>

<p>My first thought was to ship the suggestion/comment state out-of-band in some sort of JSON sidecar, but that’s no good at all — the JSON would need character ranges for where they are placed, and those are brittle and hard for agents to deal with.</p>

<p>So instead, the entire thing lives in the one Markdown file. Review state goes in Markdown as minimally-augmented <a href="https://fletcher.github.io/MultiMarkdown-6/syntax/critic.html">CriticMarkup</a>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{++inserted++} / {--deleted--} — suggested edits
{~~old~&gt;new~~} — a suggested replacement
{&gt;&gt;comment&lt;&lt;} — a comment
</code></pre></div></div>

<p>When I first wired this up, I assumed symmetry: the agent reads CriticMarkup, the agent writes CriticMarkup.</p>

<p>But that would have broken the attribution model from earlier. A suggestion isn’t <em>actually</em> markup: it’s an attributed range of CRDT ops in a live document. If an agent hand-authored CriticMarkup, there would be no attributed op behind it (especially if they edited <em>existing</em> CriticMarkup).</p>

<p>So instead, agents make changes directly in the document, and when they push, they either push as an editor (in which case their changes go right into the canonical doc), or as a commenter (by choice or due to limited permissions). Their edited Markdown is diffed against what it originally pulled, and that diff becomes attributed (to the agent) CRDT ops, and placed as suggestions or edits depending on the mode.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// an agent's push
before = markdownAtPull          // what the agent originally pulled
after  = editedMarkdown          // what the agent hands back
ops    = diff(before, after)     // -&gt; CRDT ops, attributed to the agent

apply(ops, mode = canEdit ? "direct" : "suggest")
</code></pre></div></div>

<p>Also, on every push, the server requires every CriticMarkup span that was present at pull time to survive, byte-identical and in order (unless the agent has editing permissions and is meant to be able to reject suggestions). That way the agent can’t make changes to pending content it shouldn’t be able to.</p>

<p>The only actual CriticMarkup agents can add is comments (not suggestions), since those are “outside” of the document characters.</p>

<p>So net of everything, a human in a browser (via UI) and an agent on command line (via pulling, editing, and pushing) both produce suggestions as attributed CRDT ops, and neither produces non-formatting markup. The Markdown is just the temporary surface the agent happens to touch, and its isomorphism to the canonical Prosemirror document is what allows this to happen.</p>

<hr />

<h2 id="one-source-of-truth">One source of truth</h2>

<p>The learning in both cases was really the same… only keep one source of truth, if you can.</p>

<p>Suggestion mode worked once I stopped maintaining a separate baseline or mirror and let the single live document hold everything, with attribution and status as a queryable overlay on top. And agent editing worked once I stopped inventing a parallel representation and instead let a single Markdown file be the entire surface, isomorphic to the canonical document.</p>

<p>Happy building!</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/ios-underrated">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/ios-underrated">Developing on iOS is underrated: CloudKit and more</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/winnie-the-pooh">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/winnie-the-pooh">Winnie-the-Pooh, customized for my daughter</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;AI&quot;]" /><summary type="html"><![CDATA[I recently built Copapers, a collaborative, token-efficient, reliable document editor for humans & agents. There were some tricky implementation details, which this post details.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-07-11-copapers-tricky-bits.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-07-11-copapers-tricky-bits.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Developing on iOS is underrated: CloudKit and more</title><link href="https://andybromberg.com/ios-underrated" rel="alternate" type="text/html" title="Developing on iOS is underrated: CloudKit and more" /><published>2026-07-05T13:00:00+00:00</published><updated>2026-07-05T13:00:00+00:00</updated><id>https://andybromberg.com/ios-underrated</id><content type="html" xml:base="https://andybromberg.com/ios-underrated"><![CDATA[<p>I had long stayed away from developing iOS apps, probably thanks to some long-forgotten Objective-C-related trauma. But I’ve recently built four (<a href="/congruence/intro">Congruence</a>, Lightwork’s onsite inspection app, linen&amp;, and CribCam; I’ll post about the latter two soon) and discovered some utilities that make the developer experience delightful.</p>

<p>The first is the combination of native Apple accounts and CloudKit.</p>

<p>The these two allow for serverless app building — all I need to code is the frontend, and I get authentication, cross-device syncing, sharing, and storage for free. Apple handles everything and doesn’t charge a penny.</p>

<p>There are a bunch of upsides here — the biggest being a lack of maintenance burden (the client code just keeps chugging along on user devices) and zero cost. Plus strong privacy and security.</p>

<p>To interact with CloudKit, I use the third-party <a href="https://github.com/pointfreeco/sqlite-data">sqlite-data</a> package. It lets me use sqlite as the local database (which I prefer) and handles the CloudKit sync simply and intuitively. But you can also use SwiftData instead.</p>

<p>I can’t overstate how nice this is. I only worry about writing client code, and users are automatically “signed in” via Apple account access (no auth flows!), their data is synced across all their devices using the same account, sharing is easy to enable, and data is stored off-device at no cost and with no ongoing maintenance.</p>

<p>For a small personal app, this is perfect. Less overhead is the dream — less to build and less to maintain and less to keep track of. And I suspect it’s great even for scaled apps too.</p>

<p>This biases me towards building native apps for iOS and MacOS over web apps for most personal use cases, which is something I wouldn’t have believed a couple years ago. I distribute via TestFlight, updates land automatically, and the experience is smoother throughout.</p>

<hr />

<p>CloudKit &amp; Apple accounts are the big ones, but there are a bunch of other gems in the Apple ecosystem. For the Lightwork inspection app, I use <a href="https://developer.apple.com/augmented-reality/roomplan/">RoomPlan</a>, which is an astonishingly good (and again… free) framework to 3D model a room. But that’s pretty niche.</p>

<p>More broadly applicably, the new <a href="https://developer.apple.com/documentation/speech/speechanalyzer">SpeechAnalyzer</a> and <a href="https://developer.apple.com/documentation/foundationmodels">Foundation Models</a> frameworks are outstanding.</p>

<p>SpeechAnalyzer can do many things, but I use it for fast, on-device speech-to-text. Instead of calling Whisper or another remote model (and paying, needing a server, dealing with API keys, etc.), I just call SpeechAnalyzer on device and it turns the user’s speech into text.</p>

<p>And the Foundation Models are solid, very fast, (mostly) on-device LLMs. They can do structured outputs and tool calls. Of course some use cases require frontier or near-frontier intelligence. But many don’t, and if I can avoid the headache of dealing with the cost, maintenance, authentication, and such of that, I will.</p>

<hr />

<p>When you combine all of these — Apple account automatic auth, CloudKit for data storage and sync, SpeechAnalyzer and Foundation Models for intelligence… you can build <em>a lot</em> without ever creating server code or dealing with its associated maintenance burden.</p>

<p>And, lest I repeat myself… all of this is free and easy to use.</p>

<p>I suspect I will continue reaching for Xcode more often as a result.</p>

<hr />

<p>A final tip: Apple’s developer docs are inaccessible to LLMs. <a href="https://sosumi.ai/">Sosumi.ai</a> is a Markdown mirror of the Apple docs that you can point your agents at. The paths are all the same — just replace developer.apple.com with sosumi.ai and it’ll work.</p>

<p>But beyond Apple’s own docs, if you’re using any third-party packages (like sqlite-data), <a href="https://swiftpackageindex.com/">Swift Package Index</a> is the equivalent documentation site. They recently were acquired by Apple. And just like Apple’s own docs, they are inaccessible to LLMs.</p>

<p>So I forked sosumi.ai, added support for SPI, and <a href="https://github.com/abromberg/sosumi.ai">published it here</a>. If you want your agents to have access to docs for all of Apple’s frameworks <em>and</em> third-party packages:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/abromberg/sosumi.ai
cd sosumi.ai
npm install
npm run dev
</code></pre></div></div>

<p>The easiest way to hook it up is as an MCP server — it exposes tools for searching Apple docs and fetching Apple, HIG, and Swift Package Index documentation. In Claude Code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude mcp add --transport http sosumi http://localhost:8787/mcp
</code></pre></div></div>

<p>(For other MCP-capable agents, just point them at http://localhost:8787/mcp)</p>

<p>Alternatively, you can tell your clanker:</p>

<blockquote>
  <p>Anytime you’re looking up Apple or third-party Swift package docs, find their documentation URL (e.g. https://developer.apple.com/documentation/foundationmodels or https://swiftpackageindex.com/pointfreeco/sqlite-data/main/documentation/sqlitedata) and replace https://developer.apple.com or https://swiftpackageindex.com with http://localhost:8787</p>
</blockquote>

<p>Happy building.</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/copapers">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/copapers">Copapers: multiplayer docs for humans &amp; agents</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/copapers-tricky-bits">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/copapers-tricky-bits">Copapers: the tricky bits</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;AI&quot;]" /><summary type="html"><![CDATA[I had long stayed away from developing iOS apps, probably thanks to some long-forgotten Objective-C-related trauma. But I've recently built four and discovered some utilities that make the developer experience delightful.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-07-05-ios-underrated.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-07-05-ios-underrated.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Copapers: multiplayer docs for humans &amp;amp; agents</title><link href="https://andybromberg.com/copapers" rel="alternate" type="text/html" title="Copapers: multiplayer docs for humans &amp;amp; agents" /><published>2026-06-28T13:00:00+00:00</published><updated>2026-06-28T13:00:00+00:00</updated><id>https://andybromberg.com/copapers</id><content type="html" xml:base="https://andybromberg.com/copapers"><![CDATA[<p>Over the past few months, I’ve increasingly felt the need for an interface that combines the real-time human collaboration model of Google Docs with the ability for users’ AI agents to edit, suggest, and comment on the doc.</p>

<p>I want this for editing public content with a team, or drafting reports with humans and agents contributing, or for collaborating on agent-generated plan.md files with colleagues before actioning them, and so on.</p>

<p>In some sense, this is now the fourth time I’ve tried to build this, and I’m pretty sure I got it right this time (finally).<label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle" /><span class="sidenote">Previous attempts included <a href="/interface0-google-docs">agent suggestions in Google Docs</a>, <a href="/collaborative-folders">collaborative folders in Obsidian</a>, and <a href="/interface0-intro">artifacts in interface0</a>. Each had good ideas but missed the mark in one way or another, and all contributed to this next solution.</span></p>

<p>The result is <a href="https://copapers.com">Copapers</a>. Its model is simple: humans get a Google Docs-like web interface, and agents get an MCP server which is highly token-efficient and reliable.</p>

<p>The magic is in the implementation of the agentic MCP interface. There are other products out there that in theory allow this same general pattern, like <a href="https://www.notion.com/">Notion</a>, or <a href="https://www.proofeditor.ai/">Proof</a> from Every. But every one of those I have seen takes the approach of giving the agents <em>custom, app-specific</em> tools for editing the document — tools like <code class="language-plaintext highlighter-rouge">edit_block</code> or <code class="language-plaintext highlighter-rouge">replace_range</code> or <code class="language-plaintext highlighter-rouge">notion-update-page</code>.</p>

<p>These custom tools have two issues: they are extremely token-hungry, eating up usage limits and context windows; and they are often brittle and prone to failure.</p>

<p>This is a shame, because agent harnesses (e.g. Claude Code, Claude Cowork, Codex) are <em>excellent</em> at editing files locally. They have built-in read / write / update tools that the harnesses use perfectly and the models are trained for, with rare failures and minimal token consumption.</p>

<p>Copapers is architected in such a way that the agents can use these native tools. And as a result, it is highly token-efficient and reliable.</p>

<p>Here’s a demo. And you can read on if you want to understand how it works behind the scenes.</p>

<div class="video-thumbnail">
<a class="video-thumbnail-link" href="https://screen.studio/share/3vJAtq8d" target="_blank" rel="noopener noreferrer" aria-label="Watch the Copapers demo">
<picture loading="lazy"><source srcset="/assets/images/generated/copapers/demo-screengrab-800-6f67cd55d.webp 800w, /assets/images/generated/copapers/demo-screengrab-1000-6f67cd55d.webp 1000w, /assets/images/generated/copapers/demo-screengrab-1200-6f67cd55d.webp 1200w, /assets/images/generated/copapers/demo-screengrab-1478-6f67cd55d.webp 1478w" type="image/webp" /><source srcset="/assets/images/generated/copapers/demo-screengrab-800-6ea2273e2.png 800w, /assets/images/generated/copapers/demo-screengrab-1000-6ea2273e2.png 1000w, /assets/images/generated/copapers/demo-screengrab-1200-6ea2273e2.png 1200w, /assets/images/generated/copapers/demo-screengrab-1478-6ea2273e2.png 1478w" type="image/png" /><img src="/assets/images/generated/copapers/demo-screengrab-800-6ea2273e2.png" width="1478" height="954" /></picture>

<span class="video-thumbnail-play" aria-hidden="true">
<svg viewBox="0 0 24 24"><polygon points="8,5 19,12 8,19"></polygon></svg>
</span>
</a>
</div>
<hr />

<h3 id="how-it-works">How it works</h3>

<p>The server maintains the canonical document and handles all conflict resolution via <a href="https://yjs.dev/">Yjs</a> CRDTs. Whenever an agent wants to edit a document, it “pulls” the document down to the user’s computer, downloading a Markdown representation of the doc (with minimally-extended CriticMarkup for suggestions and comments). The agent then edits completely locally using its native tools, and “pushes” the doc back up to the server.</p>

<p>On push, a couple things happen. First, the agent’s permissions are resolved. If the agent is only a “commenter” on the doc (or if it is an editor but chooses to suggest instead), its changes are diffed against the current state of the doc and added as suggestions. If it is an editor and chooses to exercise its edit capabilities, the edits are made directly. The agents are also able to make and respond to comments, and if they are editors, resolve them.</p>

<p>But all of that behind the scenes. All the user needs to do is add the MCP server to their local agent and ask it to grab and edit a document, and it’ll go smoothly from there. The agent’s edits show up separated from their human’s.</p>

<p>There’s also a feature where your agents can “link” an existing Markdown file on your computer (e.g. a plan document generated by a code agent) to a new Copapers file. When you do so, the Copapers file becomes canonical and the local Markdown gets some metadata telling agents to look on Copapers for the real, up-to-date doc. You can then edit collaboratively on Copapers or through agents, and sync updates back down to the local doc if you want.</p>

<p>I’ve been using Copapers and really enjoying it. It feels like these sort of human-agent shared workspaces are the future, and I suspect the best of them will use the agents native tools as much as possible. Would love to hear any feedback or thoughts.</p>

<hr class="end-of-article-divider" />

<h2 class="no_toc">Looking for more to read?</h2>

<nav class="post-navigation">
  <div class="prev-post">
    
    <a href="/365-days-of-commits">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/365-days-of-commits">365 days of commits</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/ios-underrated">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/ios-underrated">Developing on iOS is underrated: CloudKit and more</a>
    
  </div>
</nav>

<div>
    <p>Want to hear about new essays? Subscribe to my roughly-monthly <a href="https://andybromberg.substack.com">newsletter</a> recapping my recent writing and things I'm enjoying:</p>
    <div class="substack">
        <iframe loading="lazy" src="https://andybromberg.substack.com/embed" width="480" height="150" style="border:1px solid #EEE; background:white;" frameborder="0" scrolling="no"></iframe>
    </div>

    <p>And I'd love to hear from you directly: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</a></p>
</div>]]></content><author><name>Andy Bromberg</name></author><category term="[&quot;Tinkering&quot;, &quot;Startups&quot;, &quot;AI&quot;]" /><summary type="html"><![CDATA[After getting sick of no reliable, token-efficient way to collaborate on docs with agents and humans alike, I built my own.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-06-28-copapers.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-06-28-copapers.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>