<?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-05-01T09:48:17-07: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">Theory of Constraints: the (Claude) Skill</title><link href="https://andybromberg.com/theory-of-constraints-skill" rel="alternate" type="text/html" title="Theory of Constraints: the (Claude) Skill" /><published>2026-04-07T06:00:00-07:00</published><updated>2026-04-07T06:00:00-07:00</updated><id>https://andybromberg.com/theory-of-constraints-skill</id><content type="html" xml:base="https://andybromberg.com/theory-of-constraints-skill"><![CDATA[<p>If you’re new to the blog, you should know that I like the Theory of Constraints a <em>lot</em> (see <a href="/uwol/theory-of-constraints">a primer</a> worth reading before going further).</p>

<p>This blog post is about a Claude skill I made encoding ToC’s “Thinking Processes,” which I contend can probably help you find solutions for most every problem you are facing. If you want to try the skill, go into your command line and run (assuming you already use Claude Code):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude plugin marketplace add abromberg/toc-coaching-skill
claude plugin <span class="nb">install </span>toc-coaching@toc-marketplace
</code></pre></div></div>

<p>Then pop open <code class="language-plaintext highlighter-rouge">claude</code> and type <code class="language-plaintext highlighter-rouge">/toc-coaching [whatever your problem is]</code>.</p>

<p>It will begin to ask you questions, generate diagrams, and pull the solution out of you.</p>

<p>The repo with code <a href="https://github.com/abromberg/toc-coaching-skill">is here</a>. Here’s a shot of an in-progress “Evaporating Cloud” and the next (structured!) question for the user, making it easy to keep moving through the exercise:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/theory-of-constraints-skill/full-screenshot-800-e55a653dc.webp 800w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-1000-e55a653dc.webp 1000w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-1200-e55a653dc.webp 1200w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-1600-e55a653dc.webp 1600w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-2000-e55a653dc.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/theory-of-constraints-skill/full-screenshot-800-9451b4660.png 800w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-1000-9451b4660.png 1000w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-1200-9451b4660.png 1200w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-1600-9451b4660.png 1600w, /assets/images/generated/theory-of-constraints-skill/full-screenshot-2000-9451b4660.png 2000w" type="image/png" /><img src="/assets/images/generated/theory-of-constraints-skill/full-screenshot-800-9451b4660.png" width="3182" height="2058" /></picture>

<h2 id="the-thinking-processes">The Thinking Processes</h2>

<p>ToC, thankfully, is more than just a good theory of <em>how</em> systems work. Its creator, Eli Goldratt, also created what he refers to as the <a href="https://en.wikipedia.org/wiki/Thinking_processes_(theory_of_constraints)">“Thinking Processes”</a>. I think these can be used to solve just about any problem.</p>

<p>The Thinking Processes — the Current Reality Tree (CRT), the Evaporating Cloud (EC), and the like — are highly structured lines of inquiry which help perform the necessary ToC steps to solve your problem.</p>

<p><strong>Most every time I have rigorously followed these Thinking Processes when faced with a problem, they have led me straight to the solution.</strong> It’s hard to overstate this. We tend to think our current problems are extremely hard to solve — perhaps impossible, or that they require some epiphany from on high. But in practice, when faced with a problem, I’ve found that ToC’s Thinking Processes can reliably give me the solution (!).</p>

<p>(Of course, actually executing the solution is sometimes quite difficult! The Thinking Processes help you figure out what to do, but often you then simply need to apply <a href="/high-effort">High Effort</a>.)</p>

<p>These problems can be in business, in personal life, whatever. Most everything can be modeled as a system, and most any situation you seek to change can be modeled as a problem.</p>

<p>So we’ve got this magical formula to solve problems. What’s the issue?</p>

<p>Well, in my experience, it’s that the Thinking Processes are a bit of a pain to follow. They are quite structured and require working through some mundane background on the situation at hand, and as a result, I tend to half-ass the work and don’t get to the promised land.</p>

<p>It’s like any piece of advice — “eat clean and move your body” is obviously effective, and yet many people don’t do it. And just so, “painstakingly work through the Thinking Processes” is obviously (to me) effective, and yet I often don’t adhere.</p>

<p>The easiest way to do them is to have someone who understands the theory well interview you, rather than sitting down and doing it solo. But these people are hard to find and busy. And we have LLMs now! They can interview us according to a programmatic flow!</p>

<h2 id="the-skill">The Skill</h2>

<p>So, anyway, all that led to my Thinking Processes Claude skill. I want to be clear that none of the ideas are mine — they are Eli Goldratt and the broader ToC community’s. I’ve just encoded them into a flow that you can install and easily perform.</p>

<p>Here’s all you have to do:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude plugin marketplace add abromberg/toc-coaching-skill
claude plugin <span class="nb">install </span>toc-coaching@toc-marketplace
</code></pre></div></div>

<p>Then pop open <code class="language-plaintext highlighter-rouge">claude</code> and type <code class="language-plaintext highlighter-rouge">/toc-coaching [whatever your problem is]</code>.</p>

<p>The agent will begin to interview you, working through the phases of the Thinking Processes. Initially there will be a discovery phase where it is just picking up context and background. Then it will begin to move into the formal Processes, including diagrams and structured breakdowns. Eventually it will propose solutions to the problems.</p>

<p>I’ve been running this for the past week and it has been <em>incredible</em>. I urge you to try it out, even just once, on a problem you’re facing — and stick with it all the way through.</p>

<h2 id="bonus-data-sources">Bonus: data sources</h2>

<p>This all gets <em>way</em> better if you use something like Obsidian for your personal notes. I now frequently start up ToC sessions and tag in files, call notes, and longform writing from my Obsidian to feed context to the agent, and the results get so much better.</p>

<p>I also strongly recommend using a voice-to-text tool like Handy, Wispr Flow or their ilk. Then you can just ramble into the microphone about the situation, and the ToC agent will work with even more info.</p>

<p>As sessions wrap up, it also writes a <code class="language-plaintext highlighter-rouge">/docs/toc-sessions/</code> file with a summary. This can be very helpful to tag in when you start a new session related to a prior one as well.</p>

<p>The more data it has, the faster it will get to the right Thinking Process and conclusion. Feed it as much as you can!</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="/picking-air-purifiers">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/picking-air-purifiers">How to pick the best air purifier, objectively</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;, &quot;Startups&quot;]" /><summary type="html"><![CDATA[Theory of Constraints as a Claude skill that can probably help you solve your problems.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-04-07-theory-of-constraints-skill.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-04-07-theory-of-constraints-skill.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to pick the best air purifier, objectively</title><link href="https://andybromberg.com/picking-air-purifiers" rel="alternate" type="text/html" title="How to pick the best air purifier, objectively" /><published>2026-04-04T06:00:00-07:00</published><updated>2026-04-04T06:00:00-07:00</updated><id>https://andybromberg.com/picking-air-purifiers</id><content type="html" xml:base="https://andybromberg.com/picking-air-purifiers"><![CDATA[<p><strong>Short answer:</strong> use <a href="https://purifiers.lightworkhome.com/">Lightwork’s Air Purifier Guide</a> — 100% free; no signup required; no affiliate links, partnerships, or bias; completely data-driven, quantitative, and transparent.</p>

<p>Or, if you want to understand how we at <a href="https://lightworkhome.com">Lightwork Home Health</a> evaluate and select air purifiers (and how we arrived at the algorithm for our tool), read on…</p>

<h2 id="the-basics-of-air-purification">The basics of air purification</h2>

<p>There is so much marketing noise in the air purifier space. Companies use and come up with all sorts of buzzwords to suggest that their purifier does something <em>special</em>. It’s shockingly difficult to get straight answers and objectively evaluate performance.</p>

<p>But there are a couple key concepts to understand upfront, before getting into the nitty-gritty of comparisons.</p>

<p>First: what are you filtering? Broadly, there are two categories you might want to cover. The first is particulate matter (or “PM”) — everything from dust to pollen to smoke, mold, pet dander, and more. The second is “VOCs” — volatile organic compounds and their cousins semi-volatile organic compounds (SVOCs) and other gases and chemicals.</p>

<p>We’ll focus most of this post on the former (PM). This is the more common air quality issue that nearly every home would be well-served to address. VOCs can certainly be an issue as well, but it is more nuanced and they are not <em>quite</em> as prevalent. We’ll talk about VOCs at the <a href="#the-voc-problem">end of this post</a>.</p>

<p>The mechanisms for filtering PM and VOCs are quite different from each other. Some air purifiers can help with both, but in general, we would strongly recommend filtering them separately.</p>

<p>Once you’ve decided you’re focusing on particulate matter, the question becomes: what makes a good PM air purifier?</p>

<p>Particulate matter ranges from really tiny particulates (perhaps 0.1-0.5 microns in size, like airborne viruses, small bacteria, and ultrafine pollutants like vehicle emissions) to slightly larger ones (around 1 micron is typically larger bacteria and fine dust), or even larger (3-10 microns includes mold and plant spores, pollen, and most dust particles). A good air purifier needs to cover all of these.</p>

<p>So here’s a fun trivia question: in general, do air purifiers struggle the most with filtering:</p>
<ol>
  <li>Larger particles</li>
  <li>Or small particles?</li>
</ol>

<p>The surprising answer? <em>Neither</em>.</p>

<p>Air purifiers have their worst performance on <em>mid-size</em> particles, typically around 0.3 microns. They are extremely effective at filtering tiny particles and extremely effective at filtering large particles.</p>

<p>How can this be? Well: modern air purifiers (and certainly “HEPA” ones) actually use a combination of a few filtration techniques. One of these techniques (diffusion) is extremely good at small particles and gets worse the larger the particle is. Others (inertial impaction, interception) get more effective the larger the particle size is. (And there is also settling and electrostatic attraction in the mix too.)</p>

<p>And so, combined, the efficacy curve looks like a “U,” as in the diagram below (<a href="https://pubmed.ncbi.nlm.nih.gov/32662746/">Christopherson et al. (2020), <em>Otolaryngol Head Neck Surg.</em></a>). The purifier has the lowest filtration rate around 0.15-0.3 microns. This is referred to in the industry as the “Most Penetrating Particle Size,” or MPPS.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/picking-air-purifiers/hepa-components-800-14c3bf9cc.webp 800w, /assets/images/generated/picking-air-purifiers/hepa-components-1000-14c3bf9cc.webp 1000w, /assets/images/generated/picking-air-purifiers/hepa-components-1200-14c3bf9cc.webp 1200w, /assets/images/generated/picking-air-purifiers/hepa-components-1600-14c3bf9cc.webp 1600w, /assets/images/generated/picking-air-purifiers/hepa-components-1888-14c3bf9cc.webp 1888w" type="image/webp" /><source srcset="/assets/images/generated/picking-air-purifiers/hepa-components-800-c74c212c4.png 800w, /assets/images/generated/picking-air-purifiers/hepa-components-1000-c74c212c4.png 1000w, /assets/images/generated/picking-air-purifiers/hepa-components-1200-c74c212c4.png 1200w, /assets/images/generated/picking-air-purifiers/hepa-components-1600-c74c212c4.png 1600w, /assets/images/generated/picking-air-purifiers/hepa-components-1888-c74c212c4.png 1888w" type="image/png" /><img src="/assets/images/generated/picking-air-purifiers/hepa-components-800-c74c212c4.png" width="1888" height="1062" /></picture>

<p>But how bad is <em>low efficacy</em> in this situation? It turns out that any HEPA filter (which is the filter standard all reputable air purifiers use) in the US must filter at least 99.97% of particles at 0.3 microns.</p>

<p>Said another way: when air goes through the unit, it must filter out a <em>minimum of 99.97% of the particles around those it is worst at handling, and more than 99.97% of every other particle size</em>. That is pretty dang good! It means that when air passes through a HEPA air purifier, very few particles come out the other end.</p>

<p>So if all HEPA air purifiers are that good at filtering the air that goes through, what really matters?</p>

<p><span class="highlighter"><strong>How much air goes through.</strong></span></p>

<p>If your HEPA air purifier is underpowered, it will take a long time to clean a room — or, worse, if new pollution is being introduced (and it usually is) — it will never get the air clean. So, aside from using a HEPA filter in the first place, the most important factor is the throughput.</p>

<p>This is typically measured as Clean Air Delivery Rate, or CADR. CADR is measured in cubic feet per minute, or CFM. It is often broken down by pollutant type, but for the sake of keeping this post somewhat simple, we can just talk about it generally.</p>

<p>So, net of all that, the most important factors for filtering PM are that the filter is a HEPA filter, and that it has sufficient throughput for the room and situation (the more the better!). If you take nothing else away from this post… remember that.</p>

<p>Here’s a little toy visualization of air purifier efficacy (assuming a well-mixed room — another rabbithole we could get into. Good air circulation helps purifiers work well!). I’ve included three levels of throughput, and you can shift the room size and incoming pollution. You’ll see that small purifiers often will just never get a room clean!</p>

<div class="ap-viz">
  <div class="ap-controls">
    <div class="ap-control">
      <span class="ap-label">Room size</span>
      <input type="range" id="ap-room" min="100" max="800" value="250" step="25" />
      <span class="ap-val" id="ap-room-val">250</span>
      <span class="ap-unit">sq ft</span>
    </div>
    <div class="ap-control">
      <span class="ap-label">Incoming pollution</span>
      <select id="ap-source">
        <option value="0">None (sealed room)</option>
        <option value="7.5">Light (typical)</option>
        <option value="25" selected="">Moderate (cooking, open windows)</option>
        <option value="50">Heavy (wildfire, construction)</option>
      </select>
    </div>
  </div>

  <div class="ap-chart-wrap">
    <canvas id="ap-chart" width="600" height="260"></canvas>
  </div>

  <div class="ap-stats" id="ap-stats"></div>
</div>

<script>
(function() {
  var canvas = document.getElementById('ap-chart');
  var roomEl = document.getElementById('ap-room');
  var sourceEl = document.getElementById('ap-source');
  if (!canvas || !roomEl || !sourceEl) return;

  var ctx = canvas.getContext('2d');
  var CEIL = 8;
  var DUR = 60;
  var STEPS = 300;
  var ANIM_MS = 1500;
  var PURIFIERS = [
    { label: '50 CFM',  cadr: 50,  color: '#e85d3a', name: 'Low' },
    { label: '150 CFM', cadr: 150, color: '#3b82f6', name: 'Medium' },
    { label: '300 CFM', cadr: 300, color: '#10b981', name: 'High' }
  ];
  var PAD = { top: 16, right: 16, bottom: 36, left: 44 };
  var animId = null;
  var dim = { w: 600, h: 260 };

  function resize() {
    var wrap = canvas.parentElement;
    var w = wrap.clientWidth;
    if (w < 1) return;
    var h = Math.min(260, Math.max(180, w * 0.45));
    var dpr = window.devicePixelRatio || 1;
    canvas.width = w * dpr;
    canvas.height = h * dpr;
    canvas.style.width = w + 'px';
    canvas.style.height = h + 'px';
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    dim = { w: w, h: h };
  }

  function getParams() {
    var sqft = parseInt(roomEl.value);
    return { sqft: sqft, source: parseFloat(sourceEl.value), volume: sqft * CEIL };
  }

  function cleanAt(t, cadr, vol, src) {
    var css = src > 0 ? Math.min(src / cadr, 1) : 0;
    return 1 - css - (1 - css) * Math.exp(-cadr / vol * t);
  }

  function timeToClean(cadr, vol, src) {
    var css = src > 0 ? src / cadr : 0;
    if (css >= 0.9) return null;
    var ratio = (0.1 - css) / (1 - css);
    return ratio > 0 ? -Math.log(ratio) / (cadr / vol) : null;
  }

  function drawChart(progress) {
    var w = dim.w, h = dim.h;
    var px = PAD.left, py = PAD.top;
    var pw = w - PAD.left - PAD.right;
    var ph = h - PAD.top - PAD.bottom;
    var params = getParams();

    ctx.clearRect(0, 0, w, h);
    ctx.fillStyle = '#fff';
    ctx.fillRect(px, py, pw, ph);

    ctx.strokeStyle = '#eaeaea';
    ctx.lineWidth = 1;
    ctx.setLineDash([]);

    [0, 0.25, 0.5, 0.75, 1].forEach(function(v) {
      var y = py + ph * (1 - v);
      ctx.beginPath(); ctx.moveTo(px, y); ctx.lineTo(px + pw, y); ctx.stroke();
    });

    for (var m = 0; m <= 6; m++) {
      var x = px + pw * m / 6;
      ctx.beginPath(); ctx.moveTo(x, py); ctx.lineTo(x, py + ph); ctx.stroke();
    }

    var y90 = py + ph * 0.1;
    ctx.strokeStyle = '#bbb';
    ctx.lineWidth = 1;
    ctx.setLineDash([5, 3]);
    ctx.beginPath(); ctx.moveTo(px, y90); ctx.lineTo(px + pw, y90); ctx.stroke();
    ctx.setLineDash([]);
    ctx.fillStyle = '#bbb';
    ctx.font = '9px "Hanken Grotesk", sans-serif';
    ctx.textAlign = 'left';
    ctx.fillText('90% target', px + 3, y90 - 4);

    var n = Math.floor(STEPS * progress);
    PURIFIERS.forEach(function(p) {
      ctx.strokeStyle = p.color;
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      for (var i = 0; i <= n; i++) {
        var t = (i / STEPS) * DUR;
        var v = cleanAt(t, p.cadr, params.volume, params.source);
        var x = px + (t / DUR) * pw;
        var y = py + ph * (1 - v);
        if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
      }
      ctx.stroke();
    });

    ctx.fillStyle = '#888';
    ctx.font = '11px "Hanken Grotesk", sans-serif';
    ctx.textAlign = 'center';
    for (var m = 0; m <= 6; m++) {
      ctx.fillText((m * 10) + '', px + pw * m / 6, py + ph + 16);
    }
    ctx.fillText('Minutes', px + pw / 2, py + ph + 30);

    ctx.textAlign = 'right';
    [0, 25, 50, 75, 100].forEach(function(v) {
      ctx.fillText(v + '%', px - 5, py + ph * (1 - v / 100) + 4);
    });
  }

  function updateStats() {
    var params = getParams();
    var statsEl = document.getElementById('ap-stats');
    var html = '';
    var results = [];

    PURIFIERS.forEach(function(p) {
      var t = timeToClean(p.cadr, params.volume, params.source);
      results.push({ p: p, t90: t });

      html += '<div class="ap-stat">';
      html += '<div class="ap-stat-color" style="background:' + p.color + '"></div>';
      html += '<div class="ap-stat-name">' + p.name + '</div>';
      html += '<div class="ap-stat-cadr">' + p.label + '</div>';
      html += '<div class="ap-stat-detail">';
      html += t !== null ? Math.round(t) + ' min to 90% clean' : 'Cannot reach 90%';
      html += '</div></div>';
    });
    statsEl.innerHTML = html;
  }

  function animate() {
    if (animId) cancelAnimationFrame(animId);
    var start = null;
    function frame(ts) {
      if (!start) start = ts;
      var p = Math.min(1, (ts - start) / ANIM_MS);
      drawChart(p);
      if (p < 1) animId = requestAnimationFrame(frame);
    }
    animId = requestAnimationFrame(frame);
  }

  function update() {
    document.getElementById('ap-room-val').textContent = roomEl.value;
    updateStats();
    animate();
  }

  roomEl.addEventListener('input', function() { resize(); update(); });
  sourceEl.addEventListener('change', update);
  window.addEventListener('resize', function() {
    if (animId) cancelAnimationFrame(animId);
    resize();
    drawChart(1);
  });

  requestAnimationFrame(function() {
    resize();
    update();
  });
})();
</script>

<style>
.ap-viz {
  background: #f8f9fa;
  border: 1px solid #e0e3e8;
  border-radius: 8px;
  padding: 20px;
  margin: 1.5rem 0;
  font-family: "Hanken Grotesk", -apple-system, sans-serif;
}

.ap-title {
  font-weight: 700;
  color: #1a1a1a;
  margin-bottom: 2px;
}

.ap-subtitle {
  font-size: 0.8em;
  color: #888;
  margin-bottom: 14px;
}

.ap-controls {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.ap-control {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ap-label {
  font-size: 0.85em;
  color: #555;
}

.ap-control select {
  padding: 4px 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 0.85em;
  background: #fff;
}

.ap-control input[type="range"] {
  width: 120px;
  height: 4px;
  border-radius: 2px;
  background: #ddd;
  -webkit-appearance: none;
}

.ap-control input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #444;
  cursor: pointer;
}

.ap-val {
  font-weight: 700;
  min-width: 28px;
}

.ap-unit {
  font-size: 0.75em;
  color: #888;
}

.ap-chart-wrap {
  background: #fff;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  overflow: hidden;
  margin-bottom: 12px;
}

.ap-chart-wrap canvas {
  width: 100%;
  display: block;
}

.ap-stats {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.ap-stat {
  flex: 1;
  min-width: 130px;
  background: #fff;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  padding: 10px 12px;
}

.ap-stat-color {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  display: inline-block;
  margin-bottom: 4px;
}

.ap-stat-name {
  font-size: 0.8em;
  color: #666;
}

.ap-stat-cadr {
  font-weight: 700;
  font-size: 1em;
  color: #1a1a1a;
}

.ap-stat-detail {
  font-size: 0.8em;
  color: #555;
  margin-top: 2px;
}

@media (max-width: 600px) {
  .ap-controls { flex-direction: column; gap: 10px; }
  .ap-stats { flex-direction: column; }
  .ap-stat { min-width: auto; }
}
</style>

<h2 id="the-real-world-twist">The real-world twist</h2>

<p>At <a href="https://lightworkhome.com">Lightwork</a>, we’ve consulted on air filtration with a lot of clients. And in so doing, we’ve realized that — while what I wrote above is all accurate — there are some other real-world, practical considerations. Namely:</p>

<ol>
  <li><strong>Quiet matters.</strong> If an air purifier is too loud, people simply won’t use it. And an unused air purifier is no good at all.</li>
  <li><strong>Ease matters.</strong> If you have to replace the filters too often, most people will simply not do it. And dirty filters tank the efficacy of a unit.</li>
  <li><strong>Beauty matters.</strong> For many customers, having an ugly air purifier is a non-starter. So finding aesthetically-appealing ones — at least for certain rooms — is a factor.</li>
  <li><strong>Price matters.</strong> On top of all that: people want value. If it’s too expensive, it won’t get purchased in the first place.</li>
</ol>

<p>Numbers 2 and 3 are easy: we can restrict our search to units with a 12-month or longer filter lifespan, and to aesthetically-appealing ones (that is the only subjective filter we are going to apply!).</p>

<p>(And don’t worry — we also let you disregard any of these limitations if you don’t care.)</p>

<p>Number 4 is also easy: once we get down to a list of candidate purifiers that work, we can sort them by price.</p>

<p>But number 1 — quietness — is trickier. How do we figure out if a purifier has sufficient throughput <em>at a quiet level</em>?</p>

<p>Unfortunately, CADR is typically not reported at the different fan speeds. Manufacturers only report full-speed CADR, and that is usually <em>very</em> loud.</p>

<p>So we came up with a metric that we refer to as “Quiet CADR.” We’ve defined this as “Clean Air Delivery Rate at 41 decibels of volume or less.” We’ve found by talking to clients and evaluating their response to various units that 41 decibels<label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle" /><span class="sidenote">Note that we will use “decibels” and “dBA” interchangeably throughout this post. dBA specifically refers to sound intensity adjusted to match human ear sensitivity (as opposed to e.g. dBC, dBZ, or plain dB). But since we’re focused on humans here we’ll use the generic “decibels” to refer to dBA.</span> seems about right. It’s about the noise level of a library.</p>

<p>So here’s what we’ve done. It gets a little math-y, so feel free to <a href="#our-algorithm">skip ahead</a>.</p>

<p>We first need to find the noise levels of the various fan speeds for a unit. There are two ways to do this:</p>
<ol>
  <li>Manufacturers typically do report the noise level at the <em>lowest</em> fan speed. So we can use this. This may underestimate the unit’s Quiet CADR (that is, if fan speed 2 is still under 41 decibels, we will think the Quiet CADR is lower than it actually is by using fan speed 1. But we’d rather underestimate Quiet CADR than overestimate it!)</li>
  <li>Independent reviewers like <a href="http://airpurifierfirst.com/">AirPurifierFirst</a> and <a href="https://housefresh.com/">HouseFresh</a> have tested the noise levels of various fan speeds.</li>
</ol>

<p>Once we have that data, we find the highest fan speed that is below 41 decibels.</p>

<p>Now, we need <em>power</em> data — how much energy does the unit draw? We can get this from manufacturers, independent reviewers, energy use certification programs, or elsewhere. And specifically, we need the power usage in wattage at the “quiet” fan speed we found, as well as the top fan speed. Lots of data to collect!</p>

<p>Once we have this data, we can use what are called <a href="https://en.wikipedia.org/wiki/Affinity_laws">Fan Affinity Laws</a> to make further calculations. The fan affinity laws define various physical relationships between performance and power for pumps and fans in general.</p>

<p>The summary here is that CADR (which we can reasonably treat for these purposes as proportional to airflow) scales with the cube root of power. So if we know:</p>
<ul>
  <li>max CADR throughput (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>C</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi></mrow></msub></mrow><annotation encoding="application/x-tex">C_{max}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">ma</span><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>)</li>
  <li>power at the “quiet” level (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>q</mi></msub></mrow><annotation encoding="application/x-tex">P_q</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span>)</li>
  <li>power at the max level (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi></mrow></msub></mrow><annotation encoding="application/x-tex">P_{max}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">ma</span><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>)</li>
</ul>

<p>We can estimate the CADR at the quiet level (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>C</mi><mi>q</mi></msub></mrow><annotation encoding="application/x-tex">C_q</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span>) with:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>C</mi><mi>q</mi></msub><mo>=</mo><msub><mi>C</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi></mrow></msub><mo>&#xd7;</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mi>q</mi></msub><mi mathvariant="normal">/</mi><msub><mi>P</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi></mrow></msub><msup><mo stretchy="false">)</mo><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mn>3</mn></mrow></msup></mrow><annotation encoding="application/x-tex">C_q = C_{max} \times (P_q / P_{max})^{1/3}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">ma</span><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">&#xd7;</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.2241em;vertical-align:-0.2861em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mord">/</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">ma</span><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1/3</span></span></span></span></span></span></span></span></span></span></span></span></span>

<p>(More specifically, we’ve modeled this out based on known measured data from the purifiers for which it is available and found that the real-world scale is closer to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.344</mn></mrow><annotation encoding="application/x-tex">0.344</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0.344</span></span></span></span> than raising the power ratio to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mn>3</mn></mrow><annotation encoding="application/x-tex">1/3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1/3</span></span></span></span>. Pretty validating that it’s so close! We use the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.344</mn></mrow><annotation encoding="application/x-tex">0.344</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0.344</span></span></span></span> exponent in our recommendations given the empirical validation, but it doesn’t really make a difference.)</p>

<p>So, take a purifier that is rated at a CADR of 200 CFM. Say it draws 50W at max speed and 15W at its quiet speed. The Quiet CADR would be:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>C</mi><mi>q</mi></msub><mo>=</mo><mn>200</mn><mo>&#xd7;</mo><mo stretchy="false">(</mo><mn>15</mn><mi mathvariant="normal">/</mi><mn>50</mn><msup><mo stretchy="false">)</mo><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mn>3</mn></mrow></msup><mo>&#x2248;</mo><mn>136</mn><mtext>&#xa0;CFM</mtext></mrow><annotation encoding="application/x-tex">C_q = 200 \times (15/50)^{1/3} \approx 136 \space\text{CFM}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">200</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">&#xd7;</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.188em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">15/50</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1/3</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x2248;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">136</span><span class="mspace">&#xa0;</span><span class="mord text"><span class="mord">CFM</span></span></span></span></span></span>

<p>This metric is how we evaluate throughput — because, again, most people won’t leave an air purifier on high. It drives them nuts. So what we care about is sufficient throughput at a sufficiently quiet setting.</p>

<p>Now: how do we define <em>sufficient</em> throughput? Is 136 CFM good?</p>

<p>This has to be defined by the size of the room. Big rooms need more filtration than small ones.</p>

<p>The main way you look at this is “Air Changes per Hour” or ACH. Our general recommendation is that you want to target 5 ACH (AHAM, the Association of Home Appliance Manufacturers, recommends 4.8). You could go lower, and for certain situations (while you’re cooking, or if you’re dealing with wildfires or bad pollution), you may want to go higher.</p>

<p>So, say you have a 200 square foot room with 8 foot ceilings. That’s 1,600 cubic feet of volume. To get to 5 ACH, you’d need to have throughput of:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>1</mn><mo separator="true">,</mo><mn>600</mn><mo>&#xd7;</mo><mn>5</mn><mo>=</mo><mn>8</mn><mo separator="true">,</mo><mn>000</mn><mtext>&#xa0;cubic&#xa0;feet&#xa0;per&#xa0;hour</mtext><mo>=</mo><mn>133</mn><mtext>&#xa0;cubic&#xa0;feet&#xa0;per&#xa0;minute&#xa0;(CFM)</mtext></mrow><annotation encoding="application/x-tex">1,600 \times 5 = 8,000 \space \text{cubic feet per hour} = 133 \space \text{cubic feet per minute (CFM)}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8389em;vertical-align:-0.1944em;"></span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">600</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">&#xd7;</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">5</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">8</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">000</span><span class="mspace">&#xa0;</span><span class="mord text"><span class="mord">cubic&#xa0;feet&#xa0;per&#xa0;hour</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">133</span><span class="mspace">&#xa0;</span><span class="mord text"><span class="mord">cubic&#xa0;feet&#xa0;per&#xa0;minute&#xa0;(CFM)</span></span></span></span></span></span>

<p>So, yes! Our 136 CFM Quiet CADR air purifier will do the trick there.</p>

<h2 id="our-algorithm">Our algorithm</h2>

<p>So, when we put all that together, our objective recommendation “algorithm” for customers for PM filtration ends up looking something like this:</p>

<ol>
  <li>Take every possible air purifier and calculate their Quiet CADRs</li>
  <li>Filter out the ones with &lt;12 month filter lifespans (if they want)</li>
  <li>Filter out the ones that aren’t premium designs (if they want)</li>
  <li>Find the square footage of the room in question and its filtration needs in ACH (based on room type/usage, outdoor/indoor conditions, specific concerns, etc.), and use that to calculate a Quiet CADR minimum, and filter out the ones that don’t meet it</li>
  <li>Make two recommendations: 1) the best value (i.e. cheapest one that meets the above standards), and 2) an upgrade pick (i.e. the one that has the highest throughput up to a 3x multiple on the minimum Quiet CADR)</li>
</ol>

<p>It comes out with something like this:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/picking-air-purifiers/living-room-800-c85ee215b.webp 800w, /assets/images/generated/picking-air-purifiers/living-room-1000-c85ee215b.webp 1000w, /assets/images/generated/picking-air-purifiers/living-room-1200-c85ee215b.webp 1200w, /assets/images/generated/picking-air-purifiers/living-room-1600-c85ee215b.webp 1600w, /assets/images/generated/picking-air-purifiers/living-room-2000-c85ee215b.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/picking-air-purifiers/living-room-800-f67e3588f.png 800w, /assets/images/generated/picking-air-purifiers/living-room-1000-f67e3588f.png 1000w, /assets/images/generated/picking-air-purifiers/living-room-1200-f67e3588f.png 1200w, /assets/images/generated/picking-air-purifiers/living-room-1600-f67e3588f.png 1600w, /assets/images/generated/picking-air-purifiers/living-room-2000-f67e3588f.png 2000w" type="image/png" /><img src="/assets/images/generated/picking-air-purifiers/living-room-800-f67e3588f.png" width="2240" height="1260" /></picture>

<p>The upgrade pick is an option for <em>really</em> clean air (without going overboard). Also, bigger units with higher throughput can usually get <em>even</em> quieter for the necessary performance since they are effectively oversized for the room.</p>

<p>There is a little more nuance to the formula (detailed on the <a href="https://purifiers.lightworkhome.com/methodology">Methodology page</a> of our tool), mostly having to do with allowing for “combos.” Sometimes, it’s cheaper to get multiple smaller air purifiers instead of one bigger one. We adjust for this in a couple ways, including:</p>
<ol>
  <li>Noise. Two units make more noise than one. Making some reasonable simplifying acoustic assumptions, two units at 38 decibels each equals 41 decibels total, and three units at 36 decibels each equals 41 decibels total. So when recommending a combo, we need to adjust the Quiet CADR for the combo units to allow for a lower noise ceiling.</li>
  <li>Slightly penalizing combos (with a 25% “premium” for each added one), since it’s more of a pain to deal with multiple instead of one</li>
</ol>

<p>As an aside, are you wondering why two units at 38 decibels = 41 decibels total? Decibels are a logarithmic unit (and a <a href="https://lcamtuf.substack.com/p/decibels-are-ridiculous">pretty odd one</a> at that). Decibels are measured on sound intensity, where with <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">L</span></span></span></span> as the level in decibels, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi></mrow><annotation encoding="application/x-tex">I</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span></span></span></span> as intensity of the sound, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">I_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> as reference intensity:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>L</mi><mtext>source</mtext></msub><mo>=</mo><mn>10</mn><mtext>&#x2009;</mtext><msub><mrow><mi>log</mi><mo>&#x2061;</mo></mrow><mn>10</mn></msub><mtext>&#x2009;&#x2063;</mtext><mrow><mo fence="true">(</mo><mfrac><mi>I</mi><msub><mi>I</mi><mn>0</mn></msub></mfrac><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">L_{\text{source}} = 10\,\log_{10}\!\left(\frac{I}{I_0}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">source</span></span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span>

<p>If we’re assuming two equal sources together<label for="sn-2" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-2" class="margin-toggle" /><span class="sidenote">This is a simplification. In practice, sound waves from two sources at different positions in a room interact in complex ways — they can constructively or destructively interfere depending on frequency and listener position, and reflections off walls add further complexity. But for a rough noise budget, simply adding the intensities is a reasonable and slightly conservative approximation.</span>, that means they produce <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mi>I</mi></mrow><annotation encoding="application/x-tex">2I</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span></span></span></span> intensity, and so:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>L</mi><mtext>double</mtext></msub><mo>=</mo><mn>10</mn><mtext>&#x2009;</mtext><msub><mrow><mi>log</mi><mo>&#x2061;</mo></mrow><mn>10</mn></msub><mtext>&#x2009;&#x2063;</mtext><mrow><mo fence="true">(</mo><mfrac><mrow><mn>2</mn><mi>I</mi></mrow><msub><mi>I</mi><mn>0</mn></msub></mfrac><mo fence="true">)</mo></mrow><mo>=</mo><mn>10</mn><mtext>&#x2009;</mtext><msub><mrow><mi>log</mi><mo>&#x2061;</mo></mrow><mn>10</mn></msub><mn>2</mn><mo>+</mo><mn>10</mn><mtext>&#x2009;</mtext><msub><mrow><mi>log</mi><mo>&#x2061;</mo></mrow><mn>10</mn></msub><mtext>&#x2009;&#x2063;</mtext><mrow><mo fence="true">(</mo><mfrac><mi>I</mi><msub><mi>I</mi><mn>0</mn></msub></mfrac><mo fence="true">)</mo></mrow><mo>&#x2248;</mo><mn>3.01</mn><mo>+</mo><msub><mi>L</mi><mtext>source</mtext></msub></mrow><annotation encoding="application/x-tex">L_{\text{double}} = 10\,\log_{10}\!\left(\frac{2I}{I_0}\right) = 10\,\log_{10} 2 + 10\,\log_{10}\!\left(\frac{I}{I_0}\right) \approx 3.01 + L_{\text{source}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">double</span></span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.9386em;vertical-align:-0.2441em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x2248;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">3.01</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">source</span></span></span></span></span></span><span class="vlist-s">&#x200b;</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span>

<p>So adding a second equal noise source, regardless of the sources’ actual decibels, always increases the sound level by 3 decibels! (And you can do similar math for 3 equal sources, which results in a ~5 decibel increase)</p>

<p>People often ask us about other features, like ionizers (we don’t recommend them), UV-C lights, plasma generators, “nano” coatings, photocatalytic oxidation, and more. In general, our view is that <em>most</em> of it is marketing and far less impactful than everything written above (and in some cases, potentially harmful). We may write a future post about some of these in particular, but broadly, our advice would be: nail the basics we’ve written about here.</p>

<h2 id="the-voc-problem">The VOC problem</h2>

<p>As discussed up top, VOCs require a fundamentally different filtration approach than particulate matter — they pass straight through HEPA and similar filter types. Instead, you need a medium like activated carbon to adsorb and trap the VOC gas molecules. Over time, the carbon medium saturates — so the more carbon you have, the better it will filter and longer it will last.</p>

<p>Most HEPA filters include a <em>thin</em> carbon pre-filter. This will help with low-level ordinary VOCs, but won’t do anything for more serious or chronic issues (and regardless, it saturates fast). To <em>really</em> deal with VOCs — if they’re an issue for you — you need a purpose-built air purifier.</p>

<p>There are other challenges, too. To push air through a thick carbon media, you need a really strong fan. And a really strong fan means a lot of noise. Plus, few of these purifier units are aesthetically appealing.</p>

<p>There <em>are</em> combination units that combine nontrivial carbon media with a HEPA filter. But at some level, these two facets are battling against each other. The carbon media (if it’s meaningful) is thick and dramatically reduces airflow in the performance of its role. But, as discussed at length above, airflow is the most important factor in a particulate matter purifier doing <em>its</em> job. So the two are at odds.</p>

<p>And on top of <em>all</em> of that, not all carbon media is equal. Aside from considerations about media thickness, density, and granule size (often hard to find details for and compare), different VOC or VOC-like pollutants are adsorbed at different rates by different media.</p>

<p>For example, formaldehyde is poorly captured by plain ol’ carbon. Potassium permanganate or potassium iodide impregnated carbon is significantly more effective for it and its aldehyde siblings.</p>

<p>Many of the top-end VOC-targeted air purifiers let you swap between different filter media blends, or even customize your own to target specific contaminants. But to pick this out, you really need to know what compounds exactly you’re trying to target — either by having someone test specific VOCs in your home, or identifying a source and determining what it is outputting or offgassing.</p>

<p>We go into all this in more detail and share some quantitative model recommendations in the free <a href="https://purifiers.lightworkhome.com/voc">VOC Guide</a> on Lightwork’s air purifier tool.</p>

<h2 id="so-what-do-we-recommend">So… what do we recommend?</h2>

<p>Well, like I said: the easiest way to see our recommendations is to check out <a href="https://purifiers.lightworkhome.com/">Lightwork’s Air Purifier tool</a> — which again is 100% free; no signup required; no affiliate links, partnerships, or bias; and purely quantitative and transparent.</p>

<p>I’m not just trying to get you to use the tool — the recommendations really <em>do</em> vary based on the size of the rooms you’re trying to filter, and your preferences around design, noise levels, filter lifespan, and more.</p>

<p>One of our main goals at Lightwork is to bring science-backed, data-driven rigor to an industry — home health — that has historically been plagued with imprecision, poorly-supported claims, and (at worst) scams. We hope this post has taught you a thing or two about air quality and helps you avoid falling victim to marketing claims that don’t measure up to reality.</p>

<p>Enjoy your clean air!</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="/collaborative-folders">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/collaborative-folders">Collaborative Folders: multiplayer notes for Obsidian</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/theory-of-constraints-skill">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/theory-of-constraints-skill">Theory of Constraints: the (Claude) Skill</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;Health&quot;, &quot;Math &amp; science&quot;, &quot;Tinkering&quot;]" /><summary type="html"><![CDATA[A data-driven approach to picking an air purifier that will actually be the best for you.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-04-04-picking-air-purifiers.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-04-04-picking-air-purifiers.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Collaborative Folders: multiplayer notes for Obsidian</title><link href="https://andybromberg.com/collaborative-folders" rel="alternate" type="text/html" title="Collaborative Folders: multiplayer notes for Obsidian" /><published>2026-03-01T06:00:00-07:00</published><updated>2026-03-01T06:00:00-07:00</updated><id>https://andybromberg.com/collaborative-folders</id><content type="html" xml:base="https://andybromberg.com/collaborative-folders"><![CDATA[<p><a href="//collaborativefolders.com" target="_blank" rel="noopener noreferrer"><picture loading="lazy"><source srcset="/assets/images/generated/collaborative-folders/collaborativefolders_header-800-be0322fd8.webp 800w, /assets/images/generated/collaborative-folders/collaborativefolders_header-1000-be0322fd8.webp 1000w, /assets/images/generated/collaborative-folders/collaborativefolders_header-1200-be0322fd8.webp 1200w, /assets/images/generated/collaborative-folders/collaborativefolders_header-1600-be0322fd8.webp 1600w, /assets/images/generated/collaborative-folders/collaborativefolders_header-2000-be0322fd8.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/collaborative-folders/collaborativefolders_header-800-eb033f817.png 800w, /assets/images/generated/collaborative-folders/collaborativefolders_header-1000-eb033f817.png 1000w, /assets/images/generated/collaborative-folders/collaborativefolders_header-1200-eb033f817.png 1200w, /assets/images/generated/collaborative-folders/collaborativefolders_header-1600-eb033f817.png 1600w, /assets/images/generated/collaborative-folders/collaborativefolders_header-2000-eb033f817.png 2000w" type="image/png" /><img src="/assets/images/generated/collaborative-folders/collaborativefolders_header-800-eb033f817.png" width="2340" height="936" /></picture>
</a></p>

<p>I <em>love</em> <a href="https://obsidian.md">Obsidian</a><label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle" /><span class="sidenote">So much, in fact, this blog uses a color palette, <a href="https://stephango.com/flexoki">Flexoki</a>, designed by Obsidian CEO Steph Ango.</span>, but I <em>hate</em> copying my notes out into Google Docs to share and edit them with teammates.</p>

<p>So I built an Obsidian Plugin called <a href="https://collaborativefolders.com">Collaborative Folders</a> that enables real-time, end-to-end encrypted, Google Docs-style multiplayer editing of files inside Obsidian. Here’s a demo:</p>

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

<hr />

<p>Basically: share a folder with another Collaborative Folders user and the files inside that folder will stay in sync between your local Obsidian instances. You can edit them together and see each other’s cursors. Images and other attachments come along for the ride, too. And you can generate share links for files that deeplink into the recipient’s own Obsidian vault.</p>

<p>This is built with a relay server that handles communication between your Obsidian instances, but that relay server can’t see the content of the notes — they are encrypted end-to-end.</p>

<p>You can use my hosted relay server for a monthly fee or easily deploy your own (instructions for the latter available <a href="https://github.com/abromberg/obsidian-collaborative-folders">on GitHub</a>, along with the entire plugin &amp; server MIT-licensed source code).</p>

<p>Check it all out at <a href="https://collaborativefolders.com">collaborativefolders.com</a> and let me know what you think! Would love to hear feedback. It is very much a beta product, so follow typical backup precautions…</p>

<p><em>p.s. the valiant Obsidian plugin reviewing team is facing an onslaught of submissions due to the rise in vibecoded plugins. It may be weeks or months until they get to this one and add it to the real directory — on the site I have workaround installation instructions. But in the meantime, maybe give an emoji reaction or polite supportive comment on the <a href="https://github.com/obsidianmd/obsidian-releases/pull/10655">GitHub PR</a> for my plugin, or ping anyone you know at Obsidian :)</em></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="/i-dont-write-code-anymore">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/i-dont-write-code-anymore">I don't write code anymore</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/picking-air-purifiers">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/picking-air-purifiers">How to pick the best air purifier, objectively</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[Real-time, multiplayer shared folders and notes for Obsidian. Everything you love about Google Docs, but in your lovely local Obsidian instance.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-03-01-collaborative-folders.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-03-01-collaborative-folders.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I don’t write code anymore</title><link href="https://andybromberg.com/i-dont-write-code-anymore" rel="alternate" type="text/html" title="I don’t write code anymore" /><published>2026-02-21T06:00:00-07:00</published><updated>2026-02-21T06:00:00-07:00</updated><id>https://andybromberg.com/i-dont-write-code-anymore</id><content type="html" xml:base="https://andybromberg.com/i-dont-write-code-anymore"><![CDATA[<p>Well, it happened. As of mid-February 2026, I effectively no longer handwrite code.</p>

<p>If you had asked me six weeks ago when we’d cross that chasm, I would have said “maybe end of 2026.” But life comes at you fast.</p>

<p>I’m not alone in this, of course. Boris Cherny, creator of Claude Code, <a href="https://www.linkedin.com/posts/lennyrachitsky_head-of-claude-code-boris-cherny-a-100-activity-7430668952420302850-gXL2/">says</a>:</p>

<blockquote>
  <p>100% of my code is written by Claude Code. I have not edited a single line by hand since November. I think at this point it’s safe to say that coding is largely solved. And so now we’re starting to think about okay what’s next.</p>
</blockquote>

<p>The most remarkable thing in all this is the distinct feeling of <em>acceleration</em>. Part of the nature of being on a psuedo-exponential curve is that every moment feels <em>way</em> faster than the last. It’s hard for me to imagine where we’ll be by the end of this year, let alone five years from now. So I’m just enjoying the ride (and shipping more than I ever have before).</p>

<hr />

<p>It’s worth noting that this is not yet <em>at all</em> the same as saying “my coding skills &amp; engineering background are useless” (although that may not be far off). There are a <em>ton</em> of things I find where that knowledge still matters a lot. The way I go about prompting the agents; the ways in which I correct the models’ plans; the libraries I point them towards; how I test the product and what modifications I ask for; reviewing the code for dumb things the models tend to do; the idiosyncratic architectural approaches I suggest for certain products and use cases.</p>

<p>I observe that the people who are most productive now with coding agents have some level of management experience — because those skills (delegating, editing, directing, testing…) are in fact often <em>management</em> skills. (See my previous post <a href="/all-editors-now">We’re all the editors now</a>).</p>

<p>So my saying “I effectively no longer handwrite code” is not the same as “everyone now has equal ability to do software engineering.” Non-technical vibecoding remains incredible for many use cases, but for complex products still creates buggy balls of mud that are hard to maintain and continue to improve (again: this may not last long).</p>

<p>Although I will say that the vanguard of non-technical vibecoding seems to be advancing quite a bit too. A friend of mine took my recent posts about <a href="/congruence/intro">Congruence</a>, an app I made, and fed the links to Claude Code and asked it to reproduce the app. It did. (You should try this too, if you’re interested!)</p>

<hr />

<p>What pushed me over the edge of no longer handwriting code? Opus 4.6, GPT-5.3 Codex, and the Claude Code, Codex, and OpenCode harnesses making big strides through January and February of this year. It suddenly no longer made sense for me to write code myself.</p>

<p>I can’t wait to see what the rest of this year brings.</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="/toc-ai-stanford">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/toc-ai-stanford">Stanford Lecture on Theory of Constraints for AI</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/collaborative-folders">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/collaborative-folders">Collaborative Folders: multiplayer notes for Obsidian</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[Well, it happened. Early/mid February 2026 is when I effectively stopped handwriting code.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-02-21-i-dont-write-code-anymore.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-02-21-i-dont-write-code-anymore.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Stanford Lecture on Theory of Constraints for AI</title><link href="https://andybromberg.com/toc-ai-stanford" rel="alternate" type="text/html" title="Stanford Lecture on Theory of Constraints for AI" /><published>2026-02-17T06:00:00-07:00</published><updated>2026-02-17T06:00:00-07:00</updated><id>https://andybromberg.com/toc-ai-stanford</id><content type="html" xml:base="https://andybromberg.com/toc-ai-stanford"><![CDATA[<p>Earlier this month I gave a guest lecture at Stanford’s CS 224G course <em>“Building &amp; Scaling LLM Applications”</em> on applying the Theory of Constraints to building LLM products. Thanks to instructors John Whaley and Jan Jannick for inviting me! Below are the slides from the talk with accompanying notes.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-01-800-b5ddb1420.webp 800w, /assets/images/generated/toc-ai-stanford/slide-01-1000-b5ddb1420.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-01-1200-b5ddb1420.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-01-1600-b5ddb1420.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-01-2000-b5ddb1420.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-01-800-f5040625c.png 800w, /assets/images/generated/toc-ai-stanford/slide-01-1000-f5040625c.png 1000w, /assets/images/generated/toc-ai-stanford/slide-01-1200-f5040625c.png 1200w, /assets/images/generated/toc-ai-stanford/slide-01-1600-f5040625c.png 1600w, /assets/images/generated/toc-ai-stanford/slide-01-2000-f5040625c.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-01-800-f5040625c.png" width="8000" height="4500" /></picture>

<hr />

<h2 id="the-problem">The Problem</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-02-800-9631101a9.webp 800w, /assets/images/generated/toc-ai-stanford/slide-02-1000-9631101a9.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-02-1200-9631101a9.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-02-1600-9631101a9.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-02-2000-9631101a9.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-02-800-b8e73fb6f.png 800w, /assets/images/generated/toc-ai-stanford/slide-02-1000-b8e73fb6f.png 1000w, /assets/images/generated/toc-ai-stanford/slide-02-1200-b8e73fb6f.png 1200w, /assets/images/generated/toc-ai-stanford/slide-02-1600-b8e73fb6f.png 1600w, /assets/images/generated/toc-ai-stanford/slide-02-2000-b8e73fb6f.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-02-800-b8e73fb6f.png" width="8000" height="4500" /></picture>

<p>It’s never been easier to build things with LLMs. Anyone can spin up a prototype in an afternoon. But getting from prototype to a <em>good</em> product – one that works reliably, consistently, at quality – is a different challenge entirely.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-05-800-48ceff672.webp 800w, /assets/images/generated/toc-ai-stanford/slide-05-1000-48ceff672.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-05-1200-48ceff672.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-05-1600-48ceff672.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-05-2000-48ceff672.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-05-800-b59e891a5.png 800w, /assets/images/generated/toc-ai-stanford/slide-05-1000-b59e891a5.png 1000w, /assets/images/generated/toc-ai-stanford/slide-05-1200-b59e891a5.png 1200w, /assets/images/generated/toc-ai-stanford/slide-05-1600-b59e891a5.png 1600w, /assets/images/generated/toc-ai-stanford/slide-05-2000-b59e891a5.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-05-800-b59e891a5.png" width="8000" height="4500" /></picture>

<p>The big challenge (and arguably, the magic) with building on LLMs is that they are <strong>non-deterministic</strong>. Ask the same question twice and you get different responses. For a simple factual query that’s fine – both answers above are correct. But when you’re building a product, this variance becomes a real problem.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-06-800-c8358c831.webp 800w, /assets/images/generated/toc-ai-stanford/slide-06-1000-c8358c831.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-06-1200-c8358c831.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-06-1600-c8358c831.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-06-2000-c8358c831.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-06-800-179b6780b.png 800w, /assets/images/generated/toc-ai-stanford/slide-06-1000-179b6780b.png 1000w, /assets/images/generated/toc-ai-stanford/slide-06-1200-179b6780b.png 1200w, /assets/images/generated/toc-ai-stanford/slide-06-1600-179b6780b.png 1600w, /assets/images/generated/toc-ai-stanford/slide-06-2000-179b6780b.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-06-800-179b6780b.png" width="8000" height="4500" /></picture>

<p>And complexity compounds non-determinism. Especially when you chain LLM calls together or admit arbitrary user input, each non-deterministic step multiplies the variance of the next. By the end of a meaningful AI pipeline, things can shoot far off in the wrong direction.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-07-800-f24dc4d18.webp 800w, /assets/images/generated/toc-ai-stanford/slide-07-1000-f24dc4d18.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-07-1200-f24dc4d18.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-07-1600-f24dc4d18.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-07-2000-f24dc4d18.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-07-800-907cd116b.png 800w, /assets/images/generated/toc-ai-stanford/slide-07-1000-907cd116b.png 1000w, /assets/images/generated/toc-ai-stanford/slide-07-1200-907cd116b.png 1200w, /assets/images/generated/toc-ai-stanford/slide-07-1600-907cd116b.png 1600w, /assets/images/generated/toc-ai-stanford/slide-07-2000-907cd116b.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-07-800-907cd116b.png" width="8000" height="4500" /></picture>

<p>This compounding non-determinism causes quality and reliability issues. You can’t always just “vibe” your way to a good product when the output surface is this wide. Enter: <strong>systems thinking</strong>.</p>

<hr />

<h2 id="the-theory-of-constraints">The Theory of Constraints</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-08-800-90ee61f35.webp 800w, /assets/images/generated/toc-ai-stanford/slide-08-1000-90ee61f35.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-08-1200-90ee61f35.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-08-1600-90ee61f35.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-08-2000-90ee61f35.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-08-800-4a5d0be56.png 800w, /assets/images/generated/toc-ai-stanford/slide-08-1000-4a5d0be56.png 1000w, /assets/images/generated/toc-ai-stanford/slide-08-1200-4a5d0be56.png 1200w, /assets/images/generated/toc-ai-stanford/slide-08-1600-4a5d0be56.png 1600w, /assets/images/generated/toc-ai-stanford/slide-08-2000-4a5d0be56.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-08-800-4a5d0be56.png" width="8000" height="4500" /></picture>

<p>One framework I’ve found very useful for this is the <strong>Theory of Constraints</strong> (TOC), introduced by Eliyahu Goldratt in his 1984 book <em>The Goal</em>. Yes, it’s a 1980s sales-y looking book about factory management. Bear with me.</p>

<p>(I’ve written about TOC before: <a href="/uwol/theory-of-constraints">Theory of Constraints overview</a> and <a href="/constraints-ai">Applying constraints thinking to AI</a>.)</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-09-800-145c76156.webp 800w, /assets/images/generated/toc-ai-stanford/slide-09-1000-145c76156.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-09-1200-145c76156.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-09-1600-145c76156.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-09-2000-145c76156.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-09-800-7c9f0d97d.png 800w, /assets/images/generated/toc-ai-stanford/slide-09-1000-7c9f0d97d.png 1000w, /assets/images/generated/toc-ai-stanford/slide-09-1200-7c9f0d97d.png 1200w, /assets/images/generated/toc-ai-stanford/slide-09-1600-7c9f0d97d.png 1600w, /assets/images/generated/toc-ai-stanford/slide-09-2000-7c9f0d97d.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-09-800-7c9f0d97d.png" width="8000" height="4500" /></picture>

<p><strong>TOC in one image: The What.</strong> Think of a system as water flowing through a series of pipes. Each pipe section has a different diameter. The total throughput of the system is determined by the <em>narrowest</em> pipe – the bottleneck, or “constraint.” No matter how much you widen other sections, total flow doesn’t increase until you address the bottleneck.</p>

<p>This is one of the core principles of ToC, and it applies broadly. There is almost always <em>one</em> rate-limiting step that is constraining your system. You could improve <em>any</em> other part of the system and it would do <em>nothing</em>.</p>

<p>And note here that “throughput” is meant generally. It can refer to speed, or quality, or any other metric you are measuring. But regardless, in the “pipeline” to “throughput,” there will be one constraint.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-10-800-9f1bfde94.webp 800w, /assets/images/generated/toc-ai-stanford/slide-10-1000-9f1bfde94.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-10-1200-9f1bfde94.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-10-1600-9f1bfde94.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-10-2000-9f1bfde94.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-10-800-3174d75ca.png 800w, /assets/images/generated/toc-ai-stanford/slide-10-1000-3174d75ca.png 1000w, /assets/images/generated/toc-ai-stanford/slide-10-1200-3174d75ca.png 1200w, /assets/images/generated/toc-ai-stanford/slide-10-1600-3174d75ca.png 1600w, /assets/images/generated/toc-ai-stanford/slide-10-2000-3174d75ca.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-10-800-3174d75ca.png" width="8000" height="4500" /></picture>

<p><strong>TOC in a second image: The How.</strong> Goldratt’s five focusing steps:</p>

<ol>
  <li><strong>Identify</strong> the system’s constraint (the bottleneck)</li>
  <li><strong>Exploit</strong> the constraint (maximize its performance by leveraging existing resources)</li>
  <li><strong>Subordinate</strong> everything else (align all other processes to support the bottleneck)</li>
  <li><strong>Elevate</strong> the constraint (invest to increase the bottleneck’s capacity)</li>
  <li><strong>Repeat</strong> (prevent inertia – go back to step 1)</li>
</ol>

<p>If you just do these over and over again, you will fix your problem.</p>

<hr />

<h2 id="modeling-the-llm-system">Modeling the LLM System</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-11-800-f83e6d718.webp 800w, /assets/images/generated/toc-ai-stanford/slide-11-1000-f83e6d718.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-11-1200-f83e6d718.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-11-1600-f83e6d718.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-11-2000-f83e6d718.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-11-800-eafd899ea.png 800w, /assets/images/generated/toc-ai-stanford/slide-11-1000-eafd899ea.png 1000w, /assets/images/generated/toc-ai-stanford/slide-11-1200-eafd899ea.png 1200w, /assets/images/generated/toc-ai-stanford/slide-11-1600-eafd899ea.png 1600w, /assets/images/generated/toc-ai-stanford/slide-11-2000-eafd899ea.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-11-800-eafd899ea.png" width="8000" height="4500" /></picture>

<p>To apply TOC, you first need to model and understand the system you are optimizing and define its goal. An LLM application has a few core components: the <strong>system prompt</strong>, the <strong>user prompt</strong>, the <strong>context</strong> (retrieved data, files, etc.), the <strong>tools</strong> available to the model, the <strong>LLM</strong> itself, and the <strong>output</strong>.</p>

<p>I’ve written about this in my <a href="/field-guide-context-engineering">field guide to context engineering</a> and in <a href="/constraints-ai">theory of constraints for AI</a>.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-12-800-588e45ee6.webp 800w, /assets/images/generated/toc-ai-stanford/slide-12-1000-588e45ee6.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-12-1200-588e45ee6.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-12-1600-588e45ee6.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-12-2000-588e45ee6.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-12-800-0baaaf126.png 800w, /assets/images/generated/toc-ai-stanford/slide-12-1000-0baaaf126.png 1000w, /assets/images/generated/toc-ai-stanford/slide-12-1200-0baaaf126.png 1200w, /assets/images/generated/toc-ai-stanford/slide-12-1600-0baaaf126.png 1600w, /assets/images/generated/toc-ai-stanford/slide-12-2000-0baaaf126.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-12-800-0baaaf126.png" width="8000" height="4500" /></picture>

<p>Here’s what that looks like concretely. Imagine you’re building a contract analysis tool: the system prompt says “You are an expert lawyer…”, the user prompt includes the analysis request, the context includes the actual contract document, and a tool like PaddleOCR handles document extraction.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-13-800-661c3912a.webp 800w, /assets/images/generated/toc-ai-stanford/slide-13-1000-661c3912a.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-13-1200-661c3912a.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-13-1600-661c3912a.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-13-2000-661c3912a.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-13-800-d8877d08f.png 800w, /assets/images/generated/toc-ai-stanford/slide-13-1000-d8877d08f.png 1000w, /assets/images/generated/toc-ai-stanford/slide-13-1200-d8877d08f.png 1200w, /assets/images/generated/toc-ai-stanford/slide-13-1600-d8877d08f.png 1600w, /assets/images/generated/toc-ai-stanford/slide-13-2000-d8877d08f.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-13-800-d8877d08f.png" width="8000" height="4500" /></picture>

<p>One thing to note: <strong>you don’t have control over the LLM itself</strong>. You can’t change how Claude or GPT work internally. And honestly, the model is probably not your bottleneck anyway. The frontier models are remarkably capable today.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-14-800-2540476b0.webp 800w, /assets/images/generated/toc-ai-stanford/slide-14-1000-2540476b0.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-14-1200-2540476b0.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-14-1600-2540476b0.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-14-2000-2540476b0.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-14-800-4a49600fb.png 800w, /assets/images/generated/toc-ai-stanford/slide-14-1000-4a49600fb.png 1000w, /assets/images/generated/toc-ai-stanford/slide-14-1200-4a49600fb.png 1200w, /assets/images/generated/toc-ai-stanford/slide-14-1600-4a49600fb.png 1600w, /assets/images/generated/toc-ai-stanford/slide-14-2000-4a49600fb.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-14-800-4a49600fb.png" width="8000" height="4500" /></picture>

<p>What you <em>do</em> have control over are the inputs: the system prompt, user prompt, context, and tools. <strong>One of these is almost always the constraint.</strong> Your first job is to figure out which one.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-15-800-024050a64.webp 800w, /assets/images/generated/toc-ai-stanford/slide-15-1000-024050a64.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-15-1200-024050a64.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-15-1600-024050a64.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-15-2000-024050a64.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-15-800-a90e24003.png 800w, /assets/images/generated/toc-ai-stanford/slide-15-1000-a90e24003.png 1000w, /assets/images/generated/toc-ai-stanford/slide-15-1200-a90e24003.png 1200w, /assets/images/generated/toc-ai-stanford/slide-15-1600-a90e24003.png 1600w, /assets/images/generated/toc-ai-stanford/slide-15-2000-a90e24003.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-15-800-a90e24003.png" width="8000" height="4500" /></picture>

<p>Once you’ve identified the constraint, you can start improving it – adding detail to the system prompt, providing few-shot examples in the user prompt, enriching the context with a database of historical documents, adding a second OCR provider for redundancy. <strong>But remember: one at a time.</strong> This is key to the TOC approach. Change one variable, measure the impact, then reassess.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-16-800-e9b631c07.webp 800w, /assets/images/generated/toc-ai-stanford/slide-16-1000-e9b631c07.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-16-1200-e9b631c07.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-16-1600-e9b631c07.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-16-2000-e9b631c07.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-16-800-a5dcfc841.png 800w, /assets/images/generated/toc-ai-stanford/slide-16-1000-a5dcfc841.png 1000w, /assets/images/generated/toc-ai-stanford/slide-16-1200-a5dcfc841.png 1200w, /assets/images/generated/toc-ai-stanford/slide-16-1600-a5dcfc841.png 1600w, /assets/images/generated/toc-ai-stanford/slide-16-2000-a5dcfc841.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-16-800-a5dcfc841.png" width="8000" height="4500" /></picture>

<p>Sometimes the constraint isn’t a single component but the <strong>system architecture itself</strong>. In that case, you need to change your view of the system. For instance, splitting a single LLM call into a two-stage pipeline – one for data extraction, another for analysis – can dramatically improve results by giving each stage a focused task, better tools, and cleaner context.</p>

<hr />

<h2 id="defining-the-goal-and-measuring-progress">Defining the Goal and Measuring Progress</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-17-800-2ed27c1d4.webp 800w, /assets/images/generated/toc-ai-stanford/slide-17-1000-2ed27c1d4.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-17-1200-2ed27c1d4.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-17-1600-2ed27c1d4.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-17-2000-2ed27c1d4.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-17-800-3c8f6e819.png 800w, /assets/images/generated/toc-ai-stanford/slide-17-1000-3c8f6e819.png 1000w, /assets/images/generated/toc-ai-stanford/slide-17-1200-3c8f6e819.png 1200w, /assets/images/generated/toc-ai-stanford/slide-17-1600-3c8f6e819.png 1600w, /assets/images/generated/toc-ai-stanford/slide-17-2000-3c8f6e819.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-17-800-3c8f6e819.png" width="8000" height="4500" /></picture>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-18-800-1d838b684.webp 800w, /assets/images/generated/toc-ai-stanford/slide-18-1000-1d838b684.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-18-1200-1d838b684.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-18-1600-1d838b684.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-18-2000-1d838b684.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-18-800-8fbb5000f.png 800w, /assets/images/generated/toc-ai-stanford/slide-18-1000-8fbb5000f.png 1000w, /assets/images/generated/toc-ai-stanford/slide-18-1200-8fbb5000f.png 1200w, /assets/images/generated/toc-ai-stanford/slide-18-1600-8fbb5000f.png 1600w, /assets/images/generated/toc-ai-stanford/slide-18-2000-8fbb5000f.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-18-800-8fbb5000f.png" width="8000" height="4500" /></picture>

<p>You need to define <strong>The Goal</strong>. As Goldratt puts it: “You cannot understand the meaning of productivity unless you know what the goal is. Until then, you’re just playing a lot of games with numbers and words.”</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-19-800-bf004fc15.webp 800w, /assets/images/generated/toc-ai-stanford/slide-19-1000-bf004fc15.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-19-1200-bf004fc15.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-19-1600-bf004fc15.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-19-2000-bf004fc15.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-19-800-ad6507097.png 800w, /assets/images/generated/toc-ai-stanford/slide-19-1000-ad6507097.png 1000w, /assets/images/generated/toc-ai-stanford/slide-19-1200-ad6507097.png 1200w, /assets/images/generated/toc-ai-stanford/slide-19-1600-ad6507097.png 1600w, /assets/images/generated/toc-ai-stanford/slide-19-2000-ad6507097.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-19-800-ad6507097.png" width="8000" height="4500" /></picture>

<p>The chain of reasoning:</p>

<ul>
  <li>Good results need <strong>improvement</strong></li>
  <li>Improvement needs <strong>evals</strong></li>
  <li>Evals need a <strong>goal</strong></li>
  <li>A goal needs a <strong>definition</strong></li>
</ul>

<p>Start at the bottom: define what “good” means for your product, concretely and specifically.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-20-800-bcfa3e56b.webp 800w, /assets/images/generated/toc-ai-stanford/slide-20-1000-bcfa3e56b.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-20-1200-bcfa3e56b.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-20-1600-bcfa3e56b.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-20-2000-bcfa3e56b.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-20-800-f88d7a9c0.png 800w, /assets/images/generated/toc-ai-stanford/slide-20-1000-f88d7a9c0.png 1000w, /assets/images/generated/toc-ai-stanford/slide-20-1200-f88d7a9c0.png 1200w, /assets/images/generated/toc-ai-stanford/slide-20-1600-f88d7a9c0.png 1600w, /assets/images/generated/toc-ai-stanford/slide-20-2000-f88d7a9c0.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-20-800-f88d7a9c0.png" width="8000" height="4500" /></picture>

<p>We said our goal is <strong>consistent high quality outputs</strong>. But how do you actually measure that? We <em>need</em> to in order to apply the Theory of Constraints and see if we’re improving or not.</p>

<hr />

<h2 id="evals">Evals</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-21-800-8a32b611c.webp 800w, /assets/images/generated/toc-ai-stanford/slide-21-1000-8a32b611c.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-21-1200-8a32b611c.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-21-1600-8a32b611c.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-21-2000-8a32b611c.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-21-800-ad0d0cb8f.png 800w, /assets/images/generated/toc-ai-stanford/slide-21-1000-ad0d0cb8f.png 1000w, /assets/images/generated/toc-ai-stanford/slide-21-1200-ad0d0cb8f.png 1200w, /assets/images/generated/toc-ai-stanford/slide-21-1600-ad0d0cb8f.png 1600w, /assets/images/generated/toc-ai-stanford/slide-21-2000-ad0d0cb8f.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-21-800-ad0d0cb8f.png" width="8000" height="4500" /></picture>

<p>In the AI world, ways of measuring against a goal are called “evals.” In my view, there are categorically two approaches:</p>

<ol>
  <li><strong>“Proper” evals</strong> – structured, automated evaluation with scored results. (It is <em>much</em> easier to build a good product if you can create these!)</li>
  <li><strong>Vibe evals</strong> – a gut-check approach.</li>
</ol>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-22-800-0f613e7ea.webp 800w, /assets/images/generated/toc-ai-stanford/slide-22-1000-0f613e7ea.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-22-1200-0f613e7ea.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-22-1600-0f613e7ea.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-22-2000-0f613e7ea.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-22-800-a31a65fe4.png 800w, /assets/images/generated/toc-ai-stanford/slide-22-1000-a31a65fe4.png 1000w, /assets/images/generated/toc-ai-stanford/slide-22-1200-a31a65fe4.png 1200w, /assets/images/generated/toc-ai-stanford/slide-22-1600-a31a65fe4.png 1600w, /assets/images/generated/toc-ai-stanford/slide-22-2000-a31a65fe4.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-22-800-a31a65fe4.png" width="8000" height="4500" /></picture>

<p>For <strong>proper evals</strong>: in general, you want to build and maintain an “eval set” of test cases, run your system against it continually, and update accordingly.</p>

<p>(If you’ve wondered by Mercor, Scale AI, and Surge AI are as big as they are… it’s because of this. They are providing evals to every major AI lab and application company. It’s big business.)</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-23-800-0b4f8eda8.webp 800w, /assets/images/generated/toc-ai-stanford/slide-23-1000-0b4f8eda8.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-23-1200-0b4f8eda8.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-23-1600-0b4f8eda8.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-23-2000-0b4f8eda8.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-23-800-db431d63b.png 800w, /assets/images/generated/toc-ai-stanford/slide-23-1000-db431d63b.png 1000w, /assets/images/generated/toc-ai-stanford/slide-23-1200-db431d63b.png 1200w, /assets/images/generated/toc-ai-stanford/slide-23-1600-db431d63b.png 1600w, /assets/images/generated/toc-ai-stanford/slide-23-2000-db431d63b.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-23-800-db431d63b.png" width="8000" height="4500" /></picture>

<p>Here’s a concrete example: <strong>document extraction</strong>. You have pre-labeled documents with known-good structured output. You run your system on the same inputs, compute similarity between the expected and actual output, and try to maximize that score. This is the dream scenario for proper evals.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-24-800-34ed80505.webp 800w, /assets/images/generated/toc-ai-stanford/slide-24-1000-34ed80505.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-24-1200-34ed80505.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-24-1600-34ed80505.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-24-2000-34ed80505.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-24-800-051ec9f08.png 800w, /assets/images/generated/toc-ai-stanford/slide-24-1000-051ec9f08.png 1000w, /assets/images/generated/toc-ai-stanford/slide-24-1200-051ec9f08.png 1200w, /assets/images/generated/toc-ai-stanford/slide-24-1600-051ec9f08.png 1600w, /assets/images/generated/toc-ai-stanford/slide-24-2000-051ec9f08.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-24-800-051ec9f08.png" width="8000" height="4500" /></picture>

<p>For <strong>vibe evals</strong>: sometimes your product’s outputs are too varied or subjective for structured scoring. In those cases, you just… look at the results and ask yourself how good they are. Not as good, but much easier.</p>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-25-800-e617b0299.webp 800w, /assets/images/generated/toc-ai-stanford/slide-25-1000-e617b0299.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-25-1200-e617b0299.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-25-1600-e617b0299.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-25-2000-e617b0299.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-25-800-d21a3a63b.png 800w, /assets/images/generated/toc-ai-stanford/slide-25-1000-d21a3a63b.png 1000w, /assets/images/generated/toc-ai-stanford/slide-25-1200-d21a3a63b.png 1200w, /assets/images/generated/toc-ai-stanford/slide-25-1600-d21a3a63b.png 1600w, /assets/images/generated/toc-ai-stanford/slide-25-2000-d21a3a63b.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-25-800-d21a3a63b.png" width="8000" height="4500" /></picture>

<p>The full spectrum of eval options:</p>

<ol>
  <li><strong>Proper eval</strong>: pre-labeled results that should match exactly</li>
  <li><strong>Proper eval</strong>: pre-labeled results you can compute similarity to</li>
  <li><strong>Proper eval</strong>: LLM-as-a-judge of the results (this is a very common approach right now)</li>
  <li><strong>Vibe eval</strong>: how does it <em>feel</em>?</li>
</ol>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-30-800-ddc07836b.webp 800w, /assets/images/generated/toc-ai-stanford/slide-30-1000-ddc07836b.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-30-1200-ddc07836b.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-30-1600-ddc07836b.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-30-2000-ddc07836b.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-30-800-4b68a7b22.png 800w, /assets/images/generated/toc-ai-stanford/slide-30-1000-4b68a7b22.png 1000w, /assets/images/generated/toc-ai-stanford/slide-30-1200-4b68a7b22.png 1200w, /assets/images/generated/toc-ai-stanford/slide-30-1600-4b68a7b22.png 1600w, /assets/images/generated/toc-ai-stanford/slide-30-2000-4b68a7b22.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-30-800-4b68a7b22.png" width="8000" height="4500" /></picture>

<p>Putting it all together into a repeatable improvement loop:</p>

<ol>
  <li><strong>Diagram out your system</strong> (map the components and how they connect)</li>
  <li><strong>Define your goal &amp; eval process</strong> (proper or vibe evals)</li>
  <li><strong>Assess where the constraint is</strong> (system prompt, user prompt, context, tools, or the system view itself)</li>
  <li><strong>Use TOC</strong>: exploit the constraint, subordinate everything else, elevate the constraint… and measure evals each time</li>
  <li><strong>Go to step 3</strong></li>
</ol>

<p>This sounds boring and programmatic and obvious… <strong>but it works</strong> and is what powers the most impressive/magical AI applications. And most people do not approach it so rigorously.</p>

<p>(Bonus: it actually works for most any problem in life or business. Not just AI!)</p>

<hr />

<h2 id="practical-examples">Practical Examples</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-31-800-660794f80.webp 800w, /assets/images/generated/toc-ai-stanford/slide-31-1000-660794f80.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-31-1200-660794f80.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-31-1600-660794f80.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-31-2000-660794f80.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-31-800-2b301fe56.png 800w, /assets/images/generated/toc-ai-stanford/slide-31-1000-2b301fe56.png 1000w, /assets/images/generated/toc-ai-stanford/slide-31-1200-2b301fe56.png 1200w, /assets/images/generated/toc-ai-stanford/slide-31-1600-2b301fe56.png 1600w, /assets/images/generated/toc-ai-stanford/slide-31-2000-2b301fe56.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-31-800-2b301fe56.png" width="8000" height="4500" /></picture>

<p>Three practical examples of applying this framework:</p>

<h3 id="1-lightwork-knowledge-base">1. Lightwork Knowledge Base</h3>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-32-800-8033ebcaa.webp 800w, /assets/images/generated/toc-ai-stanford/slide-32-1000-8033ebcaa.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-32-1200-8033ebcaa.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-32-1600-8033ebcaa.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-32-2000-8033ebcaa.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-32-800-3b9038723.png 800w, /assets/images/generated/toc-ai-stanford/slide-32-1000-3b9038723.png 1000w, /assets/images/generated/toc-ai-stanford/slide-32-1200-3b9038723.png 1200w, /assets/images/generated/toc-ai-stanford/slide-32-1600-3b9038723.png 1600w, /assets/images/generated/toc-ai-stanford/slide-32-2000-3b9038723.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-32-800-3b9038723.png" width="8000" height="4500" /></picture>

<p><a href="https://lightworkhome.com">Lightwork Home Health</a> built a knowledge base tool where our team can ask questions and get draft email responses or segments of reports grounded in prior research and reports.</p>

<p><strong>Eval approach</strong>: holdback a set of known-good emails (with the client inquiry) and segments of reports (with the raw data). Run the latest system against these inputs. Use an LLM as a judge to compare the holdback with the actual outputs.</p>

<p><strong>TOC application</strong>: the original constraint was the system prompt. After improving that, the ongoing constraint has been context – needing more and better source material.</p>

<h3 id="2-interface0">2. interface0</h3>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-33-800-4da8f5532.webp 800w, /assets/images/generated/toc-ai-stanford/slide-33-1000-4da8f5532.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-33-1200-4da8f5532.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-33-1600-4da8f5532.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-33-2000-4da8f5532.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-33-800-ab328e2a5.png 800w, /assets/images/generated/toc-ai-stanford/slide-33-1000-ab328e2a5.png 1000w, /assets/images/generated/toc-ai-stanford/slide-33-1200-ab328e2a5.png 1200w, /assets/images/generated/toc-ai-stanford/slide-33-1600-ab328e2a5.png 1600w, /assets/images/generated/toc-ai-stanford/slide-33-2000-ab328e2a5.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-33-800-ab328e2a5.png" width="8000" height="4500" /></picture>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-34-800-e47a2c3ba.webp 800w, /assets/images/generated/toc-ai-stanford/slide-34-1000-e47a2c3ba.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-34-1200-e47a2c3ba.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-34-1600-e47a2c3ba.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-34-2000-e47a2c3ba.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-34-800-0d7823121.png 800w, /assets/images/generated/toc-ai-stanford/slide-34-1000-0d7823121.png 1000w, /assets/images/generated/toc-ai-stanford/slide-34-1200-0d7823121.png 1200w, /assets/images/generated/toc-ai-stanford/slide-34-1600-0d7823121.png 1600w, /assets/images/generated/toc-ai-stanford/slide-34-2000-0d7823121.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-34-800-0d7823121.png" width="8000" height="4500" /></picture>

<p><a href="https://interface0.com">interface0</a> is an AI interface for power users and teams. Because user requests are so varied (and it is sub-scale), proper evals aren’t feasible here – it’s pure <strong>vibe evals</strong>.</p>

<p><strong>TOC application</strong>: the original constraint was the system prompt. The second constraint was user prompts, which was addressed by adding templates. The ongoing constraints alternate between tools and context.</p>

<h3 id="3-redacted">3. [Redacted]</h3>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-35-800-128e94084.webp 800w, /assets/images/generated/toc-ai-stanford/slide-35-1000-128e94084.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-35-1200-128e94084.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-35-1600-128e94084.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-35-2000-128e94084.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-35-800-103d2a2d7.png 800w, /assets/images/generated/toc-ai-stanford/slide-35-1000-103d2a2d7.png 1000w, /assets/images/generated/toc-ai-stanford/slide-35-1200-103d2a2d7.png 1200w, /assets/images/generated/toc-ai-stanford/slide-35-1600-103d2a2d7.png 1600w, /assets/images/generated/toc-ai-stanford/slide-35-2000-103d2a2d7.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-35-800-103d2a2d7.png" width="8000" height="4500" /></picture>

<p>A large enterprise AI implementation (details redacted for confidentiality).</p>

<p><strong>Eval approach</strong>: expert feedback on results (some structured, some unstructured) combined with user feedback (unstructured).</p>

<p><strong>TOC application</strong>: the original constraint was system prompts for specific subagents. Then it moved to tools. Now it’s back to context and system prompts. The constraint keeps moving – which is exactly what should happen.</p>

<hr />

<h2 id="a-key-takeaway">A Key Takeaway</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-37-800-f60b01255.webp 800w, /assets/images/generated/toc-ai-stanford/slide-37-1000-f60b01255.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-37-1200-f60b01255.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-37-1600-f60b01255.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-37-2000-f60b01255.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-37-800-80dbe0ff2.png 800w, /assets/images/generated/toc-ai-stanford/slide-37-1000-80dbe0ff2.png 1000w, /assets/images/generated/toc-ai-stanford/slide-37-1200-80dbe0ff2.png 1200w, /assets/images/generated/toc-ai-stanford/slide-37-1600-80dbe0ff2.png 1600w, /assets/images/generated/toc-ai-stanford/slide-37-2000-80dbe0ff2.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-37-800-80dbe0ff2.png" width="8000" height="4500" /></picture>

<p>The state of play today: <strong>the LLM is ~never the constraint.</strong> The frontier models are good enough to do just about anything. If we buy the Theory of Constraints and the idea there <em>is</em> a constraint in the first place bottlenecking quality, that means there are things <em>in your control</em> to get your product to the quality and consistency you want.</p>

<p>It is your job to map the system, build evals, find the current constraint, and fix it. Rinse and repeat.</p>

<hr />

<h2 id="bonus-tools-i-cant-live-without">Bonus: Tools I Can’t Live Without</h2>

<picture loading="lazy" class="article-image"><source srcset="/assets/images/generated/toc-ai-stanford/slide-39-800-fc7ac624d.webp 800w, /assets/images/generated/toc-ai-stanford/slide-39-1000-fc7ac624d.webp 1000w, /assets/images/generated/toc-ai-stanford/slide-39-1200-fc7ac624d.webp 1200w, /assets/images/generated/toc-ai-stanford/slide-39-1600-fc7ac624d.webp 1600w, /assets/images/generated/toc-ai-stanford/slide-39-2000-fc7ac624d.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/toc-ai-stanford/slide-39-800-d44901bd2.png 800w, /assets/images/generated/toc-ai-stanford/slide-39-1000-d44901bd2.png 1000w, /assets/images/generated/toc-ai-stanford/slide-39-1200-d44901bd2.png 1200w, /assets/images/generated/toc-ai-stanford/slide-39-1600-d44901bd2.png 1600w, /assets/images/generated/toc-ai-stanford/slide-39-2000-d44901bd2.png 2000w" type="image/png" /><img src="/assets/images/generated/toc-ai-stanford/slide-39-800-d44901bd2.png" width="8000" height="4500" /></picture>

<p>A snapshot of the tools I was finding most useful as of February 2026 (probably already out of date by the time you read this).</p>

<ul>
  <li><a href="https://ai.google.dev/gemini-api/docs/file-search">File Search API in Gemini</a> is great for quickly prototyping RAG applications — I don’t think there’s a faster way to get a good RAG pipeline up and running for something like the Lightwork knowledge base</li>
  <li><a href="https://trigger.dev">Trigger.dev</a> is amazing for long-running agentic pipelines and durable execution. Great developer experience.</li>
  <li><a href="https://braintrust.dev">Braintrust.dev</a> for automated eval pipelines and observability</li>
  <li><a href="https://github.com/zou-group/textgrad">TextGrad</a> is a very cool library from the Zou Group at Stanford for a sort of “backprop” of text gradients</li>
  <li><a href="https://platform.claude.com/docs/en/agent-sdk/overview">Claude Agent SDK</a> is my favorite harness for building agentic applications</li>
  <li><a href="https://github.com/EveryInc/compound-engineering-plugin">Compound Engineering from Every</a> is a great starting place for planning and building workflows inside of agentic code assistants. Instant level up on productivity if you aren’t using something similar already.</li>
</ul>

<hr />

<p>If you have questions or want to discuss any of this, feel free to reach out: <a href="mailto:andy@andybromberg.com">andy@andybromberg.com</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="/clean-electrolytes">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/clean-electrolytes">Someone, please make clean electrolytes (recipes included)</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/i-dont-write-code-anymore">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/i-dont-write-code-anymore">I don't write code anymore</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[A guest lecture at Stanford's CS 224G on applying the Theory of Constraints to build better LLM products. Slides and notes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-02-17-toc-ai-stanford.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-02-17-toc-ai-stanford.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Someone, please make clean electrolytes (recipes included)</title><link href="https://andybromberg.com/clean-electrolytes" rel="alternate" type="text/html" title="Someone, please make clean electrolytes (recipes included)" /><published>2026-01-16T06:00:00-07:00</published><updated>2026-01-16T06:00:00-07:00</updated><id>https://andybromberg.com/clean-electrolytes</id><content type="html" xml:base="https://andybromberg.com/clean-electrolytes"><![CDATA[<p>I just want someone to make squeaky-clean electrolyte powders.</p>

<p>No maltodextrin or fillers, no citric acid (did you know that most food-grade citric acid is produced by fermenting sugars with <em>Aspergillus niger</em>, also known as black mold?), no non-nutritive sweeteners, no “natural” or artificial flavors.</p>

<p>The best I’ve seen on the market is <a href="https://saltt.com/products/clean-slate-electrolyte-drink-mix?variant=45900257329290">Saltt Unflavored / Clean Slate</a>. It’s fine. But I feel like we could do even better, and it would be nice to have flavored versions (but again, without natural or artificial flavors or any sweeteners and instead using organic real ingredients).</p>

<p>So: my wife and I have been making our own and testing different formulas. We’ve settled on a few that we think are really good (three flavors, each with a subjective rating of 8.5/10 or above from both of us). Here’s just a part of the tracking spreadsheet!</p>

<picture loading="lazy"><source srcset="/assets/images/generated/clean-electrolytes/spreadsheet-800-f7f6ad1d7.webp 800w, /assets/images/generated/clean-electrolytes/spreadsheet-1000-f7f6ad1d7.webp 1000w, /assets/images/generated/clean-electrolytes/spreadsheet-1200-f7f6ad1d7.webp 1200w, /assets/images/generated/clean-electrolytes/spreadsheet-1600-f7f6ad1d7.webp 1600w, /assets/images/generated/clean-electrolytes/spreadsheet-2000-f7f6ad1d7.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/clean-electrolytes/spreadsheet-800-7f481332e.png 800w, /assets/images/generated/clean-electrolytes/spreadsheet-1000-7f481332e.png 1000w, /assets/images/generated/clean-electrolytes/spreadsheet-1200-7f481332e.png 1200w, /assets/images/generated/clean-electrolytes/spreadsheet-1600-7f481332e.png 1600w, /assets/images/generated/clean-electrolytes/spreadsheet-2000-7f481332e.png 2000w" type="image/png" /><img src="/assets/images/generated/clean-electrolytes/spreadsheet-800-7f481332e.png" width="2872" height="1266" /></picture>

<p>We’ve tested a lot of different ingredients:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/clean-electrolytes/ingredients-800-144057ff3.webp 800w, /assets/images/generated/clean-electrolytes/ingredients-1000-144057ff3.webp 1000w, /assets/images/generated/clean-electrolytes/ingredients-1200-144057ff3.webp 1200w, /assets/images/generated/clean-electrolytes/ingredients-1600-144057ff3.webp 1600w, /assets/images/generated/clean-electrolytes/ingredients-2000-144057ff3.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/clean-electrolytes/ingredients-800-b085dfbb7.png 800w, /assets/images/generated/clean-electrolytes/ingredients-1000-b085dfbb7.png 1000w, /assets/images/generated/clean-electrolytes/ingredients-1200-b085dfbb7.png 1200w, /assets/images/generated/clean-electrolytes/ingredients-1600-b085dfbb7.png 1600w, /assets/images/generated/clean-electrolytes/ingredients-2000-b085dfbb7.png 2000w" type="image/png" /><img src="/assets/images/generated/clean-electrolytes/ingredients-800-b085dfbb7.png" width="2866" height="1133" /></picture>

<p>I’m publishing these recipes desperately hoping that some enterprising company will choose to start making these. I think there’s a huge demand for ultra-clean supplements like this right now. Electrolytes are a big — and growing — category, and deserve a clean offering.</p>

<p>I will concede that at-scale manufacturing, packing, shelf life considerations, and other realities of commercial electrolyte production add nuance that our kitchen experiments don’t need to account for. Excipients and fillers make a lot of this easier. But I’m sure <em>someone</em> can figure it out…</p>

<hr />

<p>Our three favorites so far: chocolate, mixed berry, and wheatgrass. Recipes below. The process for making them at home is simple:</p>
<ul>
  <li>use a spice grinder to grind the salts finely</li>
  <li>combine everything and put it through a fine sieve</li>
  <li>then shake it up in a mason jar</li>
</ul>

<p>Each of these assumes a 10g serving (roughly two scoopfuls of one of my leftover creatine scoopers) to get roughly 1000mg sodium, 200-250mg potassium, 60-100mg magnesium, and a wide range of calcium (which I was not optimizing for).</p>

<p>In the calculators below you can change the serving count and it will update with how much you need of each ingredient.</p>

<h3 id="salted-chocolate">Salted Chocolate</h3>

<p>One 10g serving costs $0.52 in raw ingredients, has 26 kcal, 5g carbs; 1026mg sodium, 206mg potassium, 58mg magnesium, 4mg calcium.</p>

<div class="recipe-calc" id="recipe-calc-salted-chocolate">
  <div class="recipe-calc-header">
    <span class="recipe-calc-title">Salted Chocolate electrolyte serving calculator</span>
    <div class="recipe-calc-servings">
      <input type="number" id="servings-salted-chocolate" class="recipe-calc-input" value="10" min="1" max="100" />
      <label for="servings-salted-chocolate">servings</label>
      <span class="recipe-calc-cost">(<strong id="total-cost-salted-chocolate">$5.20</strong> in raw ingredients)</span>
    </div>
  </div>
  
  <div class="recipe-calc-ingredients">
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="6">6g</span>
      <a href="https://amzn.to/49EKu5l" class="recipe-calc-name" target="_blank" rel="noopener">Vera Salt</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="8">8g</span>
      <a href="https://amzn.to/4sL2bZN" class="recipe-calc-name" target="_blank" rel="noopener">Wilderness Poets Organic Coconut Water Powder</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="0.5">0.5g</span>
      <a href="https://amzn.to/4b4iIls" class="recipe-calc-name" target="_blank" rel="noopener">Selina Potassium Pink Cave Salt</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="0.4">0.4g</span>
      <a href="https://amzn.to/4pH9ZJi" class="recipe-calc-name" target="_blank" rel="noopener">Lifestream Natural Magnesium</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="10">10g</span>
      <a href="https://amzn.to/3Nh40xD" class="recipe-calc-name" target="_blank" rel="noopener">365 Organic Cacao Powder</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="1">1g</span>
      <a href="https://amzn.to/3ZlBptq" class="recipe-calc-name" target="_blank" rel="noopener">Simply Organic Cinnamon</a>
    </div>
    
  </div>
</div>

<script>
(function() {
  const calcEl = document.getElementById('recipe-calc-salted-chocolate');
  if (!calcEl) return;
  
  const servingsInput = calcEl.querySelector('#servings-salted-chocolate');
  const costEl = calcEl.querySelector('#total-cost-salted-chocolate');
  const amountEls = calcEl.querySelectorAll('.recipe-calc-amount');
  const costPerServing = 0.52;
  const totalGrams = 25.9;
  const servingSize = 10;
  // batch makes this many servings:
  const baseServings = totalGrams / servingSize;
  
  function update() {
    const servings = Math.max(1, parseInt(servingsInput.value) || 1);
    const scale = servings / baseServings;
    
    // Update amounts (scale from base recipe)
    amountEls.forEach(el => {
      const base = parseFloat(el.dataset.base);
      const scaled = base * scale;
      el.textContent = scaled.toFixed(2) + 'g';
    });
    
    // Update cost
    const totalCost = costPerServing * servings;
    costEl.textContent = '$' + totalCost.toFixed(2);
  }
  
  servingsInput.addEventListener('input', update);
  update();
})();
</script>

<style>
.recipe-calc {
  background: #faf9f7;
  border: 1px solid #e5e2db;
  border-radius: 6px;
  padding: 12px 16px;
  margin: 1rem 0;
  max-width: 420px;
  font-family: "Hanken Grotesk", -apple-system, sans-serif;
}

.recipe-calc-header {
  margin-bottom: 14px;
}

.recipe-calc-title {
  display: block;
  font-weight: 700;
  font-size: 1em;
  color: #1a1a1a;
  margin-bottom: 6px;
}

.recipe-calc-servings {
  display: flex;
  align-items: center;
  gap: 6px;
}

.recipe-calc-servings label {
  font-size: 0.85em;
  color: #555;
}

.recipe-calc-input {
  width: 50px;
  padding: 4px 6px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 0.9em;
  text-align: center;
  background: #fff;
}

.recipe-calc-input:focus {
  outline: none;
  border-color: #888;
}

.recipe-calc-cost {
  font-size: 0.85em;
  color: #555;
}

.recipe-calc-cost strong {
  color: #2d6a4f;
  font-weight: 700;
}

.recipe-calc-ingredients {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.recipe-calc-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.recipe-calc-amount {
  font-weight: 600;
  font-size: 0.9em;
  min-width: 55px;
  color: #1a1a1a;
  font-variant-numeric: tabular-nums;
}

.recipe-calc-name {
  font-size: 0.85em;
  color: #444;
  text-decoration: none;
  border-bottom: 1px dotted #aaa;
}

.recipe-calc-name:hover {
  color: #1a1a1a;
  border-bottom-color: #1a1a1a;
}
</style>

<h3 id="mixed-berry">Mixed Berry</h3>

<p>One 10g serving costs $0.72 in raw ingredients, has 36 kcal, 6g carbs; 994mg sodium, 202mg potassium, 67mg magnesium, 14mg calcium.</p>

<div class="recipe-calc" id="recipe-calc-mixed-berry">
  <div class="recipe-calc-header">
    <span class="recipe-calc-title">Mixed Berry electrolyte serving calculator</span>
    <div class="recipe-calc-servings">
      <input type="number" id="servings-mixed-berry" class="recipe-calc-input" value="10" min="1" max="100" />
      <label for="servings-mixed-berry">servings</label>
      <span class="recipe-calc-cost">(<strong id="total-cost-mixed-berry">$5.20</strong> in raw ingredients)</span>
    </div>
  </div>
  
  <div class="recipe-calc-ingredients">
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="4.4">4.4g</span>
      <a href="https://amzn.to/49EKu5l" class="recipe-calc-name" target="_blank" rel="noopener">Vera Salt</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="4">4g</span>
      <a href="https://amzn.to/4sL2bZN" class="recipe-calc-name" target="_blank" rel="noopener">Wilderness Poets Organic Coconut Water Powder</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="1.5">1.5g</span>
      <a href="https://amzn.to/4sIwTTv" class="recipe-calc-name" target="_blank" rel="noopener">Numami Blueberry Powder</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="10">10g</span>
      <a href="https://amzn.to/4sOGFUc" class="recipe-calc-name" target="_blank" rel="noopener">Microingredients Strawberry Powder</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="1">1g</span>
      <a href="https://amzn.to/4b4iIls" class="recipe-calc-name" target="_blank" rel="noopener">Selina Potassium Pink Cave Salt</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="0.4">0.4g</span>
      <a href="https://amzn.to/49AVYH7" class="recipe-calc-name" target="_blank" rel="noopener">Lifestream Natural Magnesium</a>
    </div>
    
  </div>
</div>

<script>
(function() {
  const calcEl = document.getElementById('recipe-calc-mixed-berry');
  if (!calcEl) return;
  
  const servingsInput = calcEl.querySelector('#servings-mixed-berry');
  const costEl = calcEl.querySelector('#total-cost-mixed-berry');
  const amountEls = calcEl.querySelectorAll('.recipe-calc-amount');
  const costPerServing = 0.72;
  const totalGrams = 21.3;
  const servingSize = 10;
  // batch makes this many servings:
  const baseServings = totalGrams / servingSize;
  
  function update() {
    const servings = Math.max(1, parseInt(servingsInput.value) || 1);
    const scale = servings / baseServings;
    
    // Update amounts (scale from base recipe)
    amountEls.forEach(el => {
      const base = parseFloat(el.dataset.base);
      const scaled = base * scale;
      el.textContent = scaled.toFixed(2) + 'g';
    });
    
    // Update cost
    const totalCost = costPerServing * servings;
    costEl.textContent = '$' + totalCost.toFixed(2);
  }
  
  servingsInput.addEventListener('input', update);
  update();
})();
</script>

<style>
.recipe-calc {
  background: #faf9f7;
  border: 1px solid #e5e2db;
  border-radius: 6px;
  padding: 12px 16px;
  margin: 1rem 0;
  max-width: 420px;
  font-family: "Hanken Grotesk", -apple-system, sans-serif;
}

.recipe-calc-header {
  margin-bottom: 14px;
}

.recipe-calc-title {
  display: block;
  font-weight: 700;
  font-size: 1em;
  color: #1a1a1a;
  margin-bottom: 6px;
}

.recipe-calc-servings {
  display: flex;
  align-items: center;
  gap: 6px;
}

.recipe-calc-servings label {
  font-size: 0.85em;
  color: #555;
}

.recipe-calc-input {
  width: 50px;
  padding: 4px 6px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 0.9em;
  text-align: center;
  background: #fff;
}

.recipe-calc-input:focus {
  outline: none;
  border-color: #888;
}

.recipe-calc-cost {
  font-size: 0.85em;
  color: #555;
}

.recipe-calc-cost strong {
  color: #2d6a4f;
  font-weight: 700;
}

.recipe-calc-ingredients {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.recipe-calc-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.recipe-calc-amount {
  font-weight: 600;
  font-size: 0.9em;
  min-width: 55px;
  color: #1a1a1a;
  font-variant-numeric: tabular-nums;
}

.recipe-calc-name {
  font-size: 0.85em;
  color: #444;
  text-decoration: none;
  border-bottom: 1px dotted #aaa;
}

.recipe-calc-name:hover {
  color: #1a1a1a;
  border-bottom-color: #1a1a1a;
}
</style>

<h3 id="wheatgrass">Wheatgrass</h3>

<p>One 10g serving costs $1.05 in raw ingredients, has 18kcal, 2g carbs; 1035mg sodium, 263 potassium, 102mg magnesium, 25mg calcium.</p>

<div class="recipe-calc" id="recipe-calc-wheatgrass">
  <div class="recipe-calc-header">
    <span class="recipe-calc-title">Wheatgrass electrolyte serving calculator</span>
    <div class="recipe-calc-servings">
      <input type="number" id="servings-wheatgrass" class="recipe-calc-input" value="10" min="1" max="100" />
      <label for="servings-wheatgrass">servings</label>
      <span class="recipe-calc-cost">(<strong id="total-cost-wheatgrass">$5.20</strong> in raw ingredients)</span>
    </div>
  </div>
  
  <div class="recipe-calc-ingredients">
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="5">5g</span>
      <a href="https://amzn.to/49EKu5l" class="recipe-calc-name" target="_blank" rel="noopener">Vera Salt</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="5">5g</span>
      <a href="https://amzn.to/4sL2bZN" class="recipe-calc-name" target="_blank" rel="noopener">Wilderness Poets Organic Coconut Water Powder</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="0.6">0.6g</span>
      <a href="https://amzn.to/45j7jui" class="recipe-calc-name" target="_blank" rel="noopener">Lifestream Natural Magnesium</a>
    </div>
    
    <div class="recipe-calc-row">
      <span class="recipe-calc-amount" data-base="10">10g</span>
      <a href="https://amzn.to/3Nh4dAV" class="recipe-calc-name" target="_blank" rel="noopener">Antler Farms Wheatgrass Powder</a>
    </div>
    
  </div>
</div>

<script>
(function() {
  const calcEl = document.getElementById('recipe-calc-wheatgrass');
  if (!calcEl) return;
  
  const servingsInput = calcEl.querySelector('#servings-wheatgrass');
  const costEl = calcEl.querySelector('#total-cost-wheatgrass');
  const amountEls = calcEl.querySelectorAll('.recipe-calc-amount');
  const costPerServing = 1.05;
  const totalGrams = 20.6;
  const servingSize = 10;
  // batch makes this many servings:
  const baseServings = totalGrams / servingSize;
  
  function update() {
    const servings = Math.max(1, parseInt(servingsInput.value) || 1);
    const scale = servings / baseServings;
    
    // Update amounts (scale from base recipe)
    amountEls.forEach(el => {
      const base = parseFloat(el.dataset.base);
      const scaled = base * scale;
      el.textContent = scaled.toFixed(2) + 'g';
    });
    
    // Update cost
    const totalCost = costPerServing * servings;
    costEl.textContent = '$' + totalCost.toFixed(2);
  }
  
  servingsInput.addEventListener('input', update);
  update();
})();
</script>

<style>
.recipe-calc {
  background: #faf9f7;
  border: 1px solid #e5e2db;
  border-radius: 6px;
  padding: 12px 16px;
  margin: 1rem 0;
  max-width: 420px;
  font-family: "Hanken Grotesk", -apple-system, sans-serif;
}

.recipe-calc-header {
  margin-bottom: 14px;
}

.recipe-calc-title {
  display: block;
  font-weight: 700;
  font-size: 1em;
  color: #1a1a1a;
  margin-bottom: 6px;
}

.recipe-calc-servings {
  display: flex;
  align-items: center;
  gap: 6px;
}

.recipe-calc-servings label {
  font-size: 0.85em;
  color: #555;
}

.recipe-calc-input {
  width: 50px;
  padding: 4px 6px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 0.9em;
  text-align: center;
  background: #fff;
}

.recipe-calc-input:focus {
  outline: none;
  border-color: #888;
}

.recipe-calc-cost {
  font-size: 0.85em;
  color: #555;
}

.recipe-calc-cost strong {
  color: #2d6a4f;
  font-weight: 700;
}

.recipe-calc-ingredients {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.recipe-calc-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.recipe-calc-amount {
  font-weight: 600;
  font-size: 0.9em;
  min-width: 55px;
  color: #1a1a1a;
  font-variant-numeric: tabular-nums;
}

.recipe-calc-name {
  font-size: 0.85em;
  color: #444;
  text-decoration: none;
  border-bottom: 1px dotted #aaa;
}

.recipe-calc-name:hover {
  color: #1a1a1a;
  border-bottom-color: #1a1a1a;
}
</style>

<hr />

<p>As best I can tell, all the ingredients above that can be organic are so, and I don’t believe any of them have fillers. The electrolytes themselves come from natural sources (sodium mostly from the salts, potassium from the cave salt and coconut water powder, magnesium from seawater extraction).</p>

<p>If you have any suggestions for better ingredients or approaches, please let me know!</p>

<hr />

<p><em>Note: Amazon affiliate links included in this post.</em></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/barcodes-freedom">
    
    <div class="post-nav-header"><i class="fa-solid fa-arrow-left post-nav-arrow"></i> Previous post</div>
    
    </a>
    
    
      <a href="/congruence/barcodes-freedom">Barcodes &amp; Freedom Phone</a>
    
  </div>
  <div class="next-post">
    <div class="post-nav-header">
        
        <a href="/toc-ai-stanford">
        
        Next post <i class="fa-solid fa-arrow-right post-nav-arrow"></i>
        
        </a>
        
    </div>
    
      <a href="/toc-ai-stanford">Stanford Lecture on Theory of Constraints for AI</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;Health&quot;, &quot;Startups&quot;]" /><summary type="html"><![CDATA[I just want someone to make squeaky-clean electrolyte powders. Here are some recipes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-01-16-clean-electrolytes.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-01-16-clean-electrolytes.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Barcodes &amp;amp; Freedom Phone</title><link href="https://andybromberg.com/congruence/barcodes-freedom" rel="alternate" type="text/html" title="Barcodes &amp;amp; Freedom Phone" /><published>2026-01-15T08:00:00-07:00</published><updated>2026-01-15T08:00:00-07:00</updated><id>https://andybromberg.com/congruence/congruence-7-barcodes-freedom</id><content type="html" xml:base="https://andybromberg.com/congruence/barcodes-freedom"><![CDATA[<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/vibe-checks">← Vibe checks &amp; 2 things</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 7/8</div>
<div class="series-nav-next"><span class="series-nav-coming">coming soon</span></div>
</div>

<p>My favorite things to build into Congruence are the ones that are so niche and specific to me that it might not even make sense for another app to exist with them. This post goes over two of those.</p>

<p>The first is “barcodes.” There are two places I frequent where I need to scan a barcode: my local Blue Bottle Coffee, where I scan a loyalty card, and my gym, where I scan an access card.</p>

<p>Previously, I had to manually go into the Blue Bottle app and navigate to the code page, and had to carry a physical barcode tag in my wallet for the gym.</p>

<p>But now, whenever I am physically nearing those places, Congruence detects my location and proactively sends me a push notification that I can press and hold to reveal the barcode. No need to go into an app or carry anything.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/barcode-expanded-800-16d81c5ab.webp 800w, /assets/images/generated/congruence/barcode-expanded-1000-16d81c5ab.webp 1000w, /assets/images/generated/congruence/barcode-expanded-1200-16d81c5ab.webp 1200w, /assets/images/generated/congruence/barcode-expanded-1600-16d81c5ab.webp 1600w, /assets/images/generated/congruence/barcode-expanded-2000-16d81c5ab.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/barcode-expanded-800-a18979095.png 800w, /assets/images/generated/congruence/barcode-expanded-1000-a18979095.png 1000w, /assets/images/generated/congruence/barcode-expanded-1200-a18979095.png 1200w, /assets/images/generated/congruence/barcode-expanded-1600-a18979095.png 1600w, /assets/images/generated/congruence/barcode-expanded-2000-a18979095.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/barcode-expanded-800-a18979095.png" width="3670" height="1468" /></picture>

<p>A simple pleasure for sure, but one I avail myself of multiple times per day.</p>

<hr />

<p>The second such feature is my Freedom Phone feature. I’ve <a href="/freedom-phones">written previously on Freedom Phones</a> in general, although I’ve now upgraded to a second iPhone for my Freedom Phone so that I can use Congruence on it (worth it!).</p>

<p>This has enabled a really nice flow: using Congruence to manage call forwarding. There is a button in Congruence that toggles whether my main phone is forwarding calls to my Freedom Phone or not. When I hit it, it dials the forwarding number (on Verizon: <code class="language-plaintext highlighter-rouge">*72 [Freedom Phone number]</code>) if forwarding is currently off, or the disable number (<code class="language-plaintext highlighter-rouge">*73</code>) if it is on.</p>

<p>This makes it really easy to toggle the state of call forwarding. But even better, there are a variety of indicators that tell me the current status. For example, on the home screens of my phones, there is a widget that says either <code class="language-plaintext highlighter-rouge">THIS</code> or <code class="language-plaintext highlighter-rouge">NOPE</code>:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/freedom-nope-800-7134e46e1.webp 800w, /assets/images/generated/congruence/freedom-nope-1000-7134e46e1.webp 1000w, /assets/images/generated/congruence/freedom-nope-1200-7134e46e1.webp 1200w, /assets/images/generated/congruence/freedom-nope-1600-7134e46e1.webp 1600w, /assets/images/generated/congruence/freedom-nope-2000-7134e46e1.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/freedom-nope-800-68f7be9d6.png 800w, /assets/images/generated/congruence/freedom-nope-1000-68f7be9d6.png 1000w, /assets/images/generated/congruence/freedom-nope-1200-68f7be9d6.png 1200w, /assets/images/generated/congruence/freedom-nope-1600-68f7be9d6.png 1600w, /assets/images/generated/congruence/freedom-nope-2000-68f7be9d6.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/freedom-nope-800-68f7be9d6.png" width="2060" height="824" /></picture>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/freedom-this-800-5f0e0f048.webp 800w, /assets/images/generated/congruence/freedom-this-1000-5f0e0f048.webp 1000w, /assets/images/generated/congruence/freedom-this-1200-5f0e0f048.webp 1200w, /assets/images/generated/congruence/freedom-this-1600-5f0e0f048.webp 1600w, /assets/images/generated/congruence/freedom-this-2000-5f0e0f048.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/freedom-this-800-42bdfdde5.png 800w, /assets/images/generated/congruence/freedom-this-1000-42bdfdde5.png 1000w, /assets/images/generated/congruence/freedom-this-1200-42bdfdde5.png 1200w, /assets/images/generated/congruence/freedom-this-1600-42bdfdde5.png 1600w, /assets/images/generated/congruence/freedom-this-2000-42bdfdde5.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/freedom-this-800-42bdfdde5.png" width="2110" height="844" /></picture>

<p>And in the app itself, the Freedom module button lights up if it is on, and accordingly says either <code class="language-plaintext highlighter-rouge">THIS</code> or <code class="language-plaintext highlighter-rouge">NOPE</code> depending on which phone it’s being used on.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/module-freedom-800-ce0e8d16d.webp 800w, /assets/images/generated/congruence/module-freedom-1000-ce0e8d16d.webp 1000w, /assets/images/generated/congruence/module-freedom-1200-ce0e8d16d.webp 1200w, /assets/images/generated/congruence/module-freedom-1600-ce0e8d16d.webp 1600w, /assets/images/generated/congruence/module-freedom-2000-ce0e8d16d.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/module-freedom-800-7bf74975c.png 800w, /assets/images/generated/congruence/module-freedom-1000-7bf74975c.png 1000w, /assets/images/generated/congruence/module-freedom-1200-7bf74975c.png 1200w, /assets/images/generated/congruence/module-freedom-1600-7bf74975c.png 1600w, /assets/images/generated/congruence/module-freedom-2000-7bf74975c.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/module-freedom-800-7bf74975c.png" width="2440" height="976" /></picture>

<p>I used to use Verizon’s conditional call forwarding, but that isn’t a great experience for callers since if I’m on the Freedom Phone, it has to ring all the way through my main phone. But now it just perfectly routes to whichever phone I am on, and I can keep track and change it easily.</p>

<p>(p.s. I still <em>strongly</em> recommend trying a Freedom Phone experiment… big quality-of-life upgrade.)</p>

<p>More posts coming soon on further Congruence features…</p>

<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/vibe-checks">← Vibe checks &amp; 2 things</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 7/8</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[Some really personal features for Congruence: loyalty barcodes and controlling my Freedom Phone.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-01-15-congruence-7-barcodes-freedom.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-01-15-congruence-7-barcodes-freedom.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Vibe checks &amp;amp; 2 things</title><link href="https://andybromberg.com/congruence/vibe-checks" rel="alternate" type="text/html" title="Vibe checks &amp;amp; 2 things" /><published>2026-01-14T09:05:00-07:00</published><updated>2026-01-14T09:05:00-07:00</updated><id>https://andybromberg.com/congruence/congruence-6-vibe-checks</id><content type="html" xml:base="https://andybromberg.com/congruence/vibe-checks"><![CDATA[<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/call-list">← Call List</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 6/8</div>
<div class="series-nav-next"><a href="/congruence/barcodes-freedom">Barcodes &amp; Freedom Phone →</a></div>
</div>

<p>I used to use an excellent app called <a href="https://howwefeel.org/">How We Feel</a>. A few times a day, it sends you a push notification asking to check in, and you just tap one of four quadrants: high or low energy and pleasant or unpleasant. You can add more data too.</p>

<p>But I of course wanted to pull this into Congruence. Now, four times a day, it sends me a notification which brings me to this screen:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/vibe-check-800-964d1398d.webp 800w, /assets/images/generated/congruence/vibe-check-1000-964d1398d.webp 1000w, /assets/images/generated/congruence/vibe-check-1200-964d1398d.webp 1200w, /assets/images/generated/congruence/vibe-check-1600-964d1398d.webp 1600w, /assets/images/generated/congruence/vibe-check-1650-964d1398d.webp 1650w" type="image/webp" /><source srcset="/assets/images/generated/congruence/vibe-check-800-66d11ad5a.png 800w, /assets/images/generated/congruence/vibe-check-1000-66d11ad5a.png 1000w, /assets/images/generated/congruence/vibe-check-1200-66d11ad5a.png 1200w, /assets/images/generated/congruence/vibe-check-1600-66d11ad5a.png 1600w, /assets/images/generated/congruence/vibe-check-1650-66d11ad5a.png 1650w" type="image/png" /><img src="/assets/images/generated/congruence/vibe-check-800-66d11ad5a.png" width="1650" height="660" /></picture>

<p>I select one of the boxes, optionally add an annotation (below) and I’m done.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/vibe-annotation-800-28c1d4c9f.webp 800w, /assets/images/generated/congruence/vibe-annotation-1000-28c1d4c9f.webp 1000w, /assets/images/generated/congruence/vibe-annotation-1200-28c1d4c9f.webp 1200w, /assets/images/generated/congruence/vibe-annotation-1600-28c1d4c9f.webp 1600w, /assets/images/generated/congruence/vibe-annotation-1800-28c1d4c9f.webp 1800w" type="image/webp" /><source srcset="/assets/images/generated/congruence/vibe-annotation-800-e826507bc.png 800w, /assets/images/generated/congruence/vibe-annotation-1000-e826507bc.png 1000w, /assets/images/generated/congruence/vibe-annotation-1200-e826507bc.png 1200w, /assets/images/generated/congruence/vibe-annotation-1600-e826507bc.png 1600w, /assets/images/generated/congruence/vibe-annotation-1800-e826507bc.png 1800w" type="image/png" /><img src="/assets/images/generated/congruence/vibe-annotation-800-e826507bc.png" width="1800" height="720" /></picture>

<p>The selected color shows up in the smiley face on my schedule (see the yellow one?):</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/modules-schedule-800-1d47aa5ca.webp 800w, /assets/images/generated/congruence/modules-schedule-1000-1d47aa5ca.webp 1000w, /assets/images/generated/congruence/modules-schedule-1200-1d47aa5ca.webp 1200w, /assets/images/generated/congruence/modules-schedule-1600-1d47aa5ca.webp 1600w, /assets/images/generated/congruence/modules-schedule-2000-1d47aa5ca.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/modules-schedule-800-71edc27d4.png 800w, /assets/images/generated/congruence/modules-schedule-1000-71edc27d4.png 1000w, /assets/images/generated/congruence/modules-schedule-1200-71edc27d4.png 1200w, /assets/images/generated/congruence/modules-schedule-1600-71edc27d4.png 1600w, /assets/images/generated/congruence/modules-schedule-2000-71edc27d4.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/modules-schedule-800-71edc27d4.png" width="2870" height="1146" /></picture>

<p>And I can go into the Streaks view and see the history:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/vibe-streaks-800-5e197d41c.webp 800w, /assets/images/generated/congruence/vibe-streaks-1000-5e197d41c.webp 1000w, /assets/images/generated/congruence/vibe-streaks-1200-5e197d41c.webp 1200w, /assets/images/generated/congruence/vibe-streaks-1600-5e197d41c.webp 1600w, /assets/images/generated/congruence/vibe-streaks-2000-5e197d41c.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/vibe-streaks-800-bb305e567.png 800w, /assets/images/generated/congruence/vibe-streaks-1000-bb305e567.png 1000w, /assets/images/generated/congruence/vibe-streaks-1200-bb305e567.png 1200w, /assets/images/generated/congruence/vibe-streaks-1600-bb305e567.png 1600w, /assets/images/generated/congruence/vibe-streaks-2000-bb305e567.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/vibe-streaks-800-bb305e567.png" width="4220" height="1688" /></picture>

<p>It’s really cool to visually skim how I was feeling month-over-month. When I look back I can see clear periods that corresponded with different life events.</p>

<p>How We Feel has a similar-ish view — you should get the app and try it out for a few months. I’ve found it really useful in retrospectively noticing trends and making decisions.</p>

<hr />

<p>Somewhat adjacent to this, I have a practice where every night I write down at least two things that made me happy or I really enjoyed that day. I’m just a few months away from ten years of doing this.</p>

<p>I also strongly recommend this practice. It’s very cool to look back at random days and see what I was excited about. And of course, it’s now in Congruence.</p>

<p>My evening checklist each day links to a “2 things” module, where I enter in my two or more items for that day. (This module is also Face ID protected.)</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/2-things-800-f9fac53af.webp 800w, /assets/images/generated/congruence/2-things-1000-f9fac53af.webp 1000w, /assets/images/generated/congruence/2-things-1200-f9fac53af.webp 1200w, /assets/images/generated/congruence/2-things-1600-f9fac53af.webp 1600w, /assets/images/generated/congruence/2-things-2000-f9fac53af.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/2-things-800-2beb2af2f.png 800w, /assets/images/generated/congruence/2-things-1000-2beb2af2f.png 1000w, /assets/images/generated/congruence/2-things-1200-2beb2af2f.png 1200w, /assets/images/generated/congruence/2-things-1600-2beb2af2f.png 1600w, /assets/images/generated/congruence/2-things-2000-2beb2af2f.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/2-things-800-2beb2af2f.png" width="4010" height="1604" /></picture>

<p>I can then look at dots for every day in the past, and tap on them and see what I recorded that day.</p>

<p>Next up: <a href="/congruence/barcodes-freedom">barcodes &amp; Freedom Phone</a>.</p>

<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/call-list">← Call List</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 6/8</div>
<div class="series-nav-next"><a href="/congruence/barcodes-freedom">Barcodes &amp; Freedom Phone →</a></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[Logging my vibe through the day, and saving my favorite things.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-01-14-congruence-6-vibe-checks.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-01-14-congruence-6-vibe-checks.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Call List</title><link href="https://andybromberg.com/congruence/call-list" rel="alternate" type="text/html" title="Call List" /><published>2026-01-13T09:05:00-07:00</published><updated>2026-01-13T09:05:00-07:00</updated><id>https://andybromberg.com/congruence/congruence-5-call-list</id><content type="html" xml:base="https://andybromberg.com/congruence/call-list"><![CDATA[<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/meditation">← Meditation</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 5/8</div>
<div class="series-nav-next"><a href="/congruence/vibe-checks">Vibe checks &amp; 2 things →</a></div>
</div>

<p>As discussed in <a href="/just-call-me">“Just Call Me”</a>, I mostly operate on an ad-hoc phone call basis. I want a list of people I owe calls to, and Congruence offers that to me.</p>

<p>Sam Lessin has built a much more featureful (and open to the public!) version of this with <a href="https://calllist.app/">calllist.app</a> — check it out! The site also has a breakdown of why this approach is great &amp; productive.</p>

<p>My version is simple: add people either from my contacts list or manually. Re-order them at will. Enter any notes to remember. Hit the call or text (or contact card) buttons. Talk to them.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/calllist-800-b12bec63e.webp 800w, /assets/images/generated/congruence/calllist-1000-b12bec63e.webp 1000w, /assets/images/generated/congruence/calllist-1200-b12bec63e.webp 1200w, /assets/images/generated/congruence/calllist-1600-b12bec63e.webp 1600w, /assets/images/generated/congruence/calllist-2000-b12bec63e.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/calllist-800-6ff24aa40.png 800w, /assets/images/generated/congruence/calllist-1000-6ff24aa40.png 1000w, /assets/images/generated/congruence/calllist-1200-6ff24aa40.png 1200w, /assets/images/generated/congruence/calllist-1600-6ff24aa40.png 1600w, /assets/images/generated/congruence/calllist-2000-6ff24aa40.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/calllist-800-6ff24aa40.png" width="4060" height="1624" /></picture>

<p>Whenever my schedule has me planned to go out for a walk, I get a push notification that brings me right into the Call List module and I start working through it.</p>

<p>Once I hit someone, I just swipe them off and I’m good to go. This module is really simple, but it’s all I need (and the affordances of tap-to-call/text and push notification reminders are worthwhile over just putting these in a different notes place — plus then it doesn’t clutter up my notes, which I can regularly get to zero).</p>

<p>Next up: <a href="/congruence/vibe-checks">vibe checks &amp; 2 things</a>.</p>

<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/meditation">← Meditation</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 5/8</div>
<div class="series-nav-next"><a href="/congruence/vibe-checks">Vibe checks &amp; 2 things →</a></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[Keeping track of who to call and what we need to talk about.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-01-13-congruence-5-call-list.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-01-13-congruence-5-call-list.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Meditation</title><link href="https://andybromberg.com/congruence/meditation" rel="alternate" type="text/html" title="Meditation" /><published>2026-01-12T10:10:00-07:00</published><updated>2026-01-12T10:10:00-07:00</updated><id>https://andybromberg.com/congruence/congruence-4-notes-meditation</id><content type="html" xml:base="https://andybromberg.com/congruence/meditation"><![CDATA[<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/notes-ai">← Notes &amp; AI</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 4/8</div>
<div class="series-nav-next"><a href="/congruence/call-list">Call List →</a></div>
</div>

<p>I wanted a very simple meditation timer and also a guided breathwork experience. So those are part of the app too.</p>

<p>The meditation timer lets you pick duration, beginning/end chime sounds, and soundtrack (if you want one). I found some really great permissively-licensed tracks like <a href="https://freesound.org/people/Glen_Hoban/sounds/439446/">these from Glen Hoban</a>.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/meditation-timer-800-9ca22ddf2.webp 800w, /assets/images/generated/congruence/meditation-timer-1000-9ca22ddf2.webp 1000w, /assets/images/generated/congruence/meditation-timer-1200-9ca22ddf2.webp 1200w, /assets/images/generated/congruence/meditation-timer-1600-9ca22ddf2.webp 1600w, /assets/images/generated/congruence/meditation-timer-1650-9ca22ddf2.webp 1650w" type="image/webp" /><source srcset="/assets/images/generated/congruence/meditation-timer-800-ba664617d.png 800w, /assets/images/generated/congruence/meditation-timer-1000-ba664617d.png 1000w, /assets/images/generated/congruence/meditation-timer-1200-ba664617d.png 1200w, /assets/images/generated/congruence/meditation-timer-1600-ba664617d.png 1600w, /assets/images/generated/congruence/meditation-timer-1650-ba664617d.png 1650w" type="image/png" /><img src="/assets/images/generated/congruence/meditation-timer-800-ba664617d.png" width="1650" height="660" /></picture>

<p>You hold down the button and it shrinks to nothing, while haptic vibrating “heartbeats” slow down from 100 BPM to 40 BPM (bumbum… bumbum… … bumbum… … … bumbum). Then the chime goes off and you’re in the experience.</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/meditation-progress-800-a10773ef1.webp 800w, /assets/images/generated/congruence/meditation-progress-1000-a10773ef1.webp 1000w, /assets/images/generated/congruence/meditation-progress-1200-a10773ef1.webp 1200w, /assets/images/generated/congruence/meditation-progress-1600-a10773ef1.webp 1600w, /assets/images/generated/congruence/meditation-progress-2000-a10773ef1.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/meditation-progress-800-3fdbc87a2.png 800w, /assets/images/generated/congruence/meditation-progress-1000-3fdbc87a2.png 1000w, /assets/images/generated/congruence/meditation-progress-1200-3fdbc87a2.png 1200w, /assets/images/generated/congruence/meditation-progress-1600-3fdbc87a2.png 1600w, /assets/images/generated/congruence/meditation-progress-2000-3fdbc87a2.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/meditation-progress-800-3fdbc87a2.png" width="3710" height="1484" /></picture>

<p>Once the time is up, a chime goes off, the timer keeps counting, and whenever you’re done, you hold to end it (and the haptic heartbeat ramps back up).</p>

<p>You can add a reflection at the end of the meditation if you want, and then it’s done.</p>

<hr />

<p>There’s also a breathwork module with a variety of pre-built routines:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/breathwork-menu-800-0929c1999.webp 800w, /assets/images/generated/congruence/breathwork-menu-1000-0929c1999.webp 1000w, /assets/images/generated/congruence/breathwork-menu-1200-0929c1999.webp 1200w, /assets/images/generated/congruence/breathwork-menu-1600-0929c1999.webp 1600w, /assets/images/generated/congruence/breathwork-menu-2000-0929c1999.webp 2000w" type="image/webp" /><source srcset="/assets/images/generated/congruence/breathwork-menu-800-25a0db35f.png 800w, /assets/images/generated/congruence/breathwork-menu-1000-25a0db35f.png 1000w, /assets/images/generated/congruence/breathwork-menu-1200-25a0db35f.png 1200w, /assets/images/generated/congruence/breathwork-menu-1600-25a0db35f.png 1600w, /assets/images/generated/congruence/breathwork-menu-2000-25a0db35f.png 2000w" type="image/png" /><img src="/assets/images/generated/congruence/breathwork-menu-800-25a0db35f.png" width="3880" height="1552" /></picture>

<p>These routines guide you fully through the sequence with audio and visual cues (text, a pulsing circle). A few screenshots from a Buteyko sequence:</p>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/buteyko-1-800-bb8a963cf.webp 800w, /assets/images/generated/congruence/buteyko-1-1000-bb8a963cf.webp 1000w, /assets/images/generated/congruence/buteyko-1-1200-bb8a963cf.webp 1200w, /assets/images/generated/congruence/buteyko-1-1560-bb8a963cf.webp 1560w" type="image/webp" /><source srcset="/assets/images/generated/congruence/buteyko-1-800-2a734d6b5.png 800w, /assets/images/generated/congruence/buteyko-1-1000-2a734d6b5.png 1000w, /assets/images/generated/congruence/buteyko-1-1200-2a734d6b5.png 1200w, /assets/images/generated/congruence/buteyko-1-1560-2a734d6b5.png 1560w" type="image/png" /><img src="/assets/images/generated/congruence/buteyko-1-800-2a734d6b5.png" width="1560" height="624" /></picture>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/buteyko-2-800-87a171777.webp 800w, /assets/images/generated/congruence/buteyko-2-1000-87a171777.webp 1000w, /assets/images/generated/congruence/buteyko-2-1200-87a171777.webp 1200w, /assets/images/generated/congruence/buteyko-2-1600-87a171777.webp 1600w, /assets/images/generated/congruence/buteyko-2-1650-87a171777.webp 1650w" type="image/webp" /><source srcset="/assets/images/generated/congruence/buteyko-2-800-af3f6ee06.png 800w, /assets/images/generated/congruence/buteyko-2-1000-af3f6ee06.png 1000w, /assets/images/generated/congruence/buteyko-2-1200-af3f6ee06.png 1200w, /assets/images/generated/congruence/buteyko-2-1600-af3f6ee06.png 1600w, /assets/images/generated/congruence/buteyko-2-1650-af3f6ee06.png 1650w" type="image/png" /><img src="/assets/images/generated/congruence/buteyko-2-800-af3f6ee06.png" width="1650" height="660" /></picture>

<picture loading="lazy"><source srcset="/assets/images/generated/congruence/buteyko-3-800-0a38430b6.webp 800w, /assets/images/generated/congruence/buteyko-3-1000-0a38430b6.webp 1000w, /assets/images/generated/congruence/buteyko-3-1200-0a38430b6.webp 1200w, /assets/images/generated/congruence/buteyko-3-1600-0a38430b6.webp 1600w, /assets/images/generated/congruence/buteyko-3-1650-0a38430b6.webp 1650w" type="image/webp" /><source srcset="/assets/images/generated/congruence/buteyko-3-800-f7e88e21b.png 800w, /assets/images/generated/congruence/buteyko-3-1000-f7e88e21b.png 1000w, /assets/images/generated/congruence/buteyko-3-1200-f7e88e21b.png 1200w, /assets/images/generated/congruence/buteyko-3-1600-f7e88e21b.png 1600w, /assets/images/generated/congruence/buteyko-3-1650-f7e88e21b.png 1650w" type="image/png" /><img src="/assets/images/generated/congruence/buteyko-3-800-f7e88e21b.png" width="1650" height="660" /></picture>

<p>I built out a nice schema that allows for a variety of segments — ones with user-controlled length (“tap to continue”); pre-programmed durations of inhale, hold full, exhale, hold empty; warmups and cooldowns; etc.</p>

<p>I wish all breathwork &amp; meditation apps were this simple, clear, and customizable… but that’s tough to monetize, of course. Another vote for home-cooked software!</p>

<p>Next up: <a href="/congruence/call-list">Call List</a>.</p>

<div class="series-nav">
<div class="series-nav-prev"><a href="/congruence/notes-ai">← Notes &amp; AI</a></div>
<div class="series-nav-center">Congruence: a very personal app · section 4/8</div>
<div class="series-nav-next"><a href="/congruence/call-list">Call List →</a></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[The simple meditation timer and breathwork guides I wish I could find on the App Store.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://andybromberg.com/assets/images/og/2026-01-12-congruence-4-notes-meditation.png" /><media:content medium="image" url="https://andybromberg.com/assets/images/og/2026-01-12-congruence-4-notes-meditation.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>