<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>The Dan MacKinlay stable of variably-well-consider’d enterprises</title>
<link>https://danmackinlay.name/</link>
<atom:link href="https://danmackinlay.name/index.xml" rel="self" type="application/rss+xml"/>
<description></description>
<image>
<url>https://danmackinlay.name/generated_thumbnails/site_previews/images/punch_index.thumbnail.avif</url>
<title>The Dan MacKinlay stable of variably-well-consider’d enterprises</title>
<link>https://danmackinlay.name/</link>
</image>
<generator>quarto-1.10.19</generator>
<lastBuildDate>Wed, 16 Sep 2026 16:36:13 GMT</lastBuildDate>
<item>
  <title>Algorithmic improvement</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/algorithmic_improvement.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-whipit2gofaster" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-whipit2gofaster-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/whipit2gofaster_RP-P-BI-2221.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/whipit2gofaster_RP-P-BI-2221.avif" id="fig-whipit2gofaster" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-whipit2gofaster-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Huh. I thought I had created a notebook about this at some point, starting from <span class="citation" data-cites="Dongarra2000Guest">Dongarra and Sullivan (2000)</span> and moving on to <span class="citation" data-cites="Grace2013Algorithmic">Grace (2013)</span>. Apparently I imagined it.</p>
<p>In AI forecasting, people ask all the time how much progress comes from better algorithms versus more compute. That question has always seemed underspecified to me. Here is the simplest version of it I can make work.</p>
<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>After this human -written intro, the rest is machine slop. Written about my ideas, at my instruction, but informed by papers I have not read, in a turgid and horrible style. You have been warned.</p>
</div>
</div>
</div>
<p>“We got faster at task X” can mean three different things:</p>
<ol type="1">
<li><em>Hardware</em>: the same operations run faster, or cheaper.</li>
<li><em>Algorithms</em>: the same answer needs fewer operations.</li>
<li><em>Loosening</em>: we changed what counts as an answer, and the new kind of answer is cheaper to produce.</li>
</ol>
<p>The first two get most of the attention. I suspect a large share of the big wins come from the third, and that most measurements book it as the second.</p>
<section id="decomposition" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="decomposition"><span class="header-section-number">1</span> The decomposition</h2>
<p>Write <img src="https://latex.codecogs.com/png.latex?T"> for the time (or dollars) needed to reach target <img src="https://latex.codecogs.com/png.latex?q">, <img src="https://latex.codecogs.com/png.latex?W_a(q)"> for the operations algorithm <img src="https://latex.codecogs.com/png.latex?a"> needs to reach it, and <img src="https://latex.codecogs.com/png.latex?S"> for the operations per second (or per dollar) our hardware delivers. Then <img src="https://latex.codecogs.com/png.latex?T%20=%20W_a(q)/S">.</p>
<p>Moving from an old setup <img src="https://latex.codecogs.com/png.latex?(a_0,%20S_0,%20q_0)"> to a new one <img src="https://latex.codecogs.com/png.latex?(a_1,%20S_1,%20q_1)">, the speedup telescopes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BT_0%7D%7BT_1%7D%0A=%20%5Cunderbrace%7B%5Cfrac%7BS_1%7D%7BS_0%7D%7D_%7B%5Ctext%7Bhardware%7D%7D%0A%5Ctimes%20%5Cunderbrace%7B%5Cfrac%7BW_%7Ba_0%7D(q_0)%7D%7BW_%7Ba_1%7D(q_0)%7D%7D_%7B%5Ctext%7Balgorithm%7D%7D%0A%5Ctimes%20%5Cunderbrace%7B%5Cfrac%7BW_%7Ba_1%7D(q_0)%7D%7BW_%7Ba_1%7D(q_1)%7D%7D_%7B%5Ctext%7Bloosening%7D%7D%0A"></p>
<p>This is an identity, so it always holds. In logs, the three terms add, and “what share of the progress is algorithmic?” looks like it has an answer. It only has one if the identity is also a production function, in which the factors do not depend on one another. Even as accounting, it has gaps. One is that the split depends on the order in which we apply the changes. Another is that some of the factors can be infinite. If the old method cannot reach the new target at any budget, or the new method only makes sense for a target the old problem never posed, the ratio is infinite or undefined. Infeasible-to-feasible transitions are arguably where most of the value lies, and a cost ratio cannot see them.</p>
</section>
<section id="loosening-example" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="loosening-example"><span class="header-section-number">2</span> Example</h2>
<p>Gaussian-process regression makes a handy worked example. The task is posterior predictive inference. With <img src="https://latex.codecogs.com/png.latex?n"> training points, kernel matrix <img src="https://latex.codecogs.com/png.latex?K"> and noise <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2">, the predictive mean at a test point is <img src="https://latex.codecogs.com/png.latex?k_*%5E%5Ctop%20(K%20+%20%5Csigma%5E2%20I)%5E%7B-1%7D%20y"> and the predictive variance is <img src="https://latex.codecogs.com/png.latex?k_%7B**%7D%20-%20k_*%5E%5Ctop%20(K%20+%20%5Csigma%5E2%20I)%5E%7B-1%7D%20k_*">; fitting the hyperparameters also needs <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cdet(K%20+%20%5Csigma%5E2%20I)">. The cost of a prediction is dominated by an <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> linear solve with respect to a symmetric matrix.</p>
<ul>
<li>Exact, naive: transcribe the equations, form <img src="https://latex.codecogs.com/png.latex?(K%20+%20%5Csigma%5E2%20I)%5E%7B-1%7D"> explicitly and multiply. About <img src="https://latex.codecogs.com/png.latex?2n%5E3"> operations and poor conditioning, i.e., numerically unstable. This is the cost of the base problem as written.</li>
<li>Exact, competent: Cholesky <img src="https://latex.codecogs.com/png.latex?LL%5E%5Ctop%20=%20K%20+%20%5Csigma%5E2%20I"> at <img src="https://latex.codecogs.com/png.latex?n%5E3/3"> operations, triangular solves at <img src="https://latex.codecogs.com/png.latex?n%5E2"> per right-hand side, <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cdet"> read off the diagonal of <img src="https://latex.codecogs.com/png.latex?L"> for free. Same answer in principle but due to floating point shenanigans, much faster and more stable. Still <img src="https://latex.codecogs.com/png.latex?O(n%5E3)"> but less awful. A pure algorithmic gain: same answer, fewer operations.</li>
<li>Same answer to tolerance <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">: the same posterior but we allow some error. Conjugate gradients for the solve and Lanczos methods for <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cdet"> cost <img src="https://latex.codecogs.com/png.latex?O(n%5E2)"> per iteration and stop at <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">; randomized sketches <span class="citation" data-cites="Halko2011Finding">(Halko, Martinsson, and Tropp 2011)</span> give an <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">-approximate answer with a probabilistic guarantee. Admits more data at a known loss of precision, so now we have a tradeoff.</li>
<li>Same predictive risk, different estimator: a variational posterior. Inducing-point methods summarize the data by <img src="https://latex.codecogs.com/png.latex?m%20%5Cll%20n"> pseudo-inputs at <img src="https://latex.codecogs.com/png.latex?O(nm%5E2)"> cost. The output is no longer the GP posterior <em>per se</em>; it is a different distribution we hope has about the same predictive risk, which in practice we assess by scoring on a held-out set.</li>
<li>Same held-out score, different model class: a neural network that scores as well on the held-out set. There is no <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> system to solve any more, so the cost of inference is <img src="https://latex.codecogs.com/png.latex?O(n)">. There is also no posterior <em>per se</em>: the network returns a point prediction, or an uncertainty estimate of some other kind.</li>
</ul>
<p>Only the second step leaves the target alone. Each later step is a looser contract about what counts as success: the output to tolerance, then the risk of the estimator, then a benchmark score, and at the far end, the task the benchmark was meant to stand in for.</p>
<section id="order-dependence" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="order-dependence"><span class="header-section-number">2.1</span> Order dependence</h3>
<p>Once the target moves, the split between algorithm and loosening depends on the order in which we take the two changes. Take the step from Cholesky to conjugate gradients, on fixed hardware. The decomposition identity changes the algorithm first, at the old target, and then loosens the target:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BT_%7B%5Ctext%7BChol%7D%7D(%5Ctext%7Bexact%7D)%7D%7BT_%7B%5Ctext%7BCG%7D%7D(%5Cepsilon)%7D%0A=%20%5Cunderbrace%7B%5Cfrac%7BW_%7B%5Ctext%7BChol%7D%7D(%5Ctext%7Bexact%7D)%7D%7BW_%7B%5Ctext%7BCG%7D%7D(%5Ctext%7Bexact%7D)%7D%7D_%7B%5Ctext%7Balgorithm%7D%7D%0A%5Ctimes%20%5Cunderbrace%7B%5Cfrac%7BW_%7B%5Ctext%7BCG%7D%7D(%5Ctext%7Bexact%7D)%7D%7BW_%7B%5Ctext%7BCG%7D%7D(%5Cepsilon)%7D%7D_%7B%5Ctext%7Bloosening%7D%7D%0A"></p>
<p>To run to exact convergence, conjugate gradients needs <img src="https://latex.codecogs.com/png.latex?n"> iterations at about <img src="https://latex.codecogs.com/png.latex?2n%5E2"> operations each, so <img src="https://latex.codecogs.com/png.latex?W_%7B%5Ctext%7BCG%7D%7D(%5Ctext%7Bexact%7D)%20%5Capprox%202n%5E3">, which is worse than Cholesky’s <img src="https://latex.codecogs.com/png.latex?n%5E3/3">. The algorithm factor is about <img src="https://latex.codecogs.com/png.latex?1/6">, and the loosening factor deserves all the credit and then some.</p>
<p>Now loosen the target first, keeping the old algorithm, and then change the algorithm at the new target:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BT_%7B%5Ctext%7BChol%7D%7D(%5Ctext%7Bexact%7D)%7D%7BT_%7B%5Ctext%7BCG%7D%7D(%5Cepsilon)%7D%0A=%20%5Cunderbrace%7B%5Cfrac%7BW_%7B%5Ctext%7BChol%7D%7D(%5Ctext%7Bexact%7D)%7D%7BW_%7B%5Ctext%7BChol%7D%7D(%5Cepsilon)%7D%7D_%7B%5Ctext%7Bloosening%7D%7D%0A%5Ctimes%20%5Cunderbrace%7B%5Cfrac%7BW_%7B%5Ctext%7BChol%7D%7D(%5Cepsilon)%7D%7BW_%7B%5Ctext%7BCG%7D%7D(%5Cepsilon)%7D%7D_%7B%5Ctext%7Balgorithm%7D%7D%0A"></p>
<p>Cholesky has no way to stop early, so <img src="https://latex.codecogs.com/png.latex?W_%7B%5Ctext%7BChol%7D%7D(%5Cepsilon)%20=%20W_%7B%5Ctext%7BChol%7D%7D(%5Ctext%7Bexact%7D)">. The loosening factor is 1, and the algorithm factor gets all the credit.</p>
<p>Same total speedup, booked entirely to loosening one way and entirely to the algorithm the other. The two orderings agree only if cost separates as <img src="https://latex.codecogs.com/png.latex?W_a(q)%20=%20f(a)%5C,g(q)">, so that how much an algorithm saves does not depend on the target. Here it is the opposite: conjugate gradients is only worth having because we loosened the target. <span class="citation" data-cites="Erdil2023Algorithmic">Erdil and Besiroglu (2023)</span> and <span class="citation" data-cites="Ho2024Algorithmic">Ho et al. (2024)</span> hit the same problem splitting progress between compute, data, and time, and average each factor’s contribution over every ordering (Shapley values). That gives a single number, but the interaction is still there, divided up by convention.</p>
<p>Loosening also works on the input side by shrinking the set of instances the method must handle from the worst case to the ones that arise. SAT is NP-complete, and yet industrial instances with millions of variables solve in seconds; the success of clause learning is a fact about which instances turn up, not about the complexity class.</p>
<p>Grammar induction is my favourite case where both sides moved at once. Learning a grammar from positive examples is provably impossible in the limit <span class="citation" data-cites="Gold1967Language">(Gold 1967)</span>, and yet language models learn language. They do it by not producing a grammar (output side) and by only needing to handle the languages humans speak (input side); <span class="citation" data-cites="Kleinberg2024Language">Kleinberg and Mullainathan (2024)</span> make the substitution into a theorem, showing that <em>generation</em> in the limit is achievable where identification is not.</p>
<p>The property of loosening I care about is that it is a step, not a rate. Once we discover that a statistical estimate need not be more accurate than the noise in the data, we cannot discover it again.</p>
</section>
</section>
<section id="production-function" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="production-function"><span class="header-section-number">3</span> As a production function</h2>
<p>The multiplicative form treats hardware and algorithms as inputs to a <a href="../notebook/production_functions.html">production function</a> in which only their product matters, the “effective compute” <img src="https://latex.codecogs.com/png.latex?A%20%5Ccdot%20C"> of the scaling-law literature. Its isoquants are hyperbolae, so it is Cobb-Douglas between the two, with elasticity of substitution <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%201"> and no cross terms; in translog terms it is the first-order expansion with every <img src="https://latex.codecogs.com/png.latex?b_%7Bij%7D%20=%200">. The hardware lottery <span class="citation" data-cites="Hooker2020Hardware">(Hooker 2020)</span> says the cross terms are not zero.</p>
<p>A cost function makes the structure easier to see. Hardware does not have <em>a</em> speed; it has a price for each kind of operation: FLOPs at each precision, bytes moved from memory, synchronizations, mispredicted branches. Write <img src="https://latex.codecogs.com/png.latex?w(h)"> for that price vector on hardware <img src="https://latex.codecogs.com/png.latex?h">. An algorithm <img src="https://latex.codecogs.com/png.latex?a"> that reaches target <img src="https://latex.codecogs.com/png.latex?q"> uses a bundle <img src="https://latex.codecogs.com/png.latex?x(a,%20q)"> of those operations in fixed proportions, like a Leontief technique in activity analysis. The cost of reaching <img src="https://latex.codecogs.com/png.latex?q"> is then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC(h,%20q)%20=%20%5Cmin_%7Ba%20%5Cin%20%5Cmathcal%7BA%7D(q)%7D%20w(h)%20%5Ccdot%20x(a,%20q),%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D(q)"> is the set of algorithms we know that reach <img src="https://latex.codecogs.com/png.latex?q">. Algorithmic progress adds techniques to <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D">; hardware progress changes <img src="https://latex.codecogs.com/png.latex?w">.</p>
<p><em>Uniform hardware gains are exactly multiplicative.</em> A cost function is homogeneous of degree one in prices, <img src="https://latex.codecogs.com/png.latex?C(%5Clambda%20w,%20q)%20=%20%5Clambda%20C(w,%20q)">, whatever the algorithm. If every operation got cheaper by the same factor, the hardware factor would be well defined and independent of the algorithm. Clock-speed scaling up to about 2005 was the nearest thing we had, and even then memory latency fell behind.</p>
<p><em>Small uneven gains are multiplicative at the margin.</em> By the envelope theorem, the first-order change in cost from a small change in prices is <img src="https://latex.codecogs.com/png.latex?dC%20=%20x%5E*%20%5Ccdot%20dw">, evaluated at the algorithm we already use; switching algorithms gains only at second order. So locally the multiplicative form is fine, and that is the sense in which hardware and algorithms are separable.</p>
<p><em>Large uneven gains are not.</em> When relative prices tilt far enough, the cheapest algorithm changes. That is the hardware lottery restated as a change in relative prices. Since about 2005 tilting has been the norm: GPU FLOP/s double every 2.3 years against memory bandwidth every 4.1 <span class="citation" data-cites="Hobbhahn2023Trends">(Hobbhahn, Heim, and Aydos 2023)</span>, and SIMD units and GPUs made regular, dense, branch-free operations far cheaper than irregular ones. FlashAttention <span class="citation" data-cites="Dao2022FlashAttention">(Dao et al. 2022)</span> is a tidy example. It computes exact attention with <em>more</em> FLOPs than the standard implementation, and runs faster because it moves less data between memory levels. Counted in FLOPs it is negative algorithmic progress, and it pays only because of the price vector. The gain from switching algorithms after a large price change belongs to neither factor; it is the interaction term, and the order in which we price the factors decides which one gets it. <span class="citation" data-cites="Fichte2023Time">Fichte, Hecher, and Szeider (2023)</span> measured it directly: SAT solvers from 2003 built on clause learning solve 1.7 to 2.8 times as many instances on 2019 hardware as on 1999 hardware, while the older solvers barely gain.</p>
<p>There is a possible rescue at the aggregate level. <span class="citation" data-cites="Houthakker1955Pareto">Houthakker (1955)</span> showed that aggregating many fixed-coefficient activities, with capacity Pareto-distributed over their input coefficients, gives a Cobb-Douglas industry production function, and <span class="citation" data-cites="Jones2005Shape">Jones (2005)</span> turned this into a theory of ideas: if techniques are discovered as draws from a Pareto distribution, the envelope over all discovered techniques, the global production function, is Cobb-Douglas, even though each technique has fixed proportions and the short-run production function substitutes poorly. Mapped onto this problem, the global cost function would be Cobb-Douglas in operation prices. An uneven hardware gain would then enter as a cost-share-weighted product of the per-operation price falls, and new algorithms would shift the constant in front. The multiplicative form would survive as a description of the frontier over many algorithm–hardware pairings, with kinks, even though no single pairing behaves that way. That would reconcile smooth trend lines with lumpy individual cases. <span class="citation" data-cites="Sherry2021How">Sherry and Thompson (2021)</span>’s bimodal gains are at least heavy-tailed; whether they are Pareto enough for this to hold, I do not know.</p>
<p>The target <img src="https://latex.codecogs.com/png.latex?q"> fits the frame worse, because it is not an input.</p>
<ul>
<li>Loosening changes the menu. <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BA%7D(q)"> depends on <img src="https://latex.codecogs.com/png.latex?q">: conjugate gradients is only an option once we accept a tolerance, and the neural network only once a held-out score is the criterion. The main effect of loosening is to enlarge the feasible set of techniques, and the “algorithm” factor is then measured over techniques that were not available before.</li>
<li>Loosening interacts with hardware. Lower precision only pays where low-precision operations are cheap, and randomized linear algebra wins partly because it turns a sequential factorization into large dense matrix products. So there are three-way interactions as well.</li>
<li>Loosening may not be a cost reduction at all. The output has several dimensions (accuracy, guarantees, calibrated uncertainty, which instances are covered), so we are on a multi-output transformation frontier rather than a production function. Whether a cheaper output counts as the same good depends on what the user wanted, that is, on the task. For someone who wants a held-out score, the neural network and the GP are the same good; for someone who needs a calibrated posterior variance, they are not. This is the quality-adjustment problem from price indices, as in the computing price series of <span class="citation" data-cites="Nordhaus2007Two">Nordhaus (2007)</span>, and it means the loosening factor is set by demand as much as by production technology.</li>
</ul>
</section>
<section id="measured-progress" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="measured-progress"><span class="header-section-number">4</span> Measured progress</h2>
<p>A measurement of algorithmic progress has to hold some target fixed, and it can only hold fixed a target it can check: the exact output, a tolerance, a benchmark score. Call that the <em>measured target</em> <img src="https://latex.codecogs.com/png.latex?b">. What a user wants done, the <em>task</em> <img src="https://latex.codecogs.com/png.latex?q%5E*">, is something else: a PDE solution accurate to the discretization error, say, or a classifier that works on the photos it will meet in deployment. A benchmark stands in for a task that is hard to measure directly, which is the construct-validity problem of <span class="citation" data-cites="Raji2021AI">Raji et al. (2021)</span>.</p>
<p>The measurement reports the algorithm factor at <img src="https://latex.codecogs.com/png.latex?b">. Multiplying and dividing by each method’s cost at the task splits that into two factors:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cunderbrace%7B%5Cfrac%7BW_%7Ba_0%7D(b)%7D%7BW_%7Ba_1%7D(b)%7D%7D_%7B%5Ctext%7Bmeasured%7D%7D%0A=%20%5Cunderbrace%7B%5Cfrac%7BW_%7Ba_0%7D(q%5E*)%7D%7BW_%7Ba_1%7D(q%5E*)%7D%7D_%7B%5Ctext%7Bprogress%20on%20the%20task%7D%7D%0A%5Ctimes%20%5Cunderbrace%7B%5Cfrac%7BW_%7Ba_1%7D(q%5E*)%20/%20W_%7Ba_1%7D(b)%7D%7BW_%7Ba_0%7D(q%5E*)%20/%20W_%7Ba_0%7D(b)%7D%7D_%7B%5Ctext%7Bmismatch%7D%7D%0A"></p>
<p>For each method, <img src="https://latex.codecogs.com/png.latex?W_a(q%5E*)/W_a(b)"> is how much more it would cost that method to do the task than to reach the measured target. The second factor compares that gap for the new method against the old one. It is 1 when the measured target is the task, or when both methods find the two equally far apart, and in those cases the measurement is progress on the task. The table sorts the published measurements by their measured target.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>setting</th>
<th>measured target</th>
<th>algorithms</th>
<th>hardware</th>
<th>source</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>LP, 2001–2020</td>
<td>exact solution</td>
<td><img src="https://latex.codecogs.com/png.latex?9%5Ctimes"></td>
<td><img src="https://latex.codecogs.com/png.latex?20%5Ctimes"></td>
<td><span class="citation" data-cites="Koch2022Progress">Koch et al. (2022)</span></td>
</tr>
<tr class="even">
<td>MILP, 2001–2020</td>
<td>exact solution</td>
<td><img src="https://latex.codecogs.com/png.latex?50%5Ctimes"></td>
<td><img src="https://latex.codecogs.com/png.latex?20%5Ctimes"></td>
<td><span class="citation" data-cites="Koch2022Progress">Koch et al. (2022)</span></td>
</tr>
<tr class="odd">
<td>SAT, 1999–2019</td>
<td>instances solved in time limit</td>
<td>about equal</td>
<td>about equal</td>
<td><span class="citation" data-cites="Fichte2023Time">Fichte, Hecher, and Szeider (2023)</span></td>
</tr>
<tr class="even">
<td>exact algorithm families</td>
<td>output</td>
<td>median 6%/yr at <img src="https://latex.codecogs.com/png.latex?n=10%5E3">, 28%/yr at <img src="https://latex.codecogs.com/png.latex?n=10%5E9"></td>
<td></td>
<td><span class="citation" data-cites="Sherry2021How">Sherry and Thompson (2021)</span></td>
</tr>
<tr class="odd">
<td>ImageNet, 2012–2019</td>
<td>AlexNet-level accuracy</td>
<td><img src="https://latex.codecogs.com/png.latex?2%5Ctimes"> / 16 months</td>
<td></td>
<td><span class="citation" data-cites="Hernandez2020Measuring">Hernandez and Brown (2020)</span></td>
</tr>
<tr class="even">
<td>ImageNet</td>
<td>accuracy, model-based</td>
<td><img src="https://latex.codecogs.com/png.latex?2%5Ctimes"> / 9 months</td>
<td></td>
<td><span class="citation" data-cites="Erdil2023Algorithmic">Erdil and Besiroglu (2023)</span></td>
</tr>
<tr class="odd">
<td>language models, 2012–2023</td>
<td>perplexity, model-based</td>
<td><img src="https://latex.codecogs.com/png.latex?2%5Ctimes"> / 8 months</td>
<td></td>
<td><span class="citation" data-cites="Ho2024Algorithmic">Ho et al. (2024)</span></td>
</tr>
<tr class="even">
<td>GPUs</td>
<td></td>
<td></td>
<td><img src="https://latex.codecogs.com/png.latex?2%5Ctimes"> / 2.3 yr FLOP/s; <img src="https://latex.codecogs.com/png.latex?2%5Ctimes"> / 2.1 yr FLOP per dollar</td>
<td><span class="citation" data-cites="Hobbhahn2023Trends">Hobbhahn, Heim, and Aydos (2023)</span></td>
</tr>
</tbody>
</table>
<p>The two halves of the table miss the task in different ways.</p>
<p><em>The classical rows hold a target stricter than the task.</em> They fix the exact output, while most tasks only need it to some tolerance. Then <img src="https://latex.codecogs.com/png.latex?W_a(q%5E*)/W_a(b)%20%5Cle%201">, and it is smallest for the methods that can exploit the tolerance, so when the new method is one of those, the mismatch factor is below 1 and the measurement undercounts progress on the task. <span class="citation" data-cites="Sherry2021How">Sherry and Thompson (2021)</span>’s exclusion of approximate algorithms is the extreme case: methods that only pay off at a tolerance are left out altogether.</p>
<p>The best-known chart of algorithms beating Moore’s law shows what changes when we measure against the task instead. <span class="citation" data-cites="Rude2018Research">Rüde et al. (2018)</span> plot the cost of solving the Poisson equation on an <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n%20%5Ctimes%20n"> grid, from banded Gaussian elimination in 1947 at <img src="https://latex.codecogs.com/png.latex?O(n%5E7)"> operations to Brandt’s multigrid in 1974 at <img src="https://latex.codecogs.com/png.latex?O(n%5E3)">. At <img src="https://latex.codecogs.com/png.latex?n%20=%2064"> the ratio is <img src="https://latex.codecogs.com/png.latex?64%5E4%20=%202%5E%7B24%7D">, which they match to the 24 doublings of Moore’s law over the same period. Gaussian elimination solves the discretized system exactly, while multigrid’s <img src="https://latex.codecogs.com/png.latex?O(n%5E3)"> is the cost of an iterative solve stopped once its error is down to the size of the discretization error. Against the exact solve, part of that speedup is loosening. Against the task, which never needed more than the discretization accuracy, all of it is algorithm, because Gaussian elimination has no way to stop early: <img src="https://latex.codecogs.com/png.latex?W_%7B%5Ctext%7BGE%7D%7D(q%5E*)%20=%20W_%7B%5Ctext%7BGE%7D%7D(%5Ctext%7Bexact%7D)">. That is the order dependence of the Cholesky example, settled by taking the task as the reference target.</p>
<p><em>The ML rows hold a target that differs from the task in both directions.</em> A benchmark score can be reached without doing the task, wherever the score fails to check something the task needs: robustness, calibration, the photos the model will meet in deployment. It can also demand things the task does not need, such as telling apart the hundred-odd dog breeds in ImageNet’s label set, or agreeing with labels that are themselves wrong; at least 6% of the ImageNet validation labels are <span class="citation" data-cites="Northcutt2021Pervasive">(Northcutt, Athalye, and Mueller 2021)</span>. So each method’s gap <img src="https://latex.codecogs.com/png.latex?W_a(q%5E*)/W_a(b)"> can sit on either side of 1, and nothing in the setup fixes the sign of the mismatch factor.</p>
<p>Selection fixes it, roughly. When a benchmark is new, nobody has optimized against it, so the first method to reach a given score has no special relationship to the benchmark’s quirks. Later methods are selected for reaching the score cheaply, that is, for a small <img src="https://latex.codecogs.com/png.latex?W_%7Ba_1%7D(b)">, and nobody selects them for a small <img src="https://latex.codecogs.com/png.latex?W_%7Ba_1%7D(q%5E*)">. Whichever way the benchmark differs from the task, that selection favours methods specialized to the benchmark: good at what it checks, whether or not the task needs it, and indifferent to what it does not check. So the mismatch factor drifts above 1, and the measurement overcounts. This is Goodhart’s law written into the identity: the benchmark measured the task well until it became the target. Shortcut learning is the limit, where a model reaches the score and cannot do the task at any budget, so <img src="https://latex.codecogs.com/png.latex?W_%7Ba_1%7D(q%5E*)"> is infinite <span class="citation" data-cites="Geirhos2020Shortcut">(Geirhos et al. 2020)</span>.</p>
<p>There are two checks I know of on how large the overcount is, and they point in different directions. <span class="citation" data-cites="Recht2019ImageNet">Recht et al. (2019)</span> rebuilt the ImageNet test set by following the original collection process, and found that models lost 11–14 points of accuracy on it, but that gains on the original test set came with larger gains on the new one. That rules out one route to overcounting, reuse of the same test set, at least on ImageNet up to 2019. It cannot see the other routes, since a fresh test set built the same way shares the original’s quirks. <span class="citation" data-cites="Beyer2020Are">Beyer et al. (2020)</span> relabelled the ImageNet validation set with a more careful annotation procedure, and found the gains of recent classifiers substantially smaller on the new labels than on the original ones. That is the overcount from fitting the benchmark’s quirks, measured directly.</p>
<p>Three more details from these measurements feed into the inferences. The MILP figure covers only the instances both generations of solver could finish; 149 of the 240 MIPLIB 2017 instances were out of reach of every 2001 solver within 24 hours. <span class="citation" data-cites="Sherry2021How">Sherry and Thompson (2021)</span> find algorithmic gains bimodal: just under half of algorithm families never improve, and a few jump by orders of magnitude, so the median family overtakes hardware only around <img src="https://latex.codecogs.com/png.latex?n%20%5Capprox%2010%5E%7B12%7D">. And <span class="citation" data-cites="Gundlach2025Origin">Gundlach et al. (2025)</span> find that the LSTM-to-transformer gain is a change of scaling exponent rather than a constant multiplier: about <img src="https://latex.codecogs.com/png.latex?6%5Ctimes"> at <img src="https://latex.codecogs.com/png.latex?10%5E%7B15%7D"> FLOP, over <img src="https://latex.codecogs.com/png.latex?100%5Ctimes"> at <img src="https://latex.codecogs.com/png.latex?10%5E%7B23%7D">.</p>
</section>
<section id="minimal-inferences" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="minimal-inferences"><span class="header-section-number">5</span> Minimal inferences</h2>
<p><strong>Where the target is held exact, algorithms and hardware are roughly even.</strong> Three different designs agree on this: solver binaries swapped across machines <span class="citation" data-cites="Koch2022Progress">(Koch et al. 2022)</span>, the time-leap SAT experiment <span class="citation" data-cites="Fichte2023Time">(Fichte, Hecher, and Szeider 2023)</span>, and <span class="citation" data-cites="Grace2013Algorithmic">Grace (2013)</span>’s survey across six domains, which puts algorithmic gains at 50–100% of hardware gains. The algorithmic gains are lumpier, which matters for forecasting even when the averages match. An exact target is stricter than most tasks, so this undercounts algorithmic progress on the task by however much the approximate methods are worth. In the area I know best, Gaussian processes, approximate methods are what people mostly use; I do not know how big the undercount is elsewhere.</p>
<p><strong>ML’s faster algorithmic rates are measured against a different target.</strong> An 8-month doubling against a 2-year hardware doubling makes algorithms look like the bigger term, but part of it may be mismatch between benchmark and task, which Goodhart’s law predicts will grow the longer a field optimizes against one benchmark. Since loosening is a one-off per task, I would not extrapolate those rates as if they were hardware-like trends. If this is right, a new task should show a burst of fast early “algorithmic” progress while people discover what can be thrown away, then slow down. That is at least consistent with how benchmarks tend to saturate, though I have not checked it.</p>
<p><strong>Some hardware progress is loosening too.</strong> Epoch fits accelerator FLOP/s within one number format and reports the move to lower precision separately, as a one-off of roughly <img src="https://latex.codecogs.com/png.latex?8%5Ctimes"> for FP16 tensor cores and <img src="https://latex.codecogs.com/png.latex?13%5Ctimes"> for INT8 <span class="citation" data-cites="Hobbhahn2023Trends">(Hobbhahn, Heim, and Aydos 2023)</span>. At a 2.3-year doubling time, those single steps are worth about 7 and 8.5 years of FP32 progress. Lower precision loosens the target, and gets booked as hardware.</p>
<p><strong>At the frontier, the split is not well defined.</strong> This is the envelope problem, in the quantity of compute rather than the price of operations. If the transformer’s advantage grows with compute, then the “algorithmic” gain at frontier scale grew because frontier compute grew, and listing it beside compute as a separate factor counts that growth twice. At a fixed small scale the comparison is cleaner, and there <span class="citation" data-cites="Gundlach2025Origin">Gundlach et al. (2025)</span> put a decade of algorithms at around <img src="https://latex.codecogs.com/png.latex?20%5Ctimes"> at <img src="https://latex.codecogs.com/png.latex?10%5E%7B18%7D"> FLOP, a bit less than the hardware gain over the same decade.</p>
<p><strong>The most valuable progress is invisible to all of these.</strong> Infeasible-to-feasible transitions give infinite ratios, and loosening is the usual way they happen. So any account of “how much faster we got” built from cost ratios underweights loosening, and that is the factor I would most like a handle on.</p>
<p>🚧TODO🚧 Can we estimate in advance how far a task’s target can be loosened? For statistical tasks there is a floor: <span class="citation" data-cites="Thompson2022Computational">Thompson et al. (2022)</span> argue compute must grow at least as performance to the fourth power, and measure something closer to the twelfth on ImageNet. The fourth power comes from two square laws: at the usual <img src="https://latex.codecogs.com/png.latex?1/%5Csqrt%7Bn%7D"> statistical rate, halving the error needs <img src="https://latex.codecogs.com/png.latex?4%5Ctimes"> the data, and training an overparameterized model costs about <img src="https://latex.codecogs.com/png.latex?n%5E2"> in the data size, so <img src="https://latex.codecogs.com/png.latex?16%5Ctimes"> the compute. That gap looks like a candidate measure of how much loosening is left.</p>
</section>
<section id="incoming" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="incoming"><span class="header-section-number">6</span> Incoming</h2>
<ul>
<li><a href="../notebook/nn_scaling_laws.html">Neural scaling laws</a></li>
<li><a href="../notebook/ml_benchmarks.html">ML benchmarks</a></li>
<li><a href="../notebook/grammatical_inference.html">Grammar induction</a></li>
<li><span class="citation" data-cites="Bloom2020Are">Bloom et al. (2020)</span> on why constant exponential progress needs exponentially growing research effort.</li>
<li><span class="citation" data-cites="Sanderson2025Rethinking">Sanderson et al. (2025)</span> on compute-dependent advances, which make algorithms and compute complements rather than substitutes.</li>
<li><span class="citation" data-cites="Pilz2024Increased">Pilz, Heim, and Brown (2024)</span> on separating the falling cost of a given capability from the rising capability per dollar.</li>
</ul>
</section>
<section id="references" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="references"><span class="header-section-number">7</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Arora2021Rip" class="csl-entry">
Arora, and Zhang. 2021. <span>“<a href="http://arxiv.org/abs/2102.13189">Rip van Winkle’s Razor: A Simple Estimate of Overfit to Test Data</a>.”</span> <em>arXiv:2102.13189 [Cs, Stat]</em>.
</div>
<div id="ref-BartoldsonComputeEfficient" class="csl-entry">
Bartoldson, Kailkhura, and Blalock. 2023. <span>“Compute-Eﬃcient Deep Learning: Algorithmic Trends and Opportunities.”</span> <em>JMLR</em>.
</div>
<div id="ref-Beyer2020Are" class="csl-entry">
Beyer, Hénaff, Kolesnikov, et al. 2020. <span>“<a href="https://arxiv.org/abs/2006.07159">Are We Done with ImageNet?</a>”</span>
</div>
<div id="ref-Bixby2012Brief" class="csl-entry">
Bixby. 2012. <span>“<a href="https://doi.org/10.4171/dms/6/16">A Brief History of Linear and Mixed-Integer Programming Computation</a>.”</span> In.
</div>
<div id="ref-Bloom2020Are" class="csl-entry">
Bloom, Jones, Van Reenen, et al. 2020. <span>“<a href="https://doi.org/10.1257/aer.20180338">Are Ideas Getting Harder to Find?</a>”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Blum2015Ladder" class="csl-entry">
Blum, and Hardt. 2015. <span>“<a href="http://arxiv.org/abs/1502.04585">The Ladder: A Reliable Leaderboard for Machine Learning Competitions</a>.”</span> <em>arXiv:1502.04585 [Cs]</em>.
</div>
<div id="ref-Dao2022FlashAttention" class="csl-entry">
Dao, Fu, Ermon, et al. 2022. <span>“<a href="https://arxiv.org/abs/2205.14135">FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness</a>.”</span>
</div>
<div id="ref-Davidson2023AI" class="csl-entry">
Davidson, Denain, Villalobos, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2312.07413">AI Capabilities Can Be Significantly Improved Without Expensive Retraining</a>.”</span>
</div>
<div id="ref-Dongarra2000Guest" class="csl-entry">
Dongarra, and Sullivan. 2000. <span>“<a href="https://doi.org/10.1109/MCISE.2000.814652">Guest Editors’ Introduction: The Top 10 Algorithms</a>.”</span> <em>Computing in Science &amp; Engineering</em>.
</div>
<div id="ref-Dorner2021Measuring" class="csl-entry">
Dorner. 2021. <span>“<a href="https://doi.org/10.48550/arXiv.2102.04881">Measuring Progress in Deep Reinforcement Learning Sample Efficiency</a>.”</span>
</div>
<div id="ref-Erdil2023Algorithmic" class="csl-entry">
Erdil, and Besiroglu. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2212.05153">Algorithmic Progress in Computer Vision</a>.”</span>
</div>
<div id="ref-Fichte2023Silent" class="csl-entry">
Fichte, Berre, Hecher, et al. 2023. <span>“<a href="https://doi.org/10.1145/3560469">The Silent (R)evolution of SAT</a>.”</span> <em>Communications of the ACM</em>.
</div>
<div id="ref-Fichte2023Time" class="csl-entry">
Fichte, Hecher, and Szeider. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2008.02215">A Time Leap Challenge for SAT Solving</a>.”</span>
</div>
<div id="ref-Fleming1986How" class="csl-entry">
Fleming, and Wallace. 1986. <span>“<a href="https://doi.org/10.1145/5666.5673">How Not to Lie with Statistics: The Correct Way to Summarize Benchmark Results</a>.”</span> <em>Communications of the ACM</em>.
</div>
<div id="ref-Geirhos2020Shortcut" class="csl-entry">
Geirhos, Jacobsen, Michaelis, et al. 2020. <span>“<a href="http://arxiv.org/abs/2004.07780">Shortcut Learning in Deep Neural Networks</a>.”</span> <em>arXiv:2004.07780 [Cs, q-Bio]</em>.
</div>
<div id="ref-Gold1967Language" class="csl-entry">
Gold. 1967. <span>“<a href="https://doi.org/10.1016/S0019-9958(67)91165-5">Language Identification in the Limit</a>.”</span> <em>Information and Control</em>.
</div>
<div id="ref-Golden2024Unexpected" class="csl-entry">
Golden, Schrage, Shier, et al. 2024. <span>“<a href="https://doi.org/10.1007/s10479-024-06245-5">The Unexpected Power of Linear Programming: An Updated Collection of Surprising Applications</a>.”</span> <em>Annals of Operations Research</em>.
</div>
<div id="ref-Grace2013Algorithmic" class="csl-entry">
Grace. 2013. <span>“<a href="https://intelligence.org/files/AlgorithmicProgress.pdf">Algorithmic Progress in Six Domains</a>.”</span>
</div>
<div id="ref-Gundlach2025Origin" class="csl-entry">
Gundlach, Fogelson, Lynch, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.21622">On the Origin of Algorithmic Progress in AI</a>.”</span>
</div>
<div id="ref-Gundlach2026Price" class="csl-entry">
Gundlach, Lynch, Mertens, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2511.23455">The Price of Progress: Price Performance and the Future of AI</a>.”</span>
</div>
<div id="ref-Hairer1996Solving" class="csl-entry">
Hairer, and Wanner. 1996. <em><a href="https://doi.org/10.1007/978-3-642-05221-7">Solving Ordinary Differential Equations 2: Stiff and Differential-Algebraic Problems</a></em>. Springer Series in Computational Mathematics.
</div>
<div id="ref-Halko2011Finding" class="csl-entry">
Halko, Martinsson, and Tropp. 2011. <span>“<a href="https://doi.org/10.1137/090771806">Finding Structure with Randomness: Probabilistic Algorithms for Constructing Approximate Matrix Decompositions</a>.”</span> <em>SIAM Review</em>.
</div>
<div id="ref-Hernandez2020Measuring" class="csl-entry">
Hernandez, and Brown. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.2005.04305">Measuring the Algorithmic Efficiency of Neural Networks</a>.”</span>
</div>
<div id="ref-Hobbhahn2023Trends" class="csl-entry">
Hobbhahn, Heim, and Aydos. 2023. <span>“<a href="https://epoch.ai/publications/trends-in-machine-learning-hardware">Trends in Machine Learning Hardware</a>.”</span>
</div>
<div id="ref-Ho2024Algorithmic" class="csl-entry">
Ho, Besiroglu, Erdil, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2403.05812">Algorithmic Progress in Language Models</a>.”</span>
</div>
<div id="ref-Hochlehnert2025Sober" class="csl-entry">
Hochlehnert, Bhatnagar, Udandarao, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2504.07086">A Sober Look at Progress in Language Model Reasoning: Pitfalls and Paths to Reproducibility</a>.”</span>
</div>
<div id="ref-Hoffmann2022Training" class="csl-entry">
Hoffmann, Borgeaud, Mensch, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2203.15556">Training Compute-Optimal Large Language Models</a>.”</span>
</div>
<div id="ref-Hooker2020Hardware" class="csl-entry">
Hooker. 2020. <span>“<a href="http://arxiv.org/abs/2009.06489">The Hardware Lottery</a>.”</span> <em>arXiv:2009.06489 [Cs]</em>.
</div>
<div id="ref-Houthakker1955Pareto" class="csl-entry">
Houthakker. 1955. <span>“<a href="https://doi.org/10.2307/2296148">The Pareto Distribution and the Cobb-Douglas Production Function in Activity Analysis</a>.”</span> <em>The Review of Economic Studies</em>.
</div>
<div id="ref-Jones2005Shape" class="csl-entry">
Jones. 2005. <span>“<a href="https://doi.org/10.1093/qje/120.2.517">The Shape of Production Functions and the Direction of Technical Change</a>.”</span> <em>The Quarterly Journal of Economics</em>.
</div>
<div id="ref-Kalavasis2024Limits" class="csl-entry">
Kalavasis, Mehrotra, and Velegkas. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2411.09642">On the Limits of Language Generation: Trade-Offs Between Hallucination and Mode Collapse</a>.”</span>
</div>
<div id="ref-Kaplan2020Scaling" class="csl-entry">
Kaplan, McCandlish, Henighan, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.2001.08361">Scaling Laws for Neural Language Models</a>.”</span>
</div>
<div id="ref-Kleinberg2024Language" class="csl-entry">
Kleinberg, and Mullainathan. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2404.06757">Language Generation in the Limit</a>.”</span>
</div>
<div id="ref-Koch2022Progress" class="csl-entry">
Koch, Berthold, Pedersen, et al. 2022. <span>“<a href="https://doi.org/10.1016/j.ejco.2022.100031">Progress in Mathematical Programming Solvers from 2001 to 2020</a>.”</span> <em>EURO Journal on Computational Optimization</em>.
</div>
<div id="ref-Leiserson2020Theres" class="csl-entry">
Leiserson, Thompson, Emer, et al. 2020. <span>“<a href="https://doi.org/10.1126/science.aam9744">There’s Plenty of Room at the Top: What Will Drive Computer Performance After Moore’s Law?</a>”</span> <em>Science</em>.
</div>
<div id="ref-Luengo2020Survey" class="csl-entry">
Luengo, Martino, Bugallo, et al. 2020. <span>“<a href="https://doi.org/10.1186/s13634-020-00675-6">A Survey of Monte Carlo Methods for Parameter Estimation</a>.”</span> <em>EURASIP Journal on Advances in Signal Processing</em>.
</div>
<div id="ref-Mattson2020MLPerf" class="csl-entry">
Mattson, Cheng, Coleman, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.1910.01500">MLPerf Training Benchmark</a>.”</span>
</div>
<div id="ref-Mertens2026There" class="csl-entry">
Mertens, Fischl-Lanzoni, and Thompson. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2602.07238">Is There “Secret Sauce’’ in Large Language Model Development?</a>”</span>
</div>
<div id="ref-Musgrave2020Metric" class="csl-entry">
Musgrave, Belongie, and Lim. 2020. <span>“<a href="http://arxiv.org/abs/2003.08505">A Metric Learning Reality Check</a>.”</span> <em>arXiv:2003.08505 [Cs]</em>.
</div>
<div id="ref-Mytkowicz2009Producing" class="csl-entry">
Mytkowicz, Diwan, Hauswirth, et al. 2009. <span>“<a href="https://doi.org/10.1145/1508244.1508275">Producing Wrong Data Without Doing Anything Obviously Wrong!</a>”</span> In <em>Proceedings of the 14th International Conference on Architectural Support for Programming Languages and Operating Systems</em>. ASPLOS XIV.
</div>
<div id="ref-Nagy2013Statistical" class="csl-entry">
Nagy, Farmer, Bui, et al. 2013. <span>“<a href="https://doi.org/10.1371/journal.pone.0052669">Statistical Basis for Predicting Technological Progress</a>.”</span> <em>PLOS ONE</em>.
</div>
<div id="ref-Nordhaus2007Two" class="csl-entry">
Nordhaus. 2007. <span>“<a href="https://doi.org/10.1017/S0022050707000058">Two Centuries of Productivity Growth in Computing</a>.”</span> <em>The Journal of Economic History</em>.
</div>
<div id="ref-Northcutt2021Pervasive" class="csl-entry">
Northcutt, Athalye, and Mueller. 2021. <span>“<a href="https://arxiv.org/abs/2103.14749">Pervasive Label Errors in Test Sets Destabilize Machine Learning Benchmarks</a>.”</span>
</div>
<div id="ref-Ott2022Mapping" class="csl-entry">
Ott, Barbosa-Silva, Blagec, et al. 2022. <span>“<a href="https://doi.org/10.1038/s41467-022-34591-0">Mapping Global Dynamics of Benchmark Creation and Saturation in Artificial Intelligence</a>.”</span> <em>Nature Communications</em>.
</div>
<div id="ref-Pilz2024Increased" class="csl-entry">
Pilz, Heim, and Brown. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2311.15377">Increased Compute Efficiency and the Diffusion of AI Capabilities</a>.”</span>
</div>
<div id="ref-Pitt1993Minimum" class="csl-entry">
Pitt, and Warmuth. 1993. <span>“<a href="https://doi.org/10.1145/138027.138042">The Minimum Consistent DFA Problem Cannot Be Approximated Within Any Polynomial</a>.”</span> <em>Journal of the ACM</em>.
</div>
<div id="ref-Prieto2025Evolution" class="csl-entry">
Prieto, Prieto, Escobar, et al. 2025. <span>“<a href="https://doi.org/10.1007/s10586-024-04767-y">Evolution of Computing Energy Efficiency: Koomey’s Law Revisited</a>.”</span> <em>Cluster Computing</em>.
</div>
<div id="ref-Raji2021AI" class="csl-entry">
Raji, Bender, Paullada, et al. 2021. <span>“<a href="https://doi.org/10.48550/arXiv.2111.15366">AI and the Everything in the Whole Wide World Benchmark</a>.”</span>
</div>
<div id="ref-Recht2019ImageNet" class="csl-entry">
Recht, Roelofs, Schmidt, et al. 2019. <span>“<a href="https://proceedings.mlr.press/v97/recht19a.html">Do ImageNet Classifiers Generalize to ImageNet?</a>”</span> In <em>Proceedings of the 36th International Conference on Machine Learning</em>.
</div>
<div id="ref-Reddi2020MLPerf" class="csl-entry">
Reddi, Cheng, Kanter, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.1911.02549">MLPerf Inference Benchmark</a>.”</span>
</div>
<div id="ref-Rice1976Algorithm" class="csl-entry">
Rice. 1976. <span>“<a href="https://doi.org/10.1016/S0065-2458(08)60520-3">The Algorithm Selection Problem</a>.”</span> <em>Advances in Computers</em>.
</div>
<div id="ref-Rome2025How" class="csl-entry">
Rome, Lynch, Li, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.22084">How Fast Are Algorithms Reducing the Demands on Memory? A Survey of Progress in Space Complexity</a>.”</span>
</div>
<div id="ref-Roughgarden2020WorstCase" class="csl-entry">
Roughgarden, ed. 2020. <em><a href="https://doi.org/10.1017/9781108637435">Beyond the Worst-Case Analysis of Algorithms</a></em>.
</div>
<div id="ref-Rude2018Research" class="csl-entry">
Rüde, Willcox, McInnes, et al. 2018. <span>“<a href="https://doi.org/10.1137/16M1096840">Research and Education in Computational Science and Engineering</a>.”</span> <em>SIAM Review</em>.
</div>
<div id="ref-Sanderson2025Rethinking" class="csl-entry">
Sanderson, Foley, Guo, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2505.04075">Rethinking LLM Advancement: Compute-Dependent and Independent Paths to Progress</a>.”</span>
</div>
<div id="ref-Sevilla2022Compute" class="csl-entry">
Sevilla, Heim, Ho, et al. 2022. <span>“<a href="https://doi.org/10.1109/IJCNN55064.2022.9891914">Compute Trends Across Three Eras of Machine Learning</a>.”</span> In <em>2022 International Joint Conference on Neural Networks (IJCNN)</em>.
</div>
<div id="ref-Sherry2021How" class="csl-entry">
Sherry, and Thompson. 2021. <span>“<a href="https://doi.org/10.1109/jproc.2021.3107219">How Fast Do Algorithms Improve? [Point of View]</a>.”</span> <em>Proceedings of the IEEE</em>.
</div>
<div id="ref-SmithMiles2023Instance" class="csl-entry">
Smith-Miles, and Muñoz. 2023. <span>“<a href="https://doi.org/10.1145/3572895">Instance Space Analysis for Algorithm&nbsp;Testing: Methodology and Software Tools</a>.”</span> <em>ACM Computing Surveys</em>.
</div>
<div id="ref-Spielman2004Smoothed" class="csl-entry">
Spielman, and Teng. 2004. <span>“<a href="https://doi.org/10.1145/990308.990310">Smoothed Analysis of Algorithms: Why the Simplex Algorithm Usually Takes Polynomial Time</a>.”</span> <em>Journal of the ACM</em>.
</div>
<div id="ref-Thompson2022Computational" class="csl-entry">
Thompson, Greenewald, Lee, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2007.05558">The Computational Limits of Deep Learning</a>.”</span>
</div>
<div id="ref-Waltsburger2023Neural" class="csl-entry">
Waltsburger, Libessart, Ren, et al. 2023. <span>“<a href="https://doi.org/10.1109/ISCAS46773.2023.10181766">Neural Network Scoring for Efficient Computing</a>.”</span> In <em>2023 IEEE International Symposium on Circuits and Systems (ISCAS)</em>.
</div>
</div>


</section>

 ]]></description>
  <category>AI safety</category>
  <category>bounded compute</category>
  <category>functional analysis</category>
  <category>machine learning</category>
  <category>model selection</category>
  <category>optimization</category>
  <category>statmech</category>
  <category>when to compute</category>
  <guid>https://danmackinlay.name/notebook/algorithmic_improvement.html</guid>
  <pubDate>Wed, 16 Sep 2026 16:36:13 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/whipit2gofaster_RP-P-BI-2221.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Should our journal publish AI-drafted manuscripts?</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/post/should_we_allow_ai_articles.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Status: Rough conceptual model.</p>
<p>This is a personal exploration of a live policy problem and definitely does not represent the opinion of the Alignment Journal itself.</p>
<p>Given the context, I had best disclose my own AI usage in this article: transformative. Although the original model design was mine, it was made way better by iterative refinement and re-drafting by AI, and by no means would I have had time to write it purely by hand.</p>
</div>
</div>
</div>
<div id="fig-mercury-scribe" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-mercury-scribe-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/mercury_scribe_RP-P-1939-690.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/mercury_scribe_RP-P-1939-690.avif" id="fig-mercury-scribe" class="right illustration figure img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-mercury-scribe-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>At <a href="https://alignmentjournal.org?ref=danmackinlay.name">the Alignment Journal</a> we <a href="https://blog.alignmentjournal.org/adaptation-to-ai/?ref=danmackinlay.name">have been discussing</a> whether to accept AI-drafted manuscripts for review. This is a relatively high-leverage question, as detecting AI-drafted prose is—currently, against authors who are not trying to hide it—surprisingly feasible, making it a cheap (albeit imperfect) proxy signal for us to use in desk review to filter out low-quality papers.<sup>1</sup></p>
<p>The ideal policy would optimize for the overall quality of the journal’s output with regard to how that serves our readers. There are many components to that; quality, readability, professional and academic norms… I ignore most of those and bloody-mindedly focus on the economic effects of AI drafting.</p>
<p>On one hand, AI drafting lowers the cost of producing a manuscript, which may increase the flow of good work into our readers’ inboxes. On the other hand, it might instead increase the flow of low-quality AI slop into those inboxes. That is, we worry about the impact of AI slop spam, <a href="https://source.washu.edu/2026/05/analysis-reveals-ais-impact-on-research-journals/?ref=danmackinlay.name">as</a> <a href="https://cerncourier.com/arxivs-one-strike-rule-on-ai/?ref=danmackinlay.name">do</a> <a href="https://blog.neurips.cc/2026/06/02/ai-generated-papers-in-the-neurips-2026-position-paper-track/?ref=danmackinlay.name">many</a> <a href="https://www.lesswrong.com/posts/nQWavk9mnwcv6ScMR/new-lesswrong-editor-also-an-update-to-our-llm-policy?ref=danmackinlay.name#Policy_on_LLM_Use">others</a> <span class="citation" data-cites="Gartenberg2026More">(Gartenberg et al. 2026)</span>.</p>
<p>Insofar as the base costs of a high-quality paper exceed those of a slop manuscript, we might hope that permitting AI drafting provides a relatively larger benefit to slop authors than to substantive authors.</p>
<p>So, if we can detect AI drafting, should we do so? And: should we use it to desk-reject papers?</p>
<p>Here is a minimal economic model of that question, in the vein of <span class="citation" data-cites="Agrawal2019Economics">Agrawal, Gans, and Goldfarb (2019)</span>, which models AI as a fall in the price of prediction. My assumptions are stylized and tractable rather than realistic or empirically calibrated. Where I need to choose some numbers, I have taken inspiration from the Alignment Journal, since that is closest to my heart.</p>
<p>Here, I assume AI drafting reduces the cost of producing <em>prose</em>: the writing-up stage of science gets cheap while the substance stage, for now, does not <span class="citation" data-cites="Kwa2025Measuring">(as per Kwa et al. 2025)</span>. That is a shaky assumption even now (for example, both my drafting and substantive work in writing this very post were AI-expedited) and it will get shakier as AI progresses.</p>
<p>My goal here is not to persuade anyone of a particular optimal policy, but to lay out a concrete model of the first- and second-order economic effects of AI drafting so that we can debate these things better. Too often the discussion is conducted in purely moral terms (AI is “efficient” or “lazy”) or aesthetic ones (“AI prose is hard to read”); those lenses matter, but our duties in publishing surely also include the economic and strategic management of scientific knowledge production, and quantitative models of that seem under-supplied.</p>
<div class="callout callout-style-simple callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>tl;dr
</div>
</div>
<div class="callout-body-container callout-body">
<p>Under my (contrived but not crazy) assumptions:</p>
<ol type="1">
<li>AI drafting preferentially incentivises production of poor papers, which crowd out good ones: the time it saves good authors turns into extra manuscripts that are never read.</li>
<li>A ban enforced by an AI-drafting detector can induce three different regimes, depending on detector quality:</li>
</ol>
<ul>
<li>At low true positive rate it changes nothing;</li>
<li>at intermediate levels it deters only the good authors, who give up AI before slop authors; then,</li>
<li>if the detector is good enough, it restores the no-AI outcome, forfeiting every hour AI would have saved.</li>
</ul>
<ol start="3" type="1">
<li>A flat submission fee lets everyone use AI while excluding spam, and beats the ban outright. The fee that does it is ballpark USD 2,500 to 7,500 per submission, which is outside the Overton window and moreover a business model indistinguishable from a predatory journal’s, so nobody, least of all me, is proposing it.</li>
<li>A hybrid, where authors may pay to skip the detector, charges only AI drafters and publishes more good papers than the ban but fewer than the flat fee, because slop authors who draft by hand still clog the queue. The fee that does it is USD 4,000 to 12,000 per AI-drafted submission if the detector holds up against motivated rewriting..</li>
<li>Not modelled: false positives, reputational effects, harmful slop, competing journals, and AI that does the science as well as the writing. And, clearly, I remain silent on whether AI can do good writing or not.</li>
</ol>
<p>Putting that all together we can see that, regarded purely as an instrument for controlling slop rates, the detector ban is a clumsy approximation to a congestion charge, but it might be better than nothing and is more palateable than fees would be.</p>
</div>
</div>
<section id="setup" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="setup"><span class="header-section-number">1</span> Setup</h2>
<p>I assume there are two types of authors: <em>high-quality</em> authors (type <img src="https://latex.codecogs.com/png.latex?H">) and <em>slop</em> authors (type <img src="https://latex.codecogs.com/png.latex?L">). They differ only in the substance of their manuscripts and behave strategically regarding presentation. The proportion of high-quality authors is <img src="https://latex.codecogs.com/png.latex?%5Clambda">, and the rest are slop authors. Articles have <em>apparent substance</em> <img src="https://latex.codecogs.com/png.latex?s">, which is the quality the manuscript presents to a reviewer—the quality a reviewer can assess. I measure it in <em>citoms</em>, my idiosyncratic unit of “citability,” which happens to be what citations count. These are themselves an imperfect proxy for the true output we desire, <em>scientific progress</em> <img src="https://latex.codecogs.com/png.latex?v">, which is what a reader gets if the manuscript is published. I measure <em>that</em> in <em>scientillas</em>.<sup>2</sup></p>
<p>A high-quality author spends <em>substance time</em> <img src="https://latex.codecogs.com/png.latex?t"> on each manuscript—the hours of research and thinking rather than writing up. The result is a manuscript that reads as well as it actually is, with as many citoms as scientillas:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Av_H%20=s_H%20=%20%5Csqrt%7Bt%7D.%0A"></p>
<p>A spread of author skill acts as a multiplier on <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7Bt%7D">; I ignore it.</p>
<p>The rest of the authors produce slop, which in my model means manuscripts worth <img src="https://latex.codecogs.com/png.latex?s_L"> citoms at a glance but zero scientillas upon deep reading, <img src="https://latex.codecogs.com/png.latex?v_L%20=%200">, made with the minimum substance time <img src="https://latex.codecogs.com/png.latex?t_%7B%5Cmin%7D">. All of which is to say, the <img src="https://latex.codecogs.com/png.latex?H"> authors aren’t deceptive, whereas the <img src="https://latex.codecogs.com/png.latex?L"> authors produce low-quality outputs masquerading as adequate.</p>
<p>Every manuscript additionally requires drafting time <img src="https://latex.codecogs.com/png.latex?d">: hand-drafting costs <img src="https://latex.codecogs.com/png.latex?f">, AI drafting costs <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20=%20f/a">, where <img src="https://latex.codecogs.com/png.latex?a"> is the AI’s drafting efficiency. At <img src="https://latex.codecogs.com/png.latex?a%20=%201"> AI saves nothing, so everyone might as well hand-draft; I call that no-AI state the <em>hand-drafting equilibrium</em>, and it is the baseline for all other policies. An author has a time budget <img src="https://latex.codecogs.com/png.latex?T"> per period (year?), so an author who produces <img src="https://latex.codecogs.com/png.latex?n"> manuscripts at a cost of substance effort time <img src="https://latex.codecogs.com/png.latex?t"> satisfies <img src="https://latex.codecogs.com/png.latex?n(t%20+%20d)%20=%20T">. We write <img src="https://latex.codecogs.com/png.latex?t_H"> and <img src="https://latex.codecogs.com/png.latex?n_H"> for the substance time and manuscript count an <img src="https://latex.codecogs.com/png.latex?H">-type author chooses, and <img src="https://latex.codecogs.com/png.latex?n_L"> for a slop author’s manuscript count; slop authors have no substance time to choose.</p>
<p>Reviewing a manuscript to the journal’s standard takes <img src="https://latex.codecogs.com/png.latex?h"> reviewer-hours, which we call the <em>depth</em> of review, and there are <img src="https://latex.codecogs.com/png.latex?R"> total reviewer-hours, so the journal can review at most <img src="https://latex.codecogs.com/png.latex?M%20=%20R/h"> manuscripts. Facing <img src="https://latex.codecogs.com/png.latex?N"> submissions, it reviews <img src="https://latex.codecogs.com/png.latex?%5Cmin(N,%20M)"> of them and rejects the rest unread, so a submission’s chance of being read at all is the coverage rate</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ar%20=%20%5Cmin%5C!%5Cleft(1,%20%5Ctfrac%7BM%7D%7BN%7D%5Cright).%0A"></p>
<p>A manuscript that receives a review is accepted with a probability depending on both its substance <img src="https://latex.codecogs.com/png.latex?s"> and the review time <img src="https://latex.codecogs.com/png.latex?h">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CPr(%5Ctext%7Baccept%7D%20%5Cmid%20s,%20%5Ctext%7Breviewed%7D)%20=%20%5CLambda%5Cbig(h%5C,(s%20-%20%5Cbar%7Bs%7D)%5Cbig),%0A%5Cqquad%20%5CLambda(x)%20=%20%5Cfrac%7B1%7D%7B1+e%5E%7B-x%7D%7D.%0A"></p>
<p>i.e., deeper reviews detect the scientific substance more precisely. Here <img src="https://latex.codecogs.com/png.latex?%5Cbar%7Bs%7D"> is the journal’s bar—the number of citoms at which we accept a reviewed manuscript half the time. We write <img src="https://latex.codecogs.com/png.latex?%5CLambda_H"> and <img src="https://latex.codecogs.com/png.latex?%5CLambda_L"> for the acceptance probability of a reviewed high-quality manuscript and a reviewed slop manuscript, respectively. The journal holds the depth <img src="https://latex.codecogs.com/png.latex?h"> fixed with respect to queue size. Review slots are the scarce resource in what follows: every submission takes one, and a submission that goes unread costs its author nothing but costs the journal whatever it displaced.</p>
<p>Authors are assumed to be <em>careerist</em>, in that they care only about a payoff <img src="https://latex.codecogs.com/png.latex?b"> per accepted paper. The payoff is earned by clearing the bar in citoms; scientillas earn an author nothing beyond that. A high-quality author facing a drafting cost <img src="https://latex.codecogs.com/png.latex?d"> thus solves</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmax_%7Bt%20%5Cge%20t_%7B%5Cmin%7D%7D%20%5C;%0A%5Cfrac%7Br%5C,b%5C,%5CLambda%5Cbig(h%5C,(%5Csqrt%7Bt%7D%20-%20%5Cbar%7Bs%7D)%5Cbig)%7D%7Bt%20+%20d%7D,%0A"></p>
<p>maximizing accepted papers per unit of time. No author can change the coverage rate <img src="https://latex.codecogs.com/png.latex?r">, and it scales the payoff of every candidate <img src="https://latex.codecogs.com/png.latex?t"> identically, so it drops out of the optimization. As such, congestion never influences the choice of how much effort we dedicate to substance. What influences the choice is the drafting cost <img src="https://latex.codecogs.com/png.latex?d">, which is <img src="https://latex.codecogs.com/png.latex?f"> by hand or <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20=%20f/a"> with AI. Every hour spent on the substance of one manuscript is an hour not spent starting the next, and <img src="https://latex.codecogs.com/png.latex?d"> controls how much that next manuscript would have cost. The first-order condition is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5CLambda'%7D%7B%5CLambda%7D%20%5Ccdot%20%5Cfrac%7Bh%7D%7B2%5Csqrt%7Bt%7D%7D%20%5C;=%5C;%20%5Cfrac%7B1%7D%7Bt+d%7D.%0A"></p>
<p>As AI capability rises, <img src="https://latex.codecogs.com/png.latex?%5Cdelta"> falls, the next manuscript gets cheaper, and the optimum tilts toward more, thinner papers.</p>
<p>Now let us consider the target audience, the readers. Recall our goal as benevolent planners to maximize the readers’ welfare. I operationalize that as the number of good papers published per period—the total amount of science we are pumping out to all readers. Of the <img src="https://latex.codecogs.com/png.latex?%5Clambda%5C,n_H"> high-quality manuscripts written, a fraction <img src="https://latex.codecogs.com/png.latex?r"> get reviewed and a fraction <img src="https://latex.codecogs.com/png.latex?%5CLambda_H"> of those pass, so the flow of high-quality papers into print is the <em>throughput</em></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AQ%20%5C;=%5C;%20r%5C,%5Clambda%5C,n_H%5C,%5CLambda_H.%0A"></p>
<p>The obvious alternative is the average scientillas per published article, <img src="https://latex.codecogs.com/png.latex?W%20=%20%5Cmathbb%7BE%7D%5B%5C,%20v%20%5Cmid%20%5Ctext%7Baccepted%7D%20%5C,%5D">, roughly the amount of science a reader finds by sampling the published literature at random; slop counts against <img src="https://latex.codecogs.com/png.latex?W">, whereas <img src="https://latex.codecogs.com/png.latex?Q"> merely ignores it. Every ranking of one policy against another below comes out the same way under both, so I track <img src="https://latex.codecogs.com/png.latex?Q"> and mention <img src="https://latex.codecogs.com/png.latex?W"> only at the point where they disagree: how high we should set the fee in Policy D. Both count scientillas, which measure public-good outcomes. The authors’ incentives, by contrast, count citoms, career-wise goals measured in citations. Slop writing is the stuff that produces the latter without the former.</p>
<p>I make three other simplifying assumptions:</p>
<ol type="1">
<li>AI drafting here introduces no errors and incurs no cognitive debt<sup>3</sup></li>
<li>Accepted slop delivers zero value but is not actively harmful.<sup>4</sup></li>
<li>The review budget is exogenous: <img src="https://latex.codecogs.com/png.latex?R"> is money divided by the going wage for qualified reviewer attention, and the model holds both fixed.</li>
</ol>
<div id="model-setup" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> livingthing.matplotlib_style <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> set_livingthing_style</span>
<span id="cb1-4">set_livingthing_style()</span>
<span id="cb1-5"></span>
<span id="cb1-6">P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(theta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, s_L<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.30</span>, sbar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.50</span>, t_min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.50</span>,</span>
<span id="cb1-7">         T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, lam<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, R<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">16.0</span>, h<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb1-8"></span>
<span id="cb1-9">T_GRID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.geomspace(P[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">25.0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4000</span>)</span>
<span id="cb1-10"></span>
<span id="cb1-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> logistic(x):</span>
<span id="cb1-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.clip(x, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>)))</span>
<span id="cb1-13"></span>
<span id="cb1-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> accept_H(t, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> logistic(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"h"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(t) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sbar"</span>]))</span>
<span id="cb1-16"></span>
<span id="cb1-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> accept_L(p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> logistic(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"h"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s_L"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sbar"</span>]))</span>
<span id="cb1-19"></span>
<span id="cb1-20"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> best_H(d, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P, fee<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>):</span>
<span id="cb1-21">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Substantive author's optimum: maximize (b*accept - fee)/(t+d).</span></span>
<span id="cb1-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    The coverage rate r is a common factor, so it never appears here.</span></span>
<span id="cb1-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns (value per unit time, t*)."""</span></span>
<span id="cb1-24">    obj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_H(T_GRID, p) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fee) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (T_GRID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> d)</span>
<span id="cb1-25">    i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.argmax(obj))</span>
<span id="cb1-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> obj[i], T_GRID[i]</span>
<span id="cb1-27"></span>
<span id="cb1-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> outcomes(nH, wH, tH, nL, wL, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-29">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Coverage, mean substance per article, and good-paper flow."""</span></span>
<span id="cb1-30">    N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> nH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> nL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wL</span>
<span id="cb1-31">    r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"R"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"h"</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> N) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb1-32">    aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> nH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_H(tH, p)</span>
<span id="cb1-33">    aL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> nL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_L(p)</span>
<span id="cb1-34">    sH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(tH)</span>
<span id="cb1-35">    W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> aL) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> aL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb1-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r, W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>W, thruH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> aH, tH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tH, sH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sH)</span>
<span id="cb1-37"></span>
<span id="cb1-38"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> solve_A(a, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-39">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Free-for-all: everyone AI-drafts, triage to capacity."""</span></span>
<span id="cb1-40">    delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> a</span>
<span id="cb1-41">    _, tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> best_H(delta, p)</span>
<span id="cb1-42">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> outcomes(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, tH,</span>
<span id="cb1-43">                    p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, p)</span>
<span id="cb1-44"></span>
<span id="cb1-45"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mu_star(a, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-46">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""True-positive rate above which slop producers abandon AI drafting."""</span></span>
<span id="cb1-47">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> a) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>])</span>
<span id="cb1-48"></span>
<span id="cb1-49"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mu_sub(a, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-50">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""True-positive rate above which high-quality authors abandon AI drafting."""</span></span>
<span id="cb1-51">    Vh, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> best_H(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>], p)</span>
<span id="cb1-52">    Va, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> best_H(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> a, p)</span>
<span id="cb1-53">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Vh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> Va</span>
<span id="cb1-54"></span>
<span id="cb1-55"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> solve_B(a, mu, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb1-56">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Detector ban: AI-drafted papers desk-rejected with true-positive rate mu."""</span></span>
<span id="cb1-57">    delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> a</span>
<span id="cb1-58">    dH, wH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (delta, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> mu_sub(a, p) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb1-59">    dL, wL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (delta, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> mu_star(a, p) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb1-60">    _, tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> best_H(dH, p)</span>
<span id="cb1-61">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> outcomes(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dH), wH, tH,</span>
<span id="cb1-62">                    p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dL), wL, p)</span>
<span id="cb1-63"></span>
<span id="cb1-64"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> solve_C(a, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P, eps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-3</span>):</span>
<span id="cb1-65">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Fee at slop's uncongested break-even: slop exits entirely."""</span></span>
<span id="cb1-66">    delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> a</span>
<span id="cb1-67">    fee <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_L(p) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eps</span>
<span id="cb1-68">    _, tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> best_H(delta, p, fee<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>fee)</span>
<span id="cb1-69">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> outcomes(p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, tH, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, p)</span>
<span id="cb1-70">    out[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fee"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fee</span>
<span id="cb1-71">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span></code></pre></div></div>
</details>
</div>
</section>
<section id="policy-a-free-for-all" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="policy-a-free-for-all"><span class="header-section-number">2</span> Policy A — Free-for-all</h2>
<p>Under this policy, anyone may use AI, and we triage submissions according to capacity (if we receive too many papers, we reject the excess). <strong>tl;dr</strong> everyone drafts their manuscripts with AI, as it is cheaper and goes unpunished, and the extra manuscripts mostly go unread. The equilibrium is easy to compute: the substance choice depends only on the drafting cost <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20=%20f/a">, submission counts follow from the time budget, and coverage follows from the counts.</p>
<div id="cell-fig-collapse" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1">a_grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.geomspace(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>)</span>
<span id="cb2-2">eqA <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [solve_A(a) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> a_grid]</span>
<span id="cb2-3"></span>
<span id="cb2-4">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb2-5">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA])</span>
<span id="cb2-6">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"substance time $t_H$"</span>)</span>
<span id="cb2-7">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA])</span>
<span id="cb2-8">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coverage $r$"</span>)</span>
<span id="cb2-9">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA])</span>
<span id="cb2-10">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"good papers per author-period $Q$"</span>)</span>
<span id="cb2-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes:</span>
<span id="cb2-12">    ax.set_xscale(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>)</span>
<span id="cb2-13">    ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AI capability $a$"</span>)</span>
<span id="cb2-14">fig.tight_layout()</span>
<span id="cb2-15">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-collapse" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-collapse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="should_we_allow_ai_articles_files/figure-html/fig-collapse-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: The free-for-all as AI drafting capability a grows: substance time per high-quality manuscript, the coverage rate r, and the throughput Q. t_H falls somewhat; the large damage comes from crowding."><img src="https://danmackinlay.name/post/should_we_allow_ai_articles_files/figure-html/fig-collapse-output-1.png" width="923" height="278" class="figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-collapse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: The free-for-all as AI drafting capability <img src="https://latex.codecogs.com/png.latex?a"> grows: substance time per high-quality manuscript, the coverage rate <img src="https://latex.codecogs.com/png.latex?r">, and the throughput <img src="https://latex.codecogs.com/png.latex?Q">. <img src="https://latex.codecogs.com/png.latex?t_H"> falls somewhat; the large damage comes from crowding.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Optimistically, we might hope that cheaper AI drafting frees up time compared to the manual alternative, allowing for more substance per paper. That does not happen under this model. The left edge of the capability sweep, <img src="https://latex.codecogs.com/png.latex?a%20=%201">, is the hand-drafting equilibrium. For the high-quality tier <img src="https://latex.codecogs.com/png.latex?H">, the substance time per manuscript <img src="https://latex.codecogs.com/png.latex?t_H"> falls modestly as <img src="https://latex.codecogs.com/png.latex?a"> rises, from <img src="https://latex.codecogs.com/png.latex?0.46"> to about <img src="https://latex.codecogs.com/png.latex?0.32">: cheaper drafting makes each manuscript cheaper, so authors choose quantity over quality. The fall is not too precipitous though: high-quality manuscripts stay above the bar (<img src="https://latex.codecogs.com/png.latex?s_H"> drifts from <img src="https://latex.codecogs.com/png.latex?0.68"> down to <img src="https://latex.codecogs.com/png.latex?0.56"> scientillas, against a bar of <img src="https://latex.codecogs.com/png.latex?0.5"> citoms), and their authors write three of them where they used to write one. For the low-quality tier <img src="https://latex.codecogs.com/png.latex?L">, the slop authors’ output grows much faster, because a slop manuscript is nearly all drafting time: <img src="https://latex.codecogs.com/png.latex?n_L"> rises from under <img src="https://latex.codecogs.com/png.latex?2"> to over <img src="https://latex.codecogs.com/png.latex?18">.</p>
<p>The downside is visible through the effect on the queue. The submission pool ends up <img src="https://latex.codecogs.com/png.latex?95%5C%25"> slop and coverage falls from roughly <img src="https://latex.codecogs.com/png.latex?1"> to <img src="https://latex.codecogs.com/png.latex?0.11">, so the journal publishes about four times fewer high-quality papers, even though the high-quality authors are writing more of them than before. Most of their extra manuscripts are simply never read. In the units of the model that is <img src="https://latex.codecogs.com/png.latex?Q"> falling from <img src="https://latex.codecogs.com/png.latex?0.22"> to <img src="https://latex.codecogs.com/png.latex?0.06"> good papers per author-period: a journal drawing on a hundred authors goes from printing 22 good papers a period to 6. The average quality of what it does print falls too, since slop passes a full review <img src="https://latex.codecogs.com/png.latex?12%5C%25"> of the time and the pool is <img src="https://latex.codecogs.com/png.latex?95%5C%25"> slop, so most of what the journal accepts is slop.</p>
<p>The collapse is smooth and the equilibrium is unique. The coverage rate cancels out of every author’s problem, so there is no feedback loop to amplify anything, and no tipping point. We could expand upon that but we do not right now.</p>
</section>
<section id="policy-b-automated-ban-on-ai-drafted-manuscripts" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="policy-b-automated-ban-on-ai-drafted-manuscripts"><span class="header-section-number">3</span> Policy B — Automated ban on AI-drafted manuscripts</h2>
<p>A stylometric detector (e.g.&nbsp;Pangram) flags manuscripts it thinks are AI-drafted, and we desk-reject those.<sup>5</sup> This is the only desk-reject stage; anything it passes goes into the capacity triage of the review queue. I assume the detector has true-positive rate <img src="https://latex.codecogs.com/png.latex?%5Cmu">, which the machine-learning literature calls its <em>recall</em>: it correctly flags a fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of AI-drafted manuscripts. For now, I assume it has a zero false-positive rate to keep it simple.<sup>6</sup> Desk-rejected papers consume no reviewer time. The rest get a normal review.</p>
<p>Before anyone changes behaviour, notice what the detector is to an author who drafts with AI: a fee. A fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of their manuscripts is thrown away unread, so each one loses <img src="https://latex.codecogs.com/png.latex?%5Cmu"> times its expected return, <img src="https://latex.codecogs.com/png.latex?%5Cmu%5C,r%5C,b%5C,%5CLambda_H"> for a good manuscript and <img src="https://latex.codecogs.com/png.latex?%5Cmu%5C,r%5C,b%5C,%5CLambda_L"> for slop. That is a proportional levy on expected return, and since <img src="https://latex.codecogs.com/png.latex?%5CLambda_H%20%3E%20%5CLambda_L"> it charges a good manuscript more than a slop one; a separating fee would do the opposite. We return to this with the flat congestion charge of Policy D. <em>That</em> by the way, is the sense in which the ban is an imperfect approximation to a fee.</p>
<p>Now, knowing this policy, everyone chooses how/if to write their papers. For low-quality authors, the trade-off is between the time saved by using AI and the risk of being desk-rejected. Their acceptance probability if they get past desk-rejection is the same however the manuscript was drafted, so it cancels from the comparison, and what remains is: papers per hour with AI, discounted by the survival rate <img src="https://latex.codecogs.com/png.latex?(1-%5Cmu)">, against papers per hour by hand. They keep AI drafting while <img src="https://latex.codecogs.com/png.latex?%5Cmu%20%3C%20%5Cmu%5E*(a)">, where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu%5E*(a)%20=%201%20-%20%5Cfrac%7Bt_%7B%5Cmin%7D%20+%20f/a%7D%7Bt_%7B%5Cmin%7D%20+%20f%7D%0A%5C;%5Cxrightarrow%7B%5C;a%20%5Cto%20%5Cinfty%5C;%7D%5C;%0A%5Cfrac%7Bf%7D%7Bt_%7B%5Cmin%7D%20+%20f%7D.%0A"></p>
<p>In this setting, the detector’s true-positive rate is pivotal, and the threshold it needs to clear depends on the time costs of producing slop. In particular, as AI capability rises toward <img src="https://latex.codecogs.com/png.latex?f/(t_%7B%5Cmin%7D+f)">, the share of a hand-drafted slop manuscript’s time spent drafting is about <img src="https://latex.codecogs.com/png.latex?0.91"> at my parameters. As such, a detector with a true-positive rate below that threshold deters slop authors only while AI is weak enough that <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)"> is under its rate; as capability grows past that point, the same detector stops working without any change in the policy or the detector. On unmodified LLM output, the best commercial detector clears that comfortably: Pangram reports a false-negative rate of <img src="https://latex.codecogs.com/png.latex?0.34%5C%25"> at a false-positive rate of <img src="https://latex.codecogs.com/png.latex?0.004%5C%25"> <span class="citation" data-cites="Glickenhaus2026Pangram">(Glickenhaus et al. 2026)</span>, and independent tests put it near <img src="https://latex.codecogs.com/png.latex?99%5C%25"> <span class="citation" data-cites="Russell2025People">(Russell, Karpinska, and Iyyer 2025)</span>. The rate that matters, though, is the one sustained against slop authors who rewrite to evade detection once it cuts into their earnings, and there the numbers are grim: Pangram’s own figure on AI-edited human text is <img src="https://latex.codecogs.com/png.latex?55">–<img src="https://latex.codecogs.com/png.latex?65%5C%25"> <span class="citation" data-cites="Glickenhaus2026Pangram">(Glickenhaus et al. 2026)</span>, and on academic abstracts a <img src="https://latex.codecogs.com/png.latex?95%5C%25"> rate falls to <img src="https://latex.codecogs.com/png.latex?24%5C%25"> once rewrite prompts are searched against the detector <span class="citation" data-cites="Ren2026Hitting">(Ren, Raghavan, and Garg 2026)</span> and below <img src="https://latex.codecogs.com/png.latex?4%5C%25"> after a commercial humanizer <span class="citation" data-cites="Karr2026Why">(Karr et al. 2026)</span>. And because <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)"> rises with capability, a ban that deters slop today can fail later without any policy change and without any decline in the detector. We ignore that twist for now.</p>
<p>The high-quality authors have a different behaviour threshold. Write <img src="https://latex.codecogs.com/png.latex?V(d)"> for the best rate of expected acceptances per unit time that a high-quality author can achieve at drafting cost <img src="https://latex.codecogs.com/png.latex?d">. High-quality authors abandon AI drafting once <img src="https://latex.codecogs.com/png.latex?(1-%5Cmu)%5C,V(%5Cdelta)"> falls below <img src="https://latex.codecogs.com/png.latex?V(f)">, that is at</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu_H(a)%20=%201%20-%20%5Cfrac%7BV(f)%7D%7BV(%5Cdelta)%7D.%0A"></p>
<p>The two thresholds are strictly ordered: <img src="https://latex.codecogs.com/png.latex?%5Cmu_H(a)%20%3C%20%5Cmu%5E*(a)"> for every <img src="https://latex.codecogs.com/png.latex?a%20%3E%201"> (at <img src="https://latex.codecogs.com/png.latex?a%20=%201"> both are zero). To see this, let <img src="https://latex.codecogs.com/png.latex?t%5E*_%5Cdelta"> be the substance time a high-quality author chooses when drafting with AI. A hand-drafting author could choose that same <img src="https://latex.codecogs.com/png.latex?t%5E*_%5Cdelta">; it is not their optimum, so <img src="https://latex.codecogs.com/png.latex?V(f)"> is at least what it yields, namely <img src="https://latex.codecogs.com/png.latex?V(%5Cdelta)%5C,%5Cfrac%7Bt%5E*_%5Cdelta%20+%20%5Cdelta%7D%7Bt%5E*_%5Cdelta%20+%20f%7D">. The ratio <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7Bt%20+%20%5Cdelta%7D%7Bt%20+%20f%7D"> increases in <img src="https://latex.codecogs.com/png.latex?t">, and a slop author is at <img src="https://latex.codecogs.com/png.latex?t%20=%20t_%7B%5Cmin%7D%20%5Cle%20t%5E*_%5Cdelta">, so their ratio is smaller and their threshold <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*%20=%201%20-%20%5Cfrac%7Bt_%7B%5Cmin%7D%20+%20%5Cdelta%7D%7Bt_%7B%5Cmin%7D%20+%20f%7D"> is larger. In other words: the more substance time we invest in a manuscript, the smaller the share of its cost that AI drafting saves, so the less detection risk we will accept to keep using it. A detector therefore stops high-quality authors from using AI strictly before it stops slop authors. Neither type is ever deterred from <em>submitting</em>: at worst, slop authors revert to hand-drafting, which restores the hand-drafting equilibrium’s spam rate, never less. Being flagged costs a high-quality author a manuscript full of work; it costs a slop author almost nothing.</p>
<div id="cell-fig-ban-capability" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1">mu_fix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb3-2">eqB <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [solve_B(a, mu_fix) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> a_grid]</span>
<span id="cb3-3">a_star <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> P[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu_fix) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (P[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> P[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> P[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>])</span>
<span id="cb3-4">lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e4</span></span>
<span id="cb3-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>):</span>
<span id="cb3-6">    mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> hi)</span>
<span id="cb3-7">    lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (mid, hi) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> mu_sub(mid) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> mu_fix <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> (lo, mid)</span>
<span id="cb3-8">a_H <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sqrt(lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> hi)</span>
<span id="cb3-9"></span>
<span id="cb3-10">fig, axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), sharex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-11">grey <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#888888"</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb3-12">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>grey)</span>
<span id="cb3-13">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqB])</span>
<span id="cb3-14">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"substance time $t_H$"</span>)</span>
<span id="cb3-15">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>grey)</span>
<span id="cb3-16">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqB])</span>
<span id="cb3-17">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coverage $r$"</span>)</span>
<span id="cb3-18">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>grey)</span>
<span id="cb3-19">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqB], label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"detector ban"</span>)</span>
<span id="cb3-20">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].plot([], [], label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"free-for-all"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>grey)</span>
<span id="cb3-21">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"good papers per author-period $Q$"</span>)</span>
<span id="cb3-22">axes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].legend(fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb3-23"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ax <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes:</span>
<span id="cb3-24">    ax.set_xscale(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>)</span>
<span id="cb3-25">    ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AI capability $a$"</span>)</span>
<span id="cb3-26">    ax.axvline(a_star, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#555555"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb3-27">    ax.axvline(a_H, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#555555"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>)</span>
<span id="cb3-28">fig.tight_layout()</span>
<span id="cb3-29">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-ban-capability" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ban-capability-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="should_we_allow_ai_articles_files/figure-html/fig-ban-capability-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;3: The detector ban at a fixed (low) true-positive rate \mu = 0.5 as AI capability a grows, in the panels of Figure&nbsp;2, with the free-for-all in grey for comparison. Vertical lines: where \mu^*(a) passes 0.5 (left) and where \mu_H(a) does (right). Left of the first line the ban deters slop authors from AI drafting, so everyone hand-drafts and the hand-drafting equilibrium of a = 1 persists whatever a is; between the lines it deters only the high-quality authors, who hand-draft while slop authors keep using AI; right of the second it deters nobody and the free-for-all returns."><img src="https://danmackinlay.name/post/should_we_allow_ai_articles_files/figure-html/fig-ban-capability-output-1.png" width="923" height="278" class="figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ban-capability-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: The detector ban at a fixed (low) true-positive rate <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%200.5"> as AI capability <img src="https://latex.codecogs.com/png.latex?a"> grows, in the panels of Figure&nbsp;2, with the free-for-all in grey for comparison. Vertical lines: where <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)"> passes <img src="https://latex.codecogs.com/png.latex?0.5"> (left) and where <img src="https://latex.codecogs.com/png.latex?%5Cmu_H(a)"> does (right). Left of the first line the ban deters slop authors from AI drafting, so everyone hand-drafts and the hand-drafting equilibrium of <img src="https://latex.codecogs.com/png.latex?a%20=%201"> persists whatever <img src="https://latex.codecogs.com/png.latex?a"> is; between the lines it deters only the high-quality authors, who hand-draft while slop authors keep using AI; right of the second it deters nobody and the free-for-all returns.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Three regimes appear as capability grows, with the detector’s true-positive rate held fixed. While <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)"> is still below the detector’s rate (left of the solid line), even slop authors dare not use AI; everyone hand-drafts, and the hand-drafting equilibrium persists whatever <img src="https://latex.codecogs.com/png.latex?a"> is: the ban works, at the price of forfeiting the hours that an AI would have saved. Once <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)"> climbs past the detector (between the lines), the ban’s only behavioural effect is on the wrong people. High-quality authors still find AI not worth the risk and hand-draft, writing one manuscript where they could have drafted three; slop authors draft with AI, submit at full rate, and write off the fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> that is desk-rejected as a cost of doing business. The ban thins the queue here because <img src="https://latex.codecogs.com/png.latex?(1-%5Cmu)"> chokes the inflow of spam, rather than because anyone has stopped spamming; the good authors’ fewer, fuller papers raise the average quality of what is printed, not the count. Once <img src="https://latex.codecogs.com/png.latex?%5Cmu_H(a)"> climbs past the detector too (right of the dashed line), the ban deters nobody. Both author types draft with AI, the detector desk-rejects the same fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of each type’s manuscripts, and so long as the queue is congested, the review slots freed by desk rejection compensate for the good manuscripts lost, so the free-for-all equilibrium returns unchanged. Read against the detector instead, at fixed capability, the same three regimes run in reverse: nothing below <img src="https://latex.codecogs.com/png.latex?%5Cmu_H">, a tax on the compliant between <img src="https://latex.codecogs.com/png.latex?%5Cmu_H"> and <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*">, and the hand-drafting equilibrium above <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*">. If we instead hold capability <img src="https://latex.codecogs.com/png.latex?a"> fixed but raise the true-positive rate <img src="https://latex.codecogs.com/png.latex?%5Cmu">, the same three regimes appear in the opposite order: below <img src="https://latex.codecogs.com/png.latex?%5Cmu_H"> the ban changes nothing; between <img src="https://latex.codecogs.com/png.latex?%5Cmu_H"> and <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> it taxes only the compliant; above <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> the hand-drafting equilibrium returns.</p>
<p>The standard objection to a ban is that it takes the time savings of AI drafting away from the compliant. My model agrees that the ban forfeits those savings, but so does the free-for-all, which converts them into extra manuscripts that mostly go unread. The savings are only worth having under a policy that can turn them into published papers, and that is what the submission fee of Policy C does.</p>
</section>
<section id="policy-c-submission-fees" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="policy-c-submission-fees"><span class="header-section-number">4</span> Policy C — submission fees</h2>
<p>Under this policy, we charge <img src="https://latex.codecogs.com/png.latex?P"> per submission, allow anything, and triage and review whoever pays for it. To be clear: this is <em>not</em> on the table for the Alignment Journal, but is included as a baseline.</p>
<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>As you read this section you will note that we are talking about <em>princely</em> submission fees, relative to the academic norm. This is because of the bonkers economy of academic publishing which pours vast amounts of labour into producing papers, much of which is effectively un-budgeted.</p>
<p>I could estimate the “value” of the paper in a few ways — the effective of a paper in getting tenure, the amount of submission fee that an author seems prepared pay, or the approximate value the papers have as represented by the dollar cost of the hours that go into a typical one.</p>
<p>I chose the last here, since it seems the easiest to estimate (I’ve written papers), but in some ways this leads to surprisingly large valuations: it shows that papers are extremely expensive in time cost, even though academics tend to have smaller cash budgets to pay in submission fees than this time cost would indicate.</p>
<p>Whether anyone would be prepared to pay fees obviously depends on not just their time cost, which is sunk, but the “value” of the venue (conference, journal) to which they submit, which is also nebulous. For reference, submitting to and attending a top tier conference can cost ~USD5000 in travel, registration, and accommodation fees.</p>
</div>
</div>
</div>
<p>An economist would say that submission fees are <em>always</em> charged, in waiting time if not in cash, and a journal chooses a mix of the two whether or not it admits to pricing <span class="citation" data-cites="Cotton2013Submission">(Cotton 2013)</span>. This doesn’t change how it feels to be charged such a fee, nor the fact that many authors have no cash budget to pay it. Nonetheless…</p>
<p>Recall that a submission’s expected private return is <img src="https://latex.codecogs.com/png.latex?b"> times its acceptance probability: <img src="https://latex.codecogs.com/png.latex?r%5C,b%5C,%5CLambda_L"> for slop, and roughly <img src="https://latex.codecogs.com/png.latex?r%5C,b%5C,%5CLambda_H"> — much larger — for a high-quality paper. Any fee between those two numbers is <em>separating</em>: submitting slop now loses money, while submitting good work still pays. Nobody is forced back to hand-drafting, because the fee remains the same however the paper was drafted — which is fine, since here at least we don’t care about provenance except as a proxy for spamminess.</p>
<p>If the submission fee is too low, slop authors keep entering until congestion drives their expected return down to the fee, <img src="https://latex.codecogs.com/png.latex?r%5C,b%5C,%5CLambda_L%20=%20P">. That pins the coverage rate at <img src="https://latex.codecogs.com/png.latex?r%20=%20P/(b%5C,%5CLambda_L)">: slop manuscripts fill every review slot that high-quality manuscripts do not. Raising <img src="https://latex.codecogs.com/png.latex?P"> buys back coverage, but the accepted papers stay mostly slop until the fee exceeds the value a slop author places on each submission.</p>
<p>Full exclusion of slop is sadly expensive. A slop author facing an uncongested queue (<img src="https://latex.codecogs.com/png.latex?r%20=%201">) expects <img src="https://latex.codecogs.com/png.latex?b%5C,%5CLambda_L"> per submission, so the fee at which slop authors stop submitting entirely is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%5E*%20%5C;=%5C;%20b%5C,%5CLambda%5Cbig(h%5C,(s_L%20-%20%5Cbar%7Bs%7D)%5Cbig),%0A"></p>
<p>about an eighth of the private value of an acceptance (using my parameters). <img src="https://latex.codecogs.com/png.latex?P%5E*"> does not depend on <img src="https://latex.codecogs.com/png.latex?a">: the review depth <img src="https://latex.codecogs.com/png.latex?h"> is fixed, so a slop manuscript’s acceptance odds remain the same however large the flood. The fee must handle all the deterring, which gives this policy the nifty feature of excluding slop authors with a single fee setting even as capabilities advance, unlike the ban, which we have to re-check as <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)"> moves. At <img src="https://latex.codecogs.com/png.latex?P%5E*"> every slop author stops submitting, at least if they are rational expected-value maximizers.</p>
<div id="cell-fig-compare" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.4</span>))</span>
<span id="cb4-2">eqC <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [solve_C(a) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> a_grid]</span>
<span id="cb4-3">ax.plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#b5541c"</span>,</span>
<span id="cb4-4">        label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A: free-for-all"</span>)</span>
<span id="cb4-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> mu, ls <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ((<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span>)):</span>
<span id="cb4-6">    ax.plot(a_grid, [solve_B(a, mu)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> a_grid], ls,</span>
<span id="cb4-7">            color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2d6e8e"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"B: detector, $</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\\</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">mu=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mu<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>)</span>
<span id="cb4-8">ax.plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqC], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#3a7d44"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C: fee"</span>)</span>
<span id="cb4-9">ax.set_xscale(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>)</span>
<span id="cb4-10">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AI capability $a$"</span>)</span>
<span id="cb4-11">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"good papers per author-period $Q$"</span>)</span>
<span id="cb4-12">ax.legend(fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb4-13">fig.tight_layout()</span>
<span id="cb4-14">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-compare" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-compare-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="should_we_allow_ai_articles_files/figure-html/fig-compare-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure&nbsp;4: The three policies as AI capability grows. Detector at true-positive rate 0.6 and 0.95; fee at the full-exclusion level P^*. Only the fee regime turns more capable AI into more good papers published."><img src="https://danmackinlay.name/post/should_we_allow_ai_articles_files/figure-html/fig-compare-output-1.png" width="613" height="325" class="figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-compare-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: The three policies as AI capability grows. Detector at true-positive rate <img src="https://latex.codecogs.com/png.latex?0.6"> and <img src="https://latex.codecogs.com/png.latex?0.95">; fee at the full-exclusion level <img src="https://latex.codecogs.com/png.latex?P%5E*">. Only the fee regime turns more capable AI into more good papers published.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Interestingly, under the fee policy, more AI capability is simply good. High-quality authors keep all the time AI saves them and spend it writing more manuscripts. Huzzah! The journal, its queue protected, has the capacity to review and publish them; and the flow of good papers into print more than doubles across the plotted range. Under the other regimes, we burn that time surplus: the ban burns it by forcing high-quality researchers back to hand-drafting, and the free-for-all converts it into unread submissions. The fee curve in the figure is computed at <img src="https://latex.codecogs.com/png.latex?P=P%5E*">, the price at which slop authors stop submitting, so no slop reaches the accepted papers at all. At any lower fee, slop authors keep submitting until the queue congestion lowers their expected return to the fee, so slop manuscripts fill every review slot the high-quality manuscripts leave free, and reviewers still accept some of those.</p>
<p>The fee’s lead has a second component that <img src="https://latex.codecogs.com/png.latex?Q"> does not show: the share of what gets printed that is high-quality. We can understand the gap as a mismatch between the proxy (was AI used to write this?) and the true target (was this article high-quality?). The best the ban can do, at any true-positive rate, is restore the hand-drafting equilibrium, which at these parameters means about <img src="https://latex.codecogs.com/png.latex?58%5C%25"> high-quality articles; a fee at <img src="https://latex.codecogs.com/png.latex?P%5E*"> removes <em>all</em> slop from the queue, so the share is <img src="https://latex.codecogs.com/png.latex?100%5C%25">. An entailment of that calibration is that the journal was hypothetically going to be about <img src="https://latex.codecogs.com/png.latex?40%5C%25"> bullshit in the absence of AI drafting. That number follows from parameter choices I made for <img src="https://latex.codecogs.com/png.latex?%5Clambda">, <img src="https://latex.codecogs.com/png.latex?s_L"> and <img src="https://latex.codecogs.com/png.latex?h">, which, as I said, are not crazy. Readers who believe <em>their</em> journal is purer than that should raise <img src="https://latex.codecogs.com/png.latex?%5Clambda"> or <img src="https://latex.codecogs.com/png.latex?h"> and watch the fee’s lead on that share shrink accordingly: the fee’s advantage arises from the hand-drafted slop it eliminates, so the dirtier we think journals already are, the stronger the case for pricing submission over policing provenance, and vice versa.</p>
<p>Of course, many journals cannot charge submission fees, for reasons of custom and equity, and because no matter how eloquent the justification it will still look like predatory-journal behaviour; the Alignment Journal is not exempt. The deadweight losses of dealing with spam are still imposed on <em>someone</em>, and in fact authors bear them, paying in lost time queuing for a review slot: with cash fees near zero, first-response delay is the de facto submission price <span class="citation" data-cites="Azar2005Review">(Azar 2005)</span>. So Policy C is useful as a benchmark: it charges for review slots in cash rather than in waiting time, which is more efficient in the raw economic sense as well as feeling better for not wasting time when we have a perfectly good substitute. The gap between it and the best policy we could feasibly adopt is the price we pay for dwelling in the earthly realm of imperfect maximizers of expected-utility and within departmental budget constraints.</p>
<p>A journal that did go this way would need to do some work to avoid moral hazard, or the perception of it. For example, the journal could do so by optimizing for transparency, demonstrably spending the fee revenue on reviewing.</p>
<p>The moral hazards look different for different types of fees. A fee charged on acceptance pays the journal for accepting, which is the predatory-journal model. A flat submission fee pays the journal either way, so the decision itself is financially neutral, but if they pay reviewers they end up ahead when <em>rejecting</em>. A fee refunded on acceptance charges bad papers more than good ones in expectation, but the journal keeps the cash only when it rejects, which is the opposite moral hazard.<sup>7</sup> One fix is for forfeited fees to go to a third party, such as a disinterested charity, so that the journal has no stake in its own academic decisions. Elaborations are left as homework.</p>
</section>
<section id="policy-d-authors-pay-to-skip-the-detector" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="policy-d-authors-pay-to-skip-the-detector"><span class="header-section-number">5</span> Policy D — authors pay to skip the detector</h2>
<p>Authors could pay a fee <img src="https://latex.codecogs.com/png.latex?P"> per manuscript to have it reviewed without passing through the detector. (Once again, not on the table for the Alignment Journal at the current time). Manuscripts that do not pay go through Policy B: the detector runs, and what it flags is desk-rejected. Hand-drafted manuscripts are never flagged, so their authors have no reason to pay. The fee is only ever paid by authors who drafted with AI and would rather not gamble on the detector. It imposes Policy C’s submission fee, but charges only AI drafters. Each manuscript now has three routes: draft by hand and pay nothing, draft with AI and risk desk rejection, or draft with AI and pay the fee.</p>
<p>Consider the slop authors first. A slop manuscript’s acceptance odds do not depend on how it was drafted, so if it reaches review it is worth <img src="https://latex.codecogs.com/png.latex?r%5C,b%5C,%5CLambda_L"> to its author however it got there. An author who drafts with AI and does not pay has a fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of their manuscripts desk-rejected, which costs them <img src="https://latex.codecogs.com/png.latex?%5Cmu%5C,%20r%5C,b%5C,%5CLambda_L"> per manuscript in expectation. So paying the fee beats risking desk rejection only when the fee is less than <img src="https://latex.codecogs.com/png.latex?%5Cmu%5C,%20r%5C,b%5C,%5CLambda_L">. Paying the fee beats drafting by hand only when the fee is less than <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*%5C,%20r%5C,b%5C,%5CLambda_L">, by the same arithmetic that gave us <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> under the ban. So a slop author pays the fee only when</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%20%5C;%3C%5C;%20%5Cmin(%5Cmu,%20%5Cmu%5E*)%5C;%20r%5C,b%5C,%5CLambda_L.%0A"></p>
<p>We call the right-hand side the <em>floor</em> fee, <img src="https://latex.codecogs.com/png.latex?%5Cunderline%7BP%7D%20=%20%5Cmin(%5Cmu,%20%5Cmu%5E*)%5C,%20r%5C,b%5C,%5CLambda_L">: below that price every slop author pays to skip the detector, and above it none does. Any fee the journal wants slop authors to avoid must be at least that high. At a fee above the floor, slop authors ignore the exemption option and behave as under Policy B. Below <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> they draft with AI and submit anyway, writing off the fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of their manuscripts that is desk-rejected as a cost of doing business. Above <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> they draft by hand, are never flagged, and submit at the hand-drafting equilibrium rate. The floor is lower than Policy C’s full-exclusion fee <img src="https://latex.codecogs.com/png.latex?P%5E*"> by the factor <img src="https://latex.codecogs.com/png.latex?%5Cmin(%5Cmu,%20%5Cmu%5E*)%5C,r">. Skipping the detector is worth exactly the fee it replaces, <img src="https://latex.codecogs.com/png.latex?%5Cmu%5C,r%5C,b%5C,%5CLambda_L"> for slop, and in a congested queue a submission is reviewed only with probability <img src="https://latex.codecogs.com/png.latex?r">, which scales its expected return and so the fee an author will pay (<img src="https://latex.codecogs.com/png.latex?P%5E*"> was defined at <img src="https://latex.codecogs.com/png.latex?r%20=%201">). That is the floor, not the fee the journal ends up charging. The optimal exemption fee comes out <em>above</em> <img src="https://latex.codecogs.com/png.latex?P%5E*"> at my parameters, as we see in a moment, because it is set by congestion among the high-quality authors rather than by what it takes to deter slop.</p>
<p>The high-quality authors need to estimate</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmax_%7Bt%20%5Cge%20t_%7B%5Cmin%7D%7D%20%5C;%0A%5Cfrac%7Br%5C,b%5C,%5CLambda%5Cbig(h%5C,(%5Csqrt%7Bt%7D%20-%20%5Cbar%7Bs%7D)%5Cbig)%20-%20P%7D%7Bt%20+%20%5Cdelta%7D,%0A"></p>
<p>which is a fancified version of the same calculation in Policy C, differing in a few elaborations.</p>
<p>First, the coverage rate <img src="https://latex.codecogs.com/png.latex?r"> no longer cancels. The fee is a fixed sum while the return scales with <img src="https://latex.codecogs.com/png.latex?r">, so the amount an author writes now depends on how congested the queue is, and that congestion depends on how much everyone writes. The equilibrium is the point where the two agree. It is unique: a higher <img src="https://latex.codecogs.com/png.latex?r"> makes the fee smaller relative to the return, which makes authors write more, which lengthens the queue and lowers <img src="https://latex.codecogs.com/png.latex?r"> again. Second, they pay only while the exemption is worth buying. At a given substance choice, paying the fee beats risking desk rejection only when <img src="https://latex.codecogs.com/png.latex?P%20%3C%20%5Cmu%5C,%20r%5C,b%5C,%5CLambda_H">. Call this the <em>cap</em>, <img src="https://latex.codecogs.com/png.latex?%5Coverline%7BP%7D%20=%20%5Cmu%5C,%20r%5C,b%5C,%5CLambda_H">: the detector’s fee-equivalent for a good manuscript, and therefore the highest fee the high-quality authors will pay. If the detector rarely catches anyone, nobody will pay to skip it. For those who do pay, the fee acts like an extra drafting cost. It pushes toward fewer, more substantial manuscripts, the opposite of what cheap drafting did in the first-order condition above.</p>
<p>Which fee? Think of the review budget as a road with a fixed number of lanes. Every submission takes a review slot, and when the queue is congested (<img src="https://latex.codecogs.com/png.latex?r%20%3C%201">) one more submission pushes some other manuscript out unread. If the pushed-out manuscript was good, the journal loses a good paper, and the author who did the pushing pays nothing for that loss. Economists call this a <em>congestion externality</em>, and the standard remedy is a <em>congestion charge</em>: we bill each author for the damage their submission does to everyone else’s work. In this model, the charge is easy to write down. In the congested case, the throughput is <img src="https://latex.codecogs.com/png.latex?Q%20=%20M%5C,A_H/N">, where <img src="https://latex.codecogs.com/png.latex?A_H"> is the number of accepted high-quality manuscripts per period and <img src="https://latex.codecogs.com/png.latex?N"> is the length of the queue. One more high-quality manuscript adds <img src="https://latex.codecogs.com/png.latex?%5CLambda_H"> to <img src="https://latex.codecogs.com/png.latex?A_H"> and one slot to <img src="https://latex.codecogs.com/png.latex?N">, so it changes <img src="https://latex.codecogs.com/png.latex?Q"> by <img src="https://latex.codecogs.com/png.latex?r%5C,%5CLambda_H%20-%20Q/N">. The author counts only the first term—the paper they might get accepted. The second term accounts for the good papers they displace, the costs of which fall on the other high-quality authors. A fee equal to the second term, converted to money at <img src="https://latex.codecogs.com/png.latex?b"> per paper, makes the author’s calculation match the journal’s, and that is the optimal fee.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%5E%5Ccirc%20%5C;=%5C;%20b%5C,%5Cfrac%7BQ%7D%7BN%7D%20%5C;=%5C;%20b%5C;r%5C,%5Csigma_H%5C,%5CLambda_H,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Csigma_H%20=%20%5Clambda%20n_H%20/%20N"> is the high-quality share of the queue. In words: we calculate the return on a high-quality submission, discounted by the share of the queue that is high-quality. We must also set the fee above the floor <img src="https://latex.codecogs.com/png.latex?%5Cunderline%7BP%7D">, so that slop authors don’t pay it, and yet below the cap <img src="https://latex.codecogs.com/png.latex?%5Coverline%7BP%7D">, so that high-quality authors do. That gives</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP_D%20%5C;=%5C;%20%5Cmin%5C!%5CBig%5C%7B%5Cmax%5C%7BP%5E%5Ccirc,%5C;%20%5Cunderline%7BP%7D%5C%7D,%5C;%20%5Coverline%7BP%7D%5CBig%5C%7D.%0A"> Everything on the right depends on the equilibrium, and the equilibrium depends on the fee, so we solve this by numerically iterating to convergence. Is the formula right? Figure&nbsp;5 checks it the slow way: we hold <img src="https://latex.codecogs.com/png.latex?a"> and <img src="https://latex.codecogs.com/png.latex?%5Cmu"> fixed, try every fee <img src="https://latex.codecogs.com/png.latex?P"> from zero upward, solve for the equilibrium at each, and plot the resulting throughput <img src="https://latex.codecogs.com/png.latex?Q">. The fee at which that curve peaks is the best fee the journal could charge, found by search rather than by formula; the solid vertical line marks the formula’s predicted best fee, and they agree. The folded cell after the figure repeats that comparison at four capability levels, from <img src="https://latex.codecogs.com/png.latex?2"> to <img src="https://latex.codecogs.com/png.latex?100">, and six true-positive rates, from <img src="https://latex.codecogs.com/png.latex?0.24"> to <img src="https://latex.codecogs.com/png.latex?0.993">, and reports the largest amount by which charging the formula fee falls short of the best fee found by search. It is zero at every point of the grid. When the congestion charge <img src="https://latex.codecogs.com/png.latex?P%5E%5Ccirc"> is below the cap, the best fee is <img src="https://latex.codecogs.com/png.latex?P%5E%5Ccirc"> itself. When <img src="https://latex.codecogs.com/png.latex?P%5E%5Ccirc"> would exceed the cap, the best the journal can do is charge the cap: any higher and the high-quality authors stop paying, take their chances with the detector, and <img src="https://latex.codecogs.com/png.latex?Q"> falls off the cliff visible in Figure&nbsp;5. At my parameters, the congestion charge is always above the floor, so the floor never matters. When the queue is uncongested, nobody displaces anybody, the charge is zero, and <img src="https://latex.codecogs.com/png.latex?P_D"> drops to the floor, which at <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%201"> is <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*%5C,b%5C,%5CLambda_L">, a fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> of the <img src="https://latex.codecogs.com/png.latex?P%5E*"> of Policy C, because a slop author who cannot slip past the detector still has hand-drafting to fall back on.</p>
<div id="exemption-fee-solver" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> respond_D(r, a, mu, fee, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P):</span>
<span id="cb5-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Best responses at coverage r when a fee buys exemption from the detector.</span></span>
<span id="cb5-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Each manuscript takes one of three routes: hand-draft free, AI-draft and risk</span></span>
<span id="cb5-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    the desk, or AI-draft and pay. Returns queue load and accepted count per type."""</span></span>
<span id="cb5-5">    delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> a</span>
<span id="cb5-6">    ret <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_H(T_GRID, p)</span>
<span id="cb5-7">    routes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(ret <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (T_GRID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>]), p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hand"</span>),</span>
<span id="cb5-8">              ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ret <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (T_GRID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), delta, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"risk"</span>),</span>
<span id="cb5-9">              ((ret <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fee) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (T_GRID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), delta, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pay"</span>)]</span>
<span id="cb5-10">    obj, dH, wH, H_mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(routes, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> o: o[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>())</span>
<span id="cb5-11">    tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T_GRID[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(np.argmax(obj))]</span>
<span id="cb5-12">    loadH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dH) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wH</span>
<span id="cb5-13">    piL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_L(p)</span>
<span id="cb5-14">    routes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(piL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>]), p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f"</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hand"</span>),</span>
<span id="cb5-15">              ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> piL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), delta, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"risk"</span>),</span>
<span id="cb5-16">              ((piL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fee) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta), delta, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pay"</span>)]</span>
<span id="cb5-17">    _, dL, wL, L_mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(routes, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> o: o[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb5-18">    loadL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lam"</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t_min"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dL) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> wL</span>
<span id="cb5-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(loadH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>loadH, loadL<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>loadL, aH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>loadH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_H(tH, p),</span>
<span id="cb5-20">                aL<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>loadL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_L(p), tH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tH, H_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>H_mode, L_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>L_mode)</span>
<span id="cb5-21"></span>
<span id="cb5-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> solve_D(a, mu, fee, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P, iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>):</span>
<span id="cb5-23">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Exemption fee: coverage no longer cancels from anyone's problem, so bisect</span></span>
<span id="cb5-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    for the fixed point in r. At a jump in either type's response that type</span></span>
<span id="cb5-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    mixes, so blend the two sides of the jump in the proportion that fills the queue."""</span></span>
<span id="cb5-26">    M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"R"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"h"</span>]</span>
<span id="cb5-27">    lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb5-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(iters):</span>
<span id="cb5-29">        mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> hi)</span>
<span id="cb5-30">        q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> respond_D(mid, a, mu, fee, p)</span>
<span id="cb5-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadH"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadL"</span>])) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> mid:</span>
<span id="cb5-32">            lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mid</span>
<span id="cb5-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb5-34">            hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mid</span>
<span id="cb5-35">    r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> hi)</span>
<span id="cb5-36">    qlo, qhi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> respond_D(lo, a, mu, fee, p), respond_D(hi, a, mu, fee, p)</span>
<span id="cb5-37">    Nlo, Nhi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qlo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadH"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> qlo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadL"</span>], qhi[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadH"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> qhi[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadL"</span>]</span>
<span id="cb5-38">    congested <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span></span>
<span id="cb5-39">    alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.clip((M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Nhi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (Nlo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Nhi), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> congested <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(Nlo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Nhi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb5-40">    mix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> k: alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> qlo[k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> qhi[k]</span>
<span id="cb5-41">    aH, aL, N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mix(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aH"</span>), mix(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aL"</span>), mix(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadH"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> mix(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loadL"</span>)</span>
<span id="cb5-42">    sH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> qlo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aH"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(qlo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tH"</span>])</span>
<span id="cb5-43">                       <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> qhi[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aH"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(qhi[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tH"</span>])) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> aH</span>
<span id="cb5-44">    W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> aL) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> aL <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb5-45">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r, W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>W, thruH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> aH, tH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>mix(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tH"</span>), sH<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sH, N<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>N,</span>
<span id="cb5-46">                H_pays<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>qlo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H_mode"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pay"</span>,</span>
<span id="cb5-47">                floor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(mu, mu_star(a, p)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_L(p),</span>
<span id="cb5-48">                charge<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> aH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> N <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> congested <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb5-49"></span>
<span id="cb5-50"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fee_D(a, mu, p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P, eps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-3</span>, iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>):</span>
<span id="cb5-51">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Q-optimal fee: the fixed point of P = max(b Q / N, floor + eps), capped at</span></span>
<span id="cb5-52"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    the largest fee the high-quality authors will pay rather than risk the desk."""</span></span>
<span id="cb5-53">    fee <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> accept_L(p)</span>
<span id="cb5-54">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(iters):</span>
<span id="cb5-55">        q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> solve_D(a, mu, fee, p)</span>
<span id="cb5-56">        new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"charge"</span>], q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"floor"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eps)</span>
<span id="cb5-57">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fee) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-7</span>:</span>
<span id="cb5-58">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb5-59">        fee <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (fee <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> new)</span>
<span id="cb5-60">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> solve_D(a, mu, fee, p)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H_pays"</span>]:</span>
<span id="cb5-61">        lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, fee</span>
<span id="cb5-62">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>):</span>
<span id="cb5-63">            mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> hi)</span>
<span id="cb5-64">            lo, hi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (mid, hi) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> solve_D(a, mu, mid, p)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H_pays"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> (lo, mid)</span>
<span id="cb5-65">        fee <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lo</span>
<span id="cb5-66">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> fee</span></code></pre></div></div>
</details>
</div>
<div id="cell-fig-toll" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> matplotlib.lines <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Line2D</span>
<span id="cb6-2">fees <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">251</span>)</span>
<span id="cb6-3">fig, axq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.2</span>))</span>
<span id="cb6-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> mu, col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ((<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2d6e8e"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#3a7d44"</span>)):</span>
<span id="cb6-5">    sweep <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [solve_D(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, mu, f) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> fees]</span>
<span id="cb6-6">    axq.plot(fees, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sweep], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"$</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\\</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">mu = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mu<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>)</span>
<span id="cb6-7">    fd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fee_D(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, mu)</span>
<span id="cb6-8">    axq.axvline(fd, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb6-9">    axq.axvline(solve_D(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, mu, fd)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"floor"</span>], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb6-10">axq.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"good papers per author-period $Q$"</span>)</span>
<span id="cb6-11">axq.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fee $P$"</span>)</span>
<span id="cb6-12">handles, labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> axq.get_legend_handles_labels()</span>
<span id="cb6-13">handles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> [Line2D([], [], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#555555"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>),</span>
<span id="cb6-14">            Line2D([], [], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#555555"</span>, ls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">":"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)]</span>
<span id="cb6-15">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"$P_D$"</span>, <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"floor </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">underline{P}</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>]</span>
<span id="cb6-16">axq.legend(handles, labels, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb6-17">fig.tight_layout()</span>
<span id="cb6-18">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-toll" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-toll-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="should_we_allow_ai_articles_files/figure-html/fig-toll-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure&nbsp;5: The exemption fee at a = 100: throughput Q against the fee P, at two true-positive rates. Solid verticals mark the formula P_D; dotted verticals mark the floor \underline{P}, below which slop authors pay the fee too. The cliff on the right of each curve is where high-quality authors stop paying and take their chances with the detector."><img src="https://danmackinlay.name/post/should_we_allow_ai_articles_files/figure-html/fig-toll-output-1.png" width="613" height="315" class="figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-toll-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: The exemption fee at <img src="https://latex.codecogs.com/png.latex?a%20=%20100">: throughput <img src="https://latex.codecogs.com/png.latex?Q"> against the fee <img src="https://latex.codecogs.com/png.latex?P">, at two true-positive rates. Solid verticals mark the formula <img src="https://latex.codecogs.com/png.latex?P_D">; dotted verticals mark the floor <img src="https://latex.codecogs.com/png.latex?%5Cunderline%7BP%7D">, below which slop authors pay the fee too. The cliff on the right of each curve is where high-quality authors stop paying and take their chances with the detector.
</figcaption>
</figure>
</div>
</div>
</div>
<div id="fee-check" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Check the formula fee against a brute-force sweep</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1">worst <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb7-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a_chk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>):</span>
<span id="cb7-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> mu_chk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.24</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.993</span>):</span>
<span id="cb7-4">        best <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(solve_D(a_chk, mu_chk, f)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> fees)</span>
<span id="cb7-5">        got <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> solve_D(a_chk, mu_chk, fee_D(a_chk, mu_chk))[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>]</span>
<span id="cb7-6">        worst <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(worst, (best <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> got) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> best)</span>
<span id="cb7-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"largest throughput shortfall of the formula fee against the sweep: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> worst<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>largest throughput shortfall of the formula fee against the sweep: 0.00%</code></pre>
</div>
</div>
<p>The detector’s true-positive rate does not appear in the congestion charge. In Policy D the detector does one thing: it gives AI-drafting authors a reason to pay by desk-rejecting a fraction <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of AI-drafted, non-fee-paying manuscripts. The true-positive rate enters only the floor and the cap, both of which rise with <img src="https://latex.codecogs.com/png.latex?%5Cmu"> because skipping a better detector is worth more; a better detector widens the range of fees that high-quality authors will pay and slop authors will not. At a low enough true-positive rate, the cap falls below the congestion charge, and the journal can charge only what high-quality authors will still pay. Unlike <img src="https://latex.codecogs.com/png.latex?P%5E*">, this fee changes as <img src="https://latex.codecogs.com/png.latex?a"> grows, because <img src="https://latex.codecogs.com/png.latex?Q"> and <img src="https://latex.codecogs.com/png.latex?N"> do. This is the one place where the average-quality metric <img src="https://latex.codecogs.com/png.latex?W"> would disagree with our overall value metric <img src="https://latex.codecogs.com/png.latex?Q">: <img src="https://latex.codecogs.com/png.latex?W"> is blind to volume as long as the average quality is high, so it would prefer the largest fee the high-quality authors will pay, right up to the cap. Near <img src="https://latex.codecogs.com/png.latex?a%20=%201">, the fee pushes high-quality authors back to hand-drafting, because AI saves them almost nothing there, and Policy D degenerates into Policy B.</p>
<div id="cell-fig-toll-compare" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.4</span>))</span>
<span id="cb9-2">eqD <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {mu: [solve_D(a, mu, fee_D(a, mu)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> a_grid] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> mu <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>)}</span>
<span id="cb9-3">ax.plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqA], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#b5541c"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A: free-for-all"</span>)</span>
<span id="cb9-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> mu, ls <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ((<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span>)):</span>
<span id="cb9-5">    ax.plot(a_grid, [solve_B(a, mu)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> a_grid], ls,</span>
<span id="cb9-6">            color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2d6e8e"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"B: detector, $</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\\</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">mu=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mu<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>)</span>
<span id="cb9-7">    ax.plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqD[mu]], ls,</span>
<span id="cb9-8">            color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#7f4c94"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"D: exemption fee, $</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\\</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">mu=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>mu<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$"</span>)</span>
<span id="cb9-9">ax.plot(a_grid, [q[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thruH"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eqC], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#3a7d44"</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C: submission fee"</span>)</span>
<span id="cb9-10">ax.set_xscale(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log"</span>)</span>
<span id="cb9-11">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AI capability $a$"</span>)</span>
<span id="cb9-12">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"good papers per author-period $Q$"</span>)</span>
<span id="cb9-13">ax.legend(fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb9-14">fig.tight_layout()</span>
<span id="cb9-15">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-toll-compare" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-toll-compare-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="should_we_allow_ai_articles_files/figure-html/fig-toll-compare-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Figure&nbsp;6: The four policies as AI capability grows. Detector and exemption fee at true-positive rate 0.6 and 0.95; submission fee at P^*; exemption fee at P_D. The exemption fee is between the ban and the submission fee at every capability level a."><img src="https://danmackinlay.name/post/should_we_allow_ai_articles_files/figure-html/fig-toll-compare-output-1.png" width="613" height="325" class="figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-toll-compare-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: The four policies as AI capability grows. Detector and exemption fee at true-positive rate <img src="https://latex.codecogs.com/png.latex?0.6"> and <img src="https://latex.codecogs.com/png.latex?0.95">; submission fee at <img src="https://latex.codecogs.com/png.latex?P%5E*">; exemption fee at <img src="https://latex.codecogs.com/png.latex?P_D">. The exemption fee is between the ban and the submission fee at every capability level <img src="https://latex.codecogs.com/png.latex?a">.
</figcaption>
</figure>
</div>
</div>
</div>
<p>The exemption fee lands between the ban and the submission fee at every capability level in Figure&nbsp;6. Above <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> the ban restores the hand-drafting equilibrium, <img src="https://latex.codecogs.com/png.latex?Q%20%5Capprox%200.22"> good papers per author-period at my parameters, regardless of <img src="https://latex.codecogs.com/png.latex?a">; the exemption fee at the same true-positive rate publishes <img src="https://latex.codecogs.com/png.latex?0.39"> at <img src="https://latex.codecogs.com/png.latex?a%20=%20100"> (39 a period per hundred authors, against 22), because high-quality authors keep the time AI saves them and the congestion charge stops them from spending all of it on extra manuscripts. Below <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*">, at <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%200.6">, the exemption fee roughly doubles the ban’s throughput, but both are poor, because the <img src="https://latex.codecogs.com/png.latex?40%5C%25"> of AI-drafted slop manuscripts that the detector misses still fill the queue. What separates the exemption fee from the submission fee is hand-drafted slop: under Policy C slop authors pay whether or not they used AI, so they stop submitting; under Policy D a slop author who drafts by hand pays nothing, is never flagged, and stays in the queue. At <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%201"> every AI drafter pays, and Policy D is Policy C levied on AI drafters alone; the slop authors who draft by hand are the whole difference.</p>
<p>For the Alignment Journal the exemption fee faces the same objection as the submission fee—that we would be charging money from a cash-constrained author base. Only authors who choose to pay are charged. Paying is also a disclosure: a hand-drafted manuscript is never flagged, so the only reason to buy the exemption is that the manuscript was AI-drafted. Most journals now ask authors to declare AI use but can at best partially check the claim; here it arrives with a fee attached, so it is truthful by construction. A false positive still costs a hand-drafting author their manuscript, as under the ban. And the revenue arrives in proportion to the number of AI-drafted manuscripts the journal has to review, so a journal that pays its reviewers could route it straight back into <img src="https://latex.codecogs.com/png.latex?R">.</p>
<p><em>I am indebted to <a href="https://www.lesswrong.com/users/vanessa_kosoy?ref=danmackinlay.name">Vanessa Kosoy</a> for helpful discussions which led to this proposal.</em></p>
</section>
<section id="future-work" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="future-work"><span class="header-section-number">6</span> Future work</h2>
<p>On this analysis the detector ban is a clumsy approximation to a congestion charge, and Policy D makes the charge explicit: what we are charging for is constrained review slots. Whether the Alignment Journal, or any journal, has a reasonable economic basis to adopt the ban depends on two parameters I have only guessed here:</p>
<ul>
<li>the true-positive rate a detector can sustain against motivated rewriting, compared against a <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> we could estimate from the time costs of producing slop; and</li>
<li>how many good AI-drafted papers we would tolerate losing in the range of true-positive rates between the two thresholds.</li>
</ul>
<section id="not-modelled" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="not-modelled"><span class="header-section-number">6.1</span> Not modelled</h3>
<ul>
<li><strong>Diluted review.</strong> Throughout, the journal holds review depth <img src="https://latex.codecogs.com/png.latex?h"> fixed and rations coverage <img src="https://latex.codecogs.com/png.latex?r"> by skipping papers, which is why every collapse above is smooth and every equilibrium unique. A journal that instead reviews everything less carefully puts congestion inside the authors’ incentives, and at some author mixes the decline becomes a fold with hysteresis: a cliff that does not reverse when capability is walked back. That variant has its own post, Diluted review; <span class="citation" data-cites="Bartolucci2026Queue">Bartolucci and Vivo (2026)</span> works the same margin out properly in a queueing model.</li>
<li><strong>Better AI prose.</strong> Everything here assumes AI lowers the cost of writing a manuscript without changing what a reviewer sees in it. Better prose breaks that in two ways. Slop presents more citoms, so <img src="https://latex.codecogs.com/png.latex?s_L"> rises. Real but modest work reads as better than it is, an inflation I assumed away by making citoms and scientillas coincide for good authors. Polished slop and oversold real work alike look better to a reviewer with a persuasive AI polisher, without being any more useful to a reader, so the acceptance gap <img src="https://latex.codecogs.com/png.latex?%5CLambda_H%20-%20%5CLambda_L"> that we need to make fees separate the classes gets narrower. As it closes, no fee separates and no detector helps, and the journal’s problem stops being desk mechanism design and becomes epistemology: telling good from bad at review, with more reviewer time per manuscript.</li>
<li><strong>Better AI science.</strong> The premise that AI can write a paper but not do the science behind it is a statement about current (or really, 2024) capability. Once AI raises the substance of a manuscript as well as its polish, cheap drafting no longer buys only thinner papers: an hour of substance time buys more scientillas, and a slop author’s manuscript need not be slop. The slop-versus-quality distinction the model rests on goes with it, and so do these results.</li>
<li><strong>Harmful slop.</strong> Accepted slop is harmless here; if it instead poisons training corpora <span class="citation" data-cites="Shumailov2023Curse">(Shumailov et al. 2023)</span> or locks in error <span class="citation" data-cites="Qiu2025Lockin">(Qiu et al. 2025)</span>, its value is negative and everything above understates the case for keeping it out.</li>
<li><strong>Many journals.</strong> There is only one journal here; with many, slop authors send their manuscripts to whichever is least strict and there is a race to the bottom, which is a different and worse game.</li>
<li><strong>False positives.</strong> The detector is assumed <em>never</em> to flag a hand-drafted manuscript, which is unlikely in practice; a false positive costs a compliant author a manuscript full of work, so even a small rate changes the outcomes near <img src="https://latex.codecogs.com/png.latex?%5Cmu_H"> and, worse, erodes trust.</li>
<li><strong>Reputations.</strong> If publishing slop were reputationally harmful, the return to slop would change and the amount of spam might also be moderated.</li>
</ul>
</section>
</section>
<section id="ballpark" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="ballpark"><span class="header-section-number">7</span> Ballpark real-world estimates</h2>
<p>The model prices everything in units of <img src="https://latex.codecogs.com/png.latex?b">, the value of one accepted paper to its author, so a fee in dollars needs a guess at <img src="https://latex.codecogs.com/png.latex?b"> in dollars. My best anchor is cost: a typical ICLR paper has historically taken about four researcher-months, and a careerist keeps spending that only if an acceptance is worth at least as much to them, so four researcher-months is a lower bound on <img src="https://latex.codecogs.com/png.latex?b">. At USD 5,000 to USD 15,000 per researcher-month, from a PhD stipend to a fully loaded academic salary, that puts <img src="https://latex.codecogs.com/png.latex?b"> at USD 20,000 to USD 60,000. I will carry both ends through.</p>
<p>Under Policy C the fee at which slop authors stop submitting is <img src="https://latex.codecogs.com/png.latex?P%5E*%20=%20b%5C,%5CLambda_L">, which at my parameters is about an eighth of <img src="https://latex.codecogs.com/png.latex?b">: USD 2,500 to USD 7,500 per submission.</p>
<p>Under Policy D with a Pangram-grade detector, taking the true-positive rate from credible independent tests <span class="citation" data-cites="Russell2025People">(Russell, Karpinska, and Iyyer 2025)</span> as <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%200.993"> (i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?99.3%5C%25">) with no false positives, that rate clears the large-<img src="https://latex.codecogs.com/png.latex?a"> limit, i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*(a)%20%5Cle%200.91%20%3C%20%5Cmu"> for every <img src="https://latex.codecogs.com/png.latex?a">, so slop authors draft by hand, take their chances and never pay the fee. The fee is then a pure congestion charge. It rises slowly with <img src="https://latex.codecogs.com/png.latex?a">, from <img src="https://latex.codecogs.com/png.latex?0.16%5C,b"> at <img src="https://latex.codecogs.com/png.latex?a%20=%202"> to <img src="https://latex.codecogs.com/png.latex?0.20%5C,b"> at <img src="https://latex.codecogs.com/png.latex?a%20=%20100">. Call it a fifth of <img src="https://latex.codecogs.com/png.latex?b">: USD 4,000 to USD 12,000 per AI-drafted submission. The floor is <img src="https://latex.codecogs.com/png.latex?0.05">–<img src="https://latex.codecogs.com/png.latex?0.09%5C,b">, well below that, so slop authors do not pay at any fee in this range. Those dollar figures assume the detector wins the arms race. At the <img src="https://latex.codecogs.com/png.latex?24%5C%25"> that survives a rewrite attack, the exemption fee collapses to <img src="https://latex.codecogs.com/png.latex?0.007%5C,b">, a few hundred dollars, because skipping a detector that catches a quarter of manuscripts is worth little to anyone, and throughput barely improves on the free-for-all (<img src="https://latex.codecogs.com/png.latex?0.07"> against <img src="https://latex.codecogs.com/png.latex?0.06"> good papers per author-period). A detector that slop authors can defeat prices nothing.</p>
<p>The ban has a price too, paid in time rather than money. Above <img src="https://latex.codecogs.com/png.latex?%5Cmu%5E*"> every high-quality author drafts by hand, so each of their manuscripts costs <img src="https://latex.codecogs.com/png.latex?f%20-%20%5Cdelta"> more time than it would with AI, about <img src="https://latex.codecogs.com/png.latex?0.5"> time units at <img src="https://latex.codecogs.com/png.latex?a%20=%20100">. On the same anchor, a paper takes about <img src="https://latex.codecogs.com/png.latex?0.96"> time units and four researcher-months, so the ban costs a good author about two researcher-months per manuscript: roughly half of <img src="https://latex.codecogs.com/png.latex?b">, or USD 10,000 to USD 30,000. That is between two and three times the exemption fee, and the journal does not even collect it. A second way to price the ban gives its fee-equivalent directly: the exemption fee at which a high-quality author would be as well off as under the ban is <img src="https://latex.codecogs.com/png.latex?0.44%5C,b"> at <img src="https://latex.codecogs.com/png.latex?a%20=%20100">, USD 9,000 to USD 26,000, falling to <img src="https://latex.codecogs.com/png.latex?0.19%5C,b"> at <img src="https://latex.codecogs.com/png.latex?a%20=%202">, USD 4,000 to USD 11,000, where AI saves little drafting time and the ban costs little. Either way the ban is a fee of the same order as the exemption fee, levied in time, on the good authors only, and collected by nobody.</p>
<p>The ideal exemption fee face value is higher than the submission fee. Under Policy C the fee only has to keep slop authors out, and once they are gone the queue is uncongested, so there is no congestion to charge for. Under Policy D the slop authors who draft by hand stay, the queue stays congested, and the fee is a congestion charge on the high-quality authors.</p>
<p>Both fees are large next to what journals that do charge actually ask; economics journals charge around USD 100–300 per submission, I believe. Either the going rate is low, or <img src="https://latex.codecogs.com/png.latex?b"> is smaller than I have guessed for most authors, or my <img src="https://latex.codecogs.com/png.latex?%5CLambda_L"> is too high. A journal whose full review lets through fewer than <img src="https://latex.codecogs.com/png.latex?12%5C%25"> of slop manuscripts needs a proportionately smaller fee, since <img src="https://latex.codecogs.com/png.latex?P%5E*"> scales with <img src="https://latex.codecogs.com/png.latex?%5CLambda_L">.</p>
</section>
<section id="incoming" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="incoming"><span class="header-section-number">8</span> Incoming</h2>
<ul>
<li><a href="https://madebyhumans.press/?ref=danmackinlay.name">AI Detection for Newsrooms</a></li>
<li>mdmakowsky on the <a href="https://economistwritingeveryday.com/2026/08/22/intellectual-squatting/?ref=danmackinlay.name">Intellectual Squatting – Economist Writing Every Day</a> / <a href="https://statmodeling.stat.columbia.edu/2026/08/27/258/?ref=danmackinlay.name">Andrew Gelman</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Agrawal2019Economics" class="csl-entry">
Agrawal, Gans, and Goldfarb. 2019. <em><a href="https://doi.org/10.7208/chicago/9780226613475.001.0001">The Economics of Artificial Intelligence: An Agenda</a></em>.
</div>
<div id="ref-Akram2023Empirical" class="csl-entry">
Akram. 2023. <span>“<a href="http://arxiv.org/abs/2310.01423">An Empirical Study of AI Generated Text Detection Tools</a>.”</span>
</div>
<div id="ref-Azar2005Review" class="csl-entry">
Azar. 2005. <span>“<a href="https://doi.org/10.1002/j.2325-8012.2005.tb00714.x">The Review Process in Economics: Is It Too Fast?</a>”</span> <em>Southern Economic Journal</em>.
</div>
<div id="ref-Bartolucci2026Queue" class="csl-entry">
Bartolucci, and Vivo. 2026. <span>“<a href="https://arxiv.org/abs/2605.27202">Queue &amp; AI: When Faster Tasks Slow Down the Workflow</a>.”</span>
</div>
<div id="ref-Bauer2026Last" class="csl-entry">
Bauer. 2026. <span>“<a href="https://arxiv.org/abs/2607.26327">The Last Costly Signal: How Generative AI Collapses Competence Signaling and Why Liability Sustains Markets for Expert Services</a>.”</span>
</div>
<div id="ref-Cotton2013Submission" class="csl-entry">
Cotton. 2013. <span>“<a href="https://doi.org/10.1257/aer.103.1.501">Submission Fees and Response Times in Academic Publishing</a>.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Cui2025Signaling" class="csl-entry">
Cui, Dias, and Ye. 2025. <span>“<a href="https://arxiv.org/abs/2509.25054">Signaling in the Age of AI: Evidence from Cover Letters</a>.”</span>
</div>
<div id="ref-Dreber2015Using" class="csl-entry">
Dreber, Pfeiffer, Almenberg, et al. 2015. <span>“<a href="https://doi.org/10.1073/pnas.1516179112">Using Prediction Markets to Estimate the Reproducibility of Scientific Research</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Emi2024Technical" class="csl-entry">
Emi, and Spero. 2024. <span>“<a href="https://arxiv.org/abs/2402.14873">Technical Report on the Pangram AI-Generated Text Classifier</a>.”</span>
</div>
<div id="ref-Fourie2026Messya" class="csl-entry">
Fourie. 2026. <span>“<a href="https://arxiv.org/abs/2607.13844">Messy Research, Certification and the Monetization of Science</a>.”</span>
</div>
<div id="ref-Gartenberg2026More" class="csl-entry">
Gartenberg, Hasan, Murray, et al. 2026. <span>“<a href="https://doi.org/10.1287/orsc.2026.ed.v37.n3">More Versus Better: Artificial Intelligence, Incentives, and the Emerging Crisis in Peer Review</a>.”</span> <em>Organization Science</em>.
</div>
<div id="ref-Glickenhaus2026Pangram" class="csl-entry">
Glickenhaus, Thai, Russell, et al. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2607.27183">Pangram 4 Technical Report</a>.”</span>
</div>
<div id="ref-Karr2026Why" class="csl-entry">
Karr, Khvatskii, Hua, et al. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2608.11256">Why AI Detection Fails for Academic Integrity</a>.”</span>
</div>
<div id="ref-Kosmyna2025Your" class="csl-entry">
Kosmyna, Hauptmann, Yuan, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2506.08872">Your Brain on ChatGPT: Accumulation of Cognitive Debt When Using an AI Assistant for Essay Writing Task</a>.”</span>
</div>
<div id="ref-Kwa2025Measuring" class="csl-entry">
Kwa, West, Becker, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2503.14499">Measuring AI Ability to Complete Long Tasks</a>.”</span>
</div>
<div id="ref-Lee2025Impact" class="csl-entry">
Lee. 2025. <span>“<a href="https://doi.org/10.1145/3706598.3713778">The Impact of Generative AI on Critical Thinking: Self-Reported Reductions in Cognitive Effort and Confidence Effects From a Survey of Knowledge Workers</a>.”</span>
</div>
<div id="ref-Qiu2025Lockin" class="csl-entry">
Qiu, He, Chugh, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2506.06166">The Lock-in Hypothesis: Stagnation by Algorithm</a>.”</span> In.
</div>
<div id="ref-Ren2026Hitting" class="csl-entry">
Ren, Raghavan, and Garg. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2606.25152">Hitting a Moving Target: Test-Time Adaptation for AI Text Detection Under Continual Distribution Shift</a>.”</span>
</div>
<div id="ref-Russell2025People" class="csl-entry">
Russell, Karpinska, and Iyyer. 2025. <span>“<a href="https://doi.org/10.48550/ARXIV.2501.15654">People Who Frequently Use ChatGPT for Writing Tasks Are Accurate and Robust Detectors of AI-Generated Text</a>.”</span>
</div>
<div id="ref-Shumailov2023Curse" class="csl-entry">
Shumailov, Shumaylov, Zhao, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2305.17493">The Curse of Recursion: Training on Generated Data Makes Models Forget</a>.”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Aside: <a href="../notebook/scientific_writing.html">the quality bar for academic prose is sadly low</a>.↩︎</p></li>
<li id="fn2"><p>The obvious alternative, the <em>millikuhn</em>, one thousandth of a paradigm shift, is too coarse for most things a journal publishes.↩︎</p></li>
<li id="fn3"><p><em>pace</em> <span class="citation" data-cites="Kosmyna2025Your">Kosmyna et al. (2025)</span> and <span class="citation" data-cites="Lee2025Impact">Lee (2025)</span>.↩︎</p></li>
<li id="fn4"><p>We could be cute here. The model treats slop as zero scientillas rather than hypothesizing <em>slopons</em>, the antiparticle of the scientilla, which annihilates one on contact over the course of a reader’s afternoon.↩︎</p></li>
<li id="fn5"><p>The detector reads style, not substance, and the two are coming apart: in the neighbouring market for cover letters, an AI writing tool cut the correlation between text quality and callbacks by half <span class="citation" data-cites="Cui2025Signaling">(Cui, Dias, and Ye 2025)</span>.↩︎</p></li>
<li id="fn6"><p>In the more realistic case that it has a nonzero false-positive rate, compliant authors would also be punished.↩︎</p></li>
<li id="fn7"><p>Jess Riedel <a href="https://www.lesswrong.com/posts/granHbSbksXbDXpLD/should-our-journal-publish-ai-drafted-manuscripts?commentId=grzaTznoyessgweFd&amp;ref=danmackinlay.name">points out</a> that the refundable fee which just excludes slop, <img src="https://latex.codecogs.com/png.latex?b%5C,%5CLambda_L/(1-%5CLambda_L)">, induces the same substance time and throughput as the flat fee <img src="https://latex.codecogs.com/png.latex?P%5E*">, while a good author pays only about a quarter as much in expectation. They must front slightly more cash, though, so it does not help authors who have none.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>academe</category>
  <category>AI safety</category>
  <category>collective knowledge</category>
  <category>doing internet</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>how do science</category>
  <category>incentive mechanisms</category>
  <category>innovation</category>
  <category>institutions</category>
  <category>machine learning</category>
  <category>mind</category>
  <category>provenance</category>
  <category>sociology</category>
  <category>technology</category>
  <guid>https://danmackinlay.name/post/should_we_allow_ai_articles.html</guid>
  <pubDate>Mon, 24 Aug 2026 07:31:26 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/mercury_scribe_RP-P-1939-690.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Ergodicity economics</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/ergodicity_economics.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-coin-countress" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-coin-countress-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/coin_countress_RP-P-BI-638.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/coin_countress_RP-P-BI-638.avif" id="fig-coin-countress" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-coin-countress-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>An <a href="../notebook/optionality_as_end.html">optionality-flavoured variant</a> of economic decision theory <span class="citation" data-cites="Peters2019Ergodicity">(Peters 2019)</span>.</p>
<p>A system is <em><a href="../notebook/ergodic_mixing.html">ergodic</a></em> if averaging an observable across many parallel trajectories at a single moment gives the same answer as averaging it along one trajectory over time — i.e., “many people now” and “one person across many years” agree.</p>
<p>Many systems we care about are non-ergodic. Peters’ canonical counter-example is a repeated coin flip bet: each round, my wealth <img src="https://latex.codecogs.com/png.latex?W"> is multiplied by <img src="https://latex.codecogs.com/png.latex?1.5"> if the coin lands heads and by <img src="https://latex.codecogs.com/png.latex?0.6"> if it lands tails. The coin is fair; whether the <em>gamble</em> is fair depends on which average we care about.<sup>1</sup></p>
<p>The <em>ensemble</em> average surveys many of us playing in parallel, at a fixed round <img src="https://latex.codecogs.com/png.latex?n">. The mean multiplier per round is the arithmetic average of the two outcomes, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5BW_n%5D%20%5C;=%5C;%20W_0%20%5Cleft(%5Cfrac%7B1.5%20+%200.6%7D%7B2%7D%5Cright)%5E%7B%5C!n%7D%20%5C;=%5C;%20W_0%20%5Ctimes%201.05%5En%20."></p>
<p>On average, the gamble is better than fair: a 5% edge per round, compounding! That sounds like a free lunch.</p>
<p>The <em>time</em> average follows one of us (e.g., me) playing many rounds in sequence. The law of large numbers grants me heads on about half the rounds and tails on the other half, so</p>
<p><img src="https://latex.codecogs.com/png.latex?W_n%20%5C;%5Capprox%5C;%20W_0%20%5C,(1.5%20%5Ctimes%200.6)%5E%7Bn/2%7D%20%5C;=%5C;%20W_0%20%5Ctimes%200.9%5E%7Bn/2%7D%20%5C;%5Capprox%5C;%20W_0%20%5Ctimes%200.95%5En%20."></p>
<p>On this average, the same gamble is <em>worse</em> than fair: my wealth decays by about 5% per round until I have nothing.</p>
<p>Both calculations are correct; they disagree because repeated multiplication can be understood in terms of the geometric mean of the multipliers, <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B1.5%20%5Ctimes%200.6%7D%20%5Capprox%200.95">, which is less than the arithmetic mean <img src="https://latex.codecogs.com/png.latex?1.05">. That disagreement is all that non-ergodicity means here. After 100 rounds the ensemble mean is <img src="https://latex.codecogs.com/png.latex?1.05%5E%7B100%7D%5C,W_0%20%5Capprox%20130%5C,W_0">, while the median player holds <img src="https://latex.codecogs.com/png.latex?0.9%5E%7B50%7D%5C,W_0">, about half a percent of their starting stake. The mean is propped up by an exponentially rare minority of heads-heavy histories. This is not reassuring to me as a player since I get to live only one history, and that is very unlikely to be the lucky one.</p>
<p>cf Garrabrant’s <a href="https://www.lesswrong.com/s/4hmf7rdfuXDJkxhfg?ref=danmackinlay.name">Geometric Rationality</a>.</p>
<p>Peters argues that under non-ergodicity, expected utility optimizes the wrong average. In our coin flip with <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5B%5Clog%20r%5D%20%3C%200"> almost every trajectory decays exponentially, so the time average of wealth is <img src="https://latex.codecogs.com/png.latex?0">, and in a gamble with <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5B%5Clog%20r%5D%20%3E%200"> it would be <img src="https://latex.codecogs.com/png.latex?%5Cinfty"> instead. This is degenerate either way — useless for ranking one gamble against another. The observable with a finite, <em>informative</em> limit is the growth rate, <img src="https://latex.codecogs.com/png.latex?%5Ctfrac%7B1%7D%7Bn%7D%5Clog(W_n/W_0)%20%5Cto%20%5Cmathbb%7BE%7D%5B%5Clog%20r%5D%20%5Capprox%20%5Clog%200.95"> per round. Almost every trajectory shares this growth rate. The growth rate, unlike wealth, is ergodic: its time average agrees with its expectation, so its expectation describes what happens to a single player, which is the property that prompted us to take expectations in the first place. Ergodicity economics says to optimize <em>that</em> number: expected wealth endorses the coin flip above, time-average growth refuses it. Under multiplicative dynamics, the growth rate is an expected log, so this happens to coincide with log utility. Neatly, the log emerges naturally from the dynamics, rather than being given, so that is nice, I suppose.</p>
<p>Contrast an additive variant of the gamble, with stakes that do not scale with our wealth: heads wins us fifty dollars, tails loses us forty, whatever our balance. The gain per round is then the ergodic observable — <img src="https://latex.codecogs.com/png.latex?(W_n%20-%20W_0)/n%20%5Cto%20%5Ctfrac12(50)%20-%20%5Ctfrac12(40)%20=%205"> dollars along almost every (solvent) trajectory, which is <em>also</em> its expectation — so ranking gambles by time-average growth is the same as ranking them by expected wealth. Maximizing time-average growth over how much we stake is the Kelly criterion, which disfavours any action that risks ejecting the agent from the support of viable trajectories.</p>
<div class="callout callout-style-simple callout-none no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">None</span>Spicy take
</div>
</div>
<div class="callout-body-container callout-body">
<p>Man, this ergodicity economics thing is not wrong <em>per se</em>, but feels like an excessively galaxy-brained way to re-derive the known fact that linear utility in any single good is silly. Suppose I want to “have lots of money”— does this mean that I want to maximize my expected wealth, my expected log-wealth or my median wealth, or minimise the chance that my wealth goes to zero, or to really juice the 0.2-quantile of <img src="https://latex.codecogs.com/png.latex?5W%5E2+%5Csqrt%5B3%5D%7BW%7D">, or whatever other statistic of the distribution? This depends. In general my utility is probably monotonic in money, but it would be surprising if it were linear in the good and indifferent to the tails. My “true” utility from money is not naturally well characterised by some dumb default option like <img src="https://latex.codecogs.com/png.latex?U(W)%20=%20W"> <em>or</em> <img src="https://latex.codecogs.com/png.latex?U(W)%20=%20%5Clog%20W">, it is some idiosyncratic function of the distribution of wealth, maybe even wealth over time. Assuming a linear dollars-to-utility conversion is something they teach you <em>not</em> to do in microeconomics; it’s a degenerate and unrealistic model producing implausible behaviour for the meat sack humans. Anyway, if we care about any interesting quantile of the wealth distribution, we <em>also</em> learn not to take stupid bets that go to zero, without needing to invoke any ergodicity argument. Don’t get me wrong, I think ergodic arguments are important in various ways, but I am not convinced they add substantially to an already enormous pile of reasons that humans shouldn’t take insane bets.</p>
<p>Am I missing something?</p>
</div>
</div>
<section id="connection-to-optionality" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="connection-to-optionality"><span class="header-section-number">1</span> Connection to optionality</h2>
<p>Kelly-style ruin aversion is the load this argument bears in <a href="../notebook/optionality_as_end.html">optionality-as-end</a>: maximizing time-average growth disfavors any action that risks ejecting the agent from the support of viable trajectories, so optionality-flavored behavior falls out without anyone having to add “preserve options” as a separate goal. Whether that observation needs the full ergodicity-economics apparatus, or just the Kelly criterion, is the question the spicy take above asks.</p>
</section>
<section id="incoming" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="incoming"><span class="header-section-number">2</span> Incoming</h2>
<ul>
<li><a href="../notebook/betting.html">Betting</a> for Kelly sizing in practice.</li>
<li><a href="../notebook/martingales.html">Martingales</a> for the fair-game formalism.</li>
<li><a href="../notebook/ergodic_mixing.html">Ergodicity and mixing</a> for what ergodicity means when it is at home in dynamical systems.</li>
</ul>
</section>
<section id="references" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="references"><span class="header-section-number">3</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Arrow1974Environmental" class="csl-entry">
Arrow, and Fisher. 1974. <span>“<a href="https://doi.org/10.2307/1883074">Environmental Preservation, Uncertainty, and Irreversibility</a>.”</span> <em>Quarterly Journal of Economics</em>.
</div>
<div id="ref-Krutilla1967Conservation" class="csl-entry">
Krutilla. 1967. <span>“Conservation Reconsidered.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Peters2019Ergodicity" class="csl-entry">
Peters. 2019. <span>“<a href="https://doi.org/10.1038/s41567-019-0732-0">The Ergodicity Problem in Economics</a>.”</span> <em>Nature Physics</em>.
</div>
<div id="ref-Weisbrod1964Collective" class="csl-entry">
Weisbrod. 1964. <span>“<a href="https://doi.org/10.2307/1879478">Collective-Consumption Services of Individual-Consumption Goods</a>.”</span> <em>Quarterly Journal of Economics</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The formal name for a fair game is a <a href="../notebook/martingales.html"><em>martingale</em></a>, and which observable we demand to be a martingale — <img src="https://latex.codecogs.com/png.latex?W"> itself or <img src="https://latex.codecogs.com/png.latex?%5Clog%20W"> — can be read as an ensemble-versus-time choice.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>economics</category>
  <category>finance</category>
  <category>probability</category>
  <category>utility</category>
  <guid>https://danmackinlay.name/notebook/ergodicity_economics.html</guid>
  <pubDate>Sat, 15 Aug 2026 23:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/coin_countress_RP-P-BI-638.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>LLM sampling stunts</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/llm_sampling_stunts.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-gideon-selects" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-gideon-selects-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/gideon_selects_RP-P-1904-3567.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/gideon_selects_RP-P-1904-3567.avif" id="fig-gideon-selects" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-gideon-selects-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>I’ve noticed a number of interesting sampling tricks for LLMs that seem related. The category in my mind is something like <em>sampling from LLMs in ways that resemble <a href="../notebook/particle_filters.html">classic particle methods</a></em>, i.e., using the kinematics of multiple hypothetical trajectories to inform the next step(s). So! A new notebook!</p>
<section id="sequential-monte-carlo-steering-for-llms" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="sequential-monte-carlo-steering-for-llms"><span class="header-section-number">1</span> Sequential Monte Carlo Steering for LLMs</h2>
<p>The thing that first drew my attention was the <a href="../notebook/sequential_monte_carlo.html">Sequential Monte Carlo</a> (SMC) steering trick for LLMs <span class="citation" data-cites="Lew2023Sequential">(Lew et al. 2023)</span>: instead of decoding one continuation—or keeping a fixed beam—we keep a small population of <img src="https://latex.codecogs.com/png.latex?N"> candidate continuations, score them against a desired constraint or reward, and then repeatedly resample promising candidates as generation proceeds. This can enforce constraints or steer semantic properties at inference time without retraining the base model, and it should look reminiscent of <a href="../notebook/particle_filters.html">particle filtering</a> except the <em>likelihood</em> is a dog’s breakfast and generally intractable in an annoying way.</p>
<p>Relatedly, LLaMPPL <span class="citation" data-cites="Lew2023Sequential">(Lew et al. 2023)</span> claims to be a <a href="../notebook/probabilistic_programming.html">Probabilistic Programming Language</a> for LLMs based on this idea. It uses <a href="../notebook/sequential_monte_carlo.html">Sequential Monte Carlo (SMC)</a> methods to perform probabilistic inference over the outputs of LLMs, which seems logical for prefix sampling; I’m a bit confused about how it works for infill sampling, although they claim both.</p>
<ul>
<li><a href="https://github.com/genlm/llamppl?ref=danmackinlay.name">genlm/llamppl: Probabilistic programming with large language models</a></li>
<li><a href="https://genlm.org/llamppl/?ref=danmackinlay.name">LLaMPPL Docs</a></li>
</ul>
<p>Basic idea: We treat a completion <img src="https://latex.codecogs.com/png.latex?y%20=%20(y_1,%5Cldots,y_T)"> as a trajectory sampled from the stochastic base model <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta(y%5Cmid%20x)">, assuming that it comes from something similar to, but not the same as, the <em>target</em> distribution that we want to hit,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi(y%5Cmid%20x)%20%5Cpropto%20p_%5Ctheta(y%5Cmid%20x)%5C,R(y)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?R(y)"> is a score—e.g., “is valid JSON,” “satisfies this regex/grammar,” “answers both prompts,” “is non-toxic,” or “meets a programmatic verifier.”</p>
<p>We…</p>
<ol type="1">
<li>Maintain <img src="https://latex.codecogs.com/png.latex?N"> partial completions.</li>
<li>Sample the next token (or tokens) for each from the “base” LLM (which in SD-SMC might be a different model entirely).</li>
<li>Give each continuation an incremental weight based on how well it performs under the constraint.</li>
<li>When weights become concentrated, resample: copy high-weight trajectories and discard weak ones.</li>
<li>Continue until completion; return a high-weight sample or sample from the final weighted population.</li>
</ol>
<p>The distinction from simple rejection sampling is that SMC doesn’t wait until the entire response is finished to discover failure. We can prune bad paths early and concentrate compute on promising ones.</p>
<p>The “trick” is worthwhile because sometimes we can write a prefix-aware potential—a function that scores a partial answer, not merely a final answer.</p>
<p>Examples:</p>
<ul>
<li>Strict JSON: incremental parses can bail early from impossible prefixes</li>
<li>Prompt intersection: Likelihood or task score under each of several prompts</li>
<li>Other stuff that doesn’t seem very practical to me.</li>
</ul>
<p>For hard constraints, a particle that makes the remaining task impossible gets weight zero. For soft preferences, the weight rises smoothly to interpolate between “worse” and “better” paths.</p>
<p>The original SMC-steering formulation interprets constrained language generation as posterior inference in a discrete sequence model. It reports capabilities including infilling, syntactic constraints, and “prompt intersection,” at a computational cost comparable to beam search.</p>
<p>LLM vibe-coded pseudo-code example:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1">particles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, cache_i) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N)]  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># text, log_weight, KV cache</span></span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_new_tokens):</span>
<span id="cb1-4">    proposed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb1-5"></span>
<span id="cb1-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> text, logw, cache <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> particles:</span>
<span id="cb1-7">        token, logp, cache2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_next_token(model, prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> text, cache)</span>
<span id="cb1-8">        text2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> decode(token)</span>
<span id="cb1-9"></span>
<span id="cb1-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># potential should assess the prefix / remaining feasibility</span></span>
<span id="cb1-11">        delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> log_potential(text2, t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> log_potential(text, t)</span>
<span id="cb1-12">        proposed.append((text2, logw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> delta, cache2))</span>
<span id="cb1-13"></span>
<span id="cb1-14">    particles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> proposed</span>
<span id="cb1-15">    weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> softmax([logw <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _, logw, _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> particles])</span>
<span id="cb1-16"></span>
<span id="cb1-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> effective_sample_size(weights) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>:</span>
<span id="cb1-18">        particles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> systematic_resample(particles, weights)</span>
<span id="cb1-19">        particles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(text, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, cache) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> text, _, cache <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> particles]</span>
<span id="cb1-20"></span>
<span id="cb1-21">answer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> select_or_sample_final(particles)</span></code></pre></div></div>
<p>A common formulation uses a sequence of potentials <img src="https://latex.codecogs.com/png.latex?%5Cphi_t(y_%7B1:t%7D)">, with the incremental correction:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clog%20w_t%20=%20%5Clog%20w_%7Bt-1%7D%0A+%20%5Clog%20%5Cphi_t(y_%7B1:t%7D)%0A-%20%5Clog%20%5Cphi_%7Bt-1%7D(y_%7B1:t-1%7D)%0A"></p>
<p>That incremental difference prevents us from repeatedly counting the same “goodness” evidence at every token. In practice, this is not the true target potential, because that is almost always super weird in token space.</p>
<p>There are a few adjacent “SMC for LLMs” ideas that have a similar shape.</p>
<dl>
<dt>SMC steering</dt>
<dd>
use particle filtering to steer toward constraints or a reward at inference time, as discussed above. SMC speculative decoding (SMC-SD)
</dd>
<dd>
drafts from a cheaper model, score blocks under an expensive target model, then reweights/resamples rather than rejecting drafts token-by-token. The aim is faster inference while retaining close target-model behaviour <span class="citation" data-cites="Emara2026Faster">(Emara et al. 2026)</span> <a href="https://systems-analysis.ru/eng/Self-consistency_prompting?ref=danmackinlay.name">Self-consistency</a>
</dd>
<dd>
sample many complete reasoning trajectories and majority-vote the answer. This is not sequential resampling, but people sometimes loosely describe it as a particle-like trick. This one has the shape of, and relevance to, <a href="../notebook/ai_reasoning.html">Reasoning LLMs</a> where it is called maj@<img src="https://latex.codecogs.com/png.latex?k">. Power-SMC
</dd>
<dd>
sample from a sharpened sequence distribution <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta(y%5Cmid%20x)%5E%5Calpha">, using parallel particles and token-level importance weights/resampling. It is positioned as a low-latency, training-free alternative to serial Metropolis–Hastings-style sampling <span class="citation" data-cites="Azizi2026PowerSMC">(Azizi et al. 2026)</span> of (in the model’s opinion) higher likelihood completions. NGL I cannot even work out what this one is supposed to do.
</dd>
</dl>
</section>
<section id="looming" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="looming"><span class="header-section-number">2</span> Looming</h2>
<p>I have just been schooled on the prior art: the <a href="https://www.lesswrong.com/posts/bxt7uCiHam4QXrQAA/cyborgism?ref=danmackinlay.name">cyborgists</a> have been doing population-based steering of language models since 2020 under the name <em>looming</em>. HT <a href="https://vgel.me/?ref=danmackinlay.name">Theia Vogel</a>.</p>
<p><a href="https://generative.ink/posts/loom-interface-to-the-multiverse/?ref=danmackinlay.name">Loom</a> is Janus’s tree-based writing interface to base models, which is to say models that continue a text as opposed to answer a chat turn. The original is <a href="https://github.com/socketteer/loom?ref=danmackinlay.name">pyloom</a>; there are descendants such as <a href="https://github.com/cosmicoptima/loom?ref=danmackinlay.name">Loomsidian</a> and Conjecture’s Bonsai, more on the <a href="https://cyborgism.wiki/hypha/loom?ref=danmackinlay.name">cyborgism wiki</a>. The procedure, in Janus’s description, is “manual iterative rejection sampling”. The operator (<em>loomer</em>, let us say) generates <img src="https://latex.codecogs.com/png.latex?N"> continuations from the current node, a paragraph or less each, reads them, picks the one we like, and generates <img src="https://latex.codecogs.com/png.latex?N"> more from there. Janus recommends <img src="https://latex.codecogs.com/png.latex?N"> from 5 to over 100, depending on how fussy the passage is and the loomer’s patience. The interface keeps every rejected branch, so we can go back into history and replay to get different outcomes. (Similarities to <a href="../notebook/agent_harness_design.html#wiring">fan-out</a> in mathematical proving left as an exercise). Figure&nbsp;2 shows a small tree from Janus’s own examples.</p>
<div id="fig-loom-tree" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-loom-tree-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="loom-tree.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: A loom tree over single tokens, redrawn from part of Janus’s adaptive-branching figure in Language models are multiverse generators (2021); the original has more branches. Where the model is nearly sure of the next token there is one candidate per step; where it is unsure there are five. The bold path is a hypothetical loomer’s pick, ours rather than Janus’s."><img src="https://danmackinlay.name/notebook/llm_sampling_stunts_files/mediabag/loom-tree.svg" class="img-fluid figure-img" style="width:100.0%"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-loom-tree-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: A loom tree over single tokens, redrawn from part of Janus’s adaptive-branching figure in <a href="https://generative.ink/posts/language-models-are-multiverse-generators/?ref=danmackinlay.name">Language models are multiverse generators</a> (2021); <a href="https://generative.ink/multiverse/adaptive.png?ref=danmackinlay.name">the original</a> has more branches. Where the model is nearly sure of the next token there is one candidate per step; where it is unsure there are five. The bold path is a hypothetical loomer’s pick, ours rather than Janus’s.
</figcaption>
</figure>
</div>
<p>In the vocabulary of this notebook, this is a particle method with a human defining the selection process instead of a potential. The proposal is generated by a base model <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta">, as in the bootstrap version of SMC steering. The weight function is “black box”; i.e.&nbsp;it is whatever goes on in the loomer’s head. In terms of the pseudo-code above, we replace <code>systematic_resample</code> with “<img src="https://latex.codecogs.com/png.latex?N"> copies of the one I clicked on”.</p>
</section>
<section id="looming-vs-smc" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="looming-vs-smc"><span class="header-section-number">3</span> Looming vs SMC</h2>
<p>Damn but that resembles SMC. There are a few differences however.</p>
<section id="looming-focuses-on-one-lineagge" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="looming-focuses-on-one-lineagge"><span class="header-section-number">3.1</span> Looming focuses on one lineagge</h3>
<p>SMC in the default forward mode resamples <img src="https://latex.codecogs.com/png.latex?N"> survivors from <img src="https://latex.codecogs.com/png.latex?N"> candidates and ideally keeps several distinct ancestries alive, because collapsing onto a single ancestor is indicative of failure for most purposes. Looming collapses onto one ancestor at every step (Figure&nbsp;3) — although the loomer can rewind and replay if desired. Anyway, per default, we are sampling from a different statistical object.</p>
<div id="fig-genealogy" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-genealogy-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="genealogy.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;3: Genealogies of N=4 candidates over four steps. Filled dots have descendants at the end; hollow dots are dead ends. In looming (left) all the candidates at a step share the one parent the loomer kept, so that parent’s Z_k is common to all of them and drops out of the choice. In SMC (right) candidates with different parents compete, so a parent whose children score poorly loses its line, and several lineages survive."><img src="https://danmackinlay.name/notebook/llm_sampling_stunts_files/mediabag/genealogy.svg" class="img-fluid figure-img" style="width:100.0%"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-genealogy-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Genealogies of <img src="https://latex.codecogs.com/png.latex?N=4"> candidates over four steps. Filled dots have descendants at the end; hollow dots are dead ends. In looming (left) all the candidates at a step share the one parent the loomer kept, so that parent’s <img src="https://latex.codecogs.com/png.latex?Z_k"> is common to all of them and drops out of the choice. In SMC (right) candidates with different parents compete, so a parent whose children score poorly loses its line, and several lineages survive.
</figcaption>
</figure>
</div>
<p>Split the text into blocks <img src="https://latex.codecogs.com/png.latex?b_1,%5Cdots,b_K"> between branch points and suppose that the loomer picks candidate <img src="https://latex.codecogs.com/png.latex?i"> with some Boltzmann-rational preference <img src="https://latex.codecogs.com/png.latex?g_k(b_%7B1:k%7D%5E%7B(i)%7D)"> (which is not what a loomer would typically claim to be doing AFAICT) but it is fun to imagine. For large <img src="https://latex.codecogs.com/png.latex?N">, looming draws from the <em>locally</em> normalized distribution</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq(y)%20=%20%5Cprod_%7Bk=1%7D%5EK%20%5Cfrac%7Bp_%5Ctheta(b_k%5Cmid%20b_%7B%3Ck%7D)%5C,g_k(b_%7B1:k%7D)%7D%7BZ_k(b_%7B%3Ck%7D)%7D,%0A%5Cqquad%0AZ_k(b_%7B%3Ck%7D)%20=%20%5Cmathbb%7BE%7D_%7Bp_%5Ctheta%7D%5Cleft%5Bg_k(b_%7B1:k%7D)%5Cmid%20b_%7B%3Ck%7D%5Cright%5D,%0A"></p>
<p>whereas SMC with the same incremental weights targets the <em>globally</em> normalized</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi(y)%20%5Cpropto%20%5Cprod_%7Bk=1%7D%5EK%20p_%5Ctheta(b_k%5Cmid%20b_%7B%3Ck%7D)%5C,g_k(b_%7B1:k%7D).%0A"></p>
<p>Their ratio we can call the <em>distortion</em>,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AD(y)%20=%20%5Cfrac%7Bq(y)%7D%7B%5Cpi(y)%7D%20=%20%5Cfrac%7BZ%7D%7B%5Cprod_%7Bk=1%7D%5EK%20Z_k(b_%7B%3Ck%7D)%7D,%0A%5Cqquad%0AZ%20=%20%5Cmathbb%7BE%7D_%7Bp_%5Ctheta%7D%5Cleft%5B%5Cprod_%7Bk=1%7D%5EK%20g_k(b_%7B1:k%7D)%5Cright%5D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?Z"> is the normalizing constant of <img src="https://latex.codecogs.com/png.latex?%5Cpi">, so the only part that depends on the path is <img src="https://latex.codecogs.com/png.latex?1/%5Cprod_k%20Z_k(b_%7B%3Ck%7D)">. <img src="https://latex.codecogs.com/png.latex?Z_k(b_%7B%3Ck%7D)"> measures how good the options were at step <img src="https://latex.codecogs.com/png.latex?k">. SMC ‘remembers’ that information, in that a particle whose children are mostly duds ends up with few descendants. Looming throws that information away. All <img src="https://latex.codecogs.com/png.latex?N"> candidates at a branch point share one parent, so <img src="https://latex.codecogs.com/png.latex?Z_k(b_%7B%3Ck%7D)"> is the same for all of them and cancels out of the choice; the current prefix never competes against a rival parent prefix. So <img src="https://latex.codecogs.com/png.latex?D"> is large on completions that pass through prefixes where the options were poor, and looming over-weights them. Suppose only 1 continuation in 100 from here is any good. Looming attributes that one continuation the whole probability mass of the step, as if all 100 had been good; SMC would weight the prefix down by a factor of 100 against a prefix whose continuations were all good. Drawing more candidates per branch point does not help: <img src="https://latex.codecogs.com/png.latex?N"> only controls how closely each pick matches <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta%20g_k%20/%20Z_k">, so as <img src="https://latex.codecogs.com/png.latex?N%5Cto%5Cinfty"> looming converges to <img src="https://latex.codecogs.com/png.latex?q">, not <img src="https://latex.codecogs.com/png.latex?%5Cpi">. Looming still finds good text, in that every block it keeps scores well under <img src="https://latex.codecogs.com/png.latex?g_k">; what it cannot do, by selection alone, is favour a prefix <em>because</em> that prefix leads somewhere good.</p>
<p>The most common case of this distortion in practice is <em>constrained decoding</em>, where we need the output to be, say, valid JSON or a match for a regex. The standard method, <em>token masking</em>, works one token at a time: it sets to zero the probability of every next token after which no valid completion exists, renormalises the probabilities of the tokens that remain, and samples from those. <a href="https://github.com/dottxt-ai/outlines?ref=danmackinlay.name">Outlines</a> and <a href="https://github.com/ggml-org/llama.cpp/blob/master/grammars/README.md?ref=danmackinlay.name">llama.cpp’s grammars</a> work this way. Token masking is looming with one-token blocks and <img src="https://latex.codecogs.com/png.latex?g_k"> a 0/1 validity check, except that it needs no candidates: the model already gives a probability for every token in its tokenizer vocabulary, so masking the invalid ones and renormalising gives each step’s distribution exactly. In the <img src="https://latex.codecogs.com/png.latex?N%5Cto%5Cinfty"> case, an exact sampler of <img src="https://latex.codecogs.com/png.latex?q">. Here <img src="https://latex.codecogs.com/png.latex?Z_k"> is the probability the model put on the allowed tokens, so the over-weighted completions are the ones that pass through prefixes where the model mostly wanted to say something invalid. A two-token example shows the size of the effect (Figure&nbsp;4). The first token is <code>a</code> or <code>b</code>, equally likely, and both are allowed. After <code>a</code>, only 10% of the model’s next-token probability lands on valid tokens; after <code>b</code>, 80% does. Conditioned on validity, <img src="https://latex.codecogs.com/png.latex?%5Cpi"> starts with <code>a</code> about 11% of the time (<img src="https://latex.codecogs.com/png.latex?0.05/0.45">). Masking cannot see at the first step that <code>a</code> leads to few valid continuations, so it picks <code>a</code> half the time and then forces a valid token, putting <code>a</code> at 50%, a distortion of about 4.5.</p>
<div id="fig-distortion" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-distortion-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="distortion.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure&nbsp;4: Where the probability mass goes under token masking, in the two-token example. Each bar is the unit interval of probability, divided by width; hatched mass is invalid. \pi keeps the valid mass and rescales it once, so a x gets 0.11. Masking rescales within each step, so a x inherits all of a’s 0.5."><img src="https://danmackinlay.name/notebook/llm_sampling_stunts_files/mediabag/distortion.svg" class="img-fluid figure-img" style="width:100.0%"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-distortion-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Where the probability mass goes under token masking, in the two-token example. Each bar is the unit interval of probability, divided by width; hatched mass is invalid. <img src="https://latex.codecogs.com/png.latex?%5Cpi"> keeps the valid mass and rescales it once, so <code>a x</code> gets 0.11. Masking rescales within each step, so <code>a x</code> inherits all of <code>a</code>’s 0.5.
</figcaption>
</figure>
</div>
<p>The output is valid but drawn from <img src="https://latex.codecogs.com/png.latex?q"> rather than from <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta"> conditioned on validity, and correcting that is the stated motivation for SMC steering <span class="citation" data-cites="Lew2023Sequential Loula2025Syntactic">(Lew et al. 2023; Loula et al. 2025)</span>.</p>
<p>There is an interesting special case where the distortion vanishes (HT Claude for pointing this out). If <img src="https://latex.codecogs.com/png.latex?g_k%20=%20%5Cpsi_k/%5Cpsi_%7Bk-1%7D"> with <img src="https://latex.codecogs.com/png.latex?%5Cpsi_k(b_%7B1:k%7D)%20=%20%5Cmathbb%7BE%7D_%7Bp_%5Ctheta%7D%5BR(y)%5Cmid%20b_%7B1:k%7D%5D">, the expected final score given the prefix, then every <img src="https://latex.codecogs.com/png.latex?Z_k%20=%201">, so <img src="https://latex.codecogs.com/png.latex?D%20%5Cequiv%201"> and <img src="https://latex.codecogs.com/png.latex?q%20=%20%5Cpi">. That <img src="https://latex.codecogs.com/png.latex?%5Cpsi_k"> is the optimal <em>twist function</em> of twisted SMC. <span class="citation" data-cites="Zhao2024Probabilistic">Zhao et al. (2024)</span> try to learn that from data. This gives us a fun interpretation of the role of a loomer, <em>as</em> a twist function: Their job is to judge a paragraph by where it is likely to lead, not by how it reads now. I do not know how well people do this, but I suspect we humans are not terrible at it, insofar as we solve problems like this every time we flap our gobs. The invective that streams from my cake hole may not be globally optimal, but it certainly seems to go to controllable places.</p>
</section>
<section id="looming-is-not-supposed-to-have-a-state-target" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="looming-is-not-supposed-to-have-a-state-target"><span class="header-section-number">3.2</span> Looming is not supposed to have a state target</h3>
<p>SMC steering starts from explicitly given <img src="https://latex.codecogs.com/png.latex?R(y)">, and the challenge is finding prefix potentials that approximate it. Specifically, the loomer assumes no particular Boltzmann-rational preference is guiding the search <img src="https://latex.codecogs.com/png.latex?g_k(b_%7B1:k%7D%5E%7B(i)%7D)">. A loomer has only the prefix potential, and it is not required to be consistent from one step to the next. Janus says they expect whatever purpose they started with to itself mutate/branch along the way. In that sense there is no posterior for the procedure to be “wrong” about, and the guarantees that make SMC attractive to statisticians (consistency, unbiased estimates of the normalizing constant) are not especially relevant to cyborgists. As such, maybe we should think of looming as closer to search than to sampling. There turns out to be some mileage in this perspective, which we come back to in a moment.</p>
<p><a href="https://generative.ink/posts/quantifying-curation/?ref=danmackinlay.name">Janus attempts to quantify such curation</a>: Choosing 1 of <img src="https://latex.codecogs.com/png.latex?N"> equiprobable completions applies <img src="https://latex.codecogs.com/png.latex?%5Clog_2%20N"> bits of optimization, and bits add across choices. AFAICT the particle-filter equivalent is <img src="https://latex.codecogs.com/png.latex?%5Clog_2(N/%5Cmathrm%7BESS%7D)"> (Effective Sample Size) bits per resampling, since <img src="https://latex.codecogs.com/png.latex?N/%5Cmathrm%7BESS%7D"> is the factor by which the weights concentrate the population. The <code>ESS &lt; N/2</code> rule in the pseudo-code says “resample once we have accumulated about 1 bit”. Loaming with <img src="https://latex.codecogs.com/png.latex?N=8"> takes ESS to 1 and injects 3 bits of information every paragraph.</p>
<p><strong>The tree is kept.</strong> SMC in the default forward mode is forward-only; a discarded particle is gone, and the history of the survivors is of interest only as a diagnostic. A loom stores the whole genealogy and extensions over the default allow us to “rewind” and “replay”, walking back up the tree and back down to alternative limbs, branching again from an earlier node when the current line goes nowhere fun. Backtracking is, as such, an interactive, manual fix for the greediness of looming. It makes looming look more like tree search than like filtering.</p>
</section>
<section id="loom-branch-points-follow-the-models-uncertainty" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="loom-branch-points-follow-the-models-uncertainty"><span class="header-section-number">3.3</span> Loom branch points follow the model’s uncertainty</h3>
<p>SMC resamples when the weights degenerate (concentrating on a small number of options), which is to say when the constraint has said something informative. Janus’s <a href="https://generative.ink/posts/language-models-are-multiverse-generators/?ref=danmackinlay.name">adaptive branching</a> puts branch points in according to their more idiosyncratic logic, e.g.&nbsp;where the model’s own next-token distribution is spread out, or where a particular sampled token had low probability. Figure&nbsp;2 is an example: one candidate per step through <code>a challenge to the traditional</code>, then five at once. One argument for this is that forcing a branch (do loomers force them to be distinct?) where the model is 99% sure of the next token only produces incoherent alternatives. I assume that <em>some</em> hip flavour of adaptive SMC does such things, but I have not seen it.</p>
<p>There is some work to unify looming and SMC. JD Pressman’s <a href="https://github.com/JD-P/minihf?ref=danmackinlay.name">MiniHF</a> is one galaxy-brained example. Pressman argues that because picking branches by hand is tiring, a loomer will want to distill their judgement into a reward model to do it for them. MiniHF’s Weave algorithm does that, running Monte Carlo tree search against an evaluator trained to approximate the loomer’s preferences, and by the author’s account it ends up close to Tree of Thoughts <span class="citation" data-cites="Yao2023Tree">(Yao et al. 2023)</span>. Once the human has been replaced by a learned evaluator we seem to be doing something like SMC steering with a learned potential, give or take the choice of search algorithm. c.f. InFerActive <span class="citation" data-cites="Hwangbo2025InFerActive">(Hwangbo et al. 2025)</span> which uses SMC with adaptive resampling to pre-populate the tree that a human evaluator then explores.</p>
<p>If we wanted to take the parallel totally seriously, we could combine them completely: devise a loom that keeps several lineages alive and asks the operator for rough weights on a handful of candidates each step. That would be a particle filter with a human likelihood. It would cost more reading per step but OTOH would buy back the global normalization, which is to say, estimate the partition function. Or is that taking the analogy too far? If, as Janus says, the loop is more exploratory than inferential, does it even make sense to worry about global normalization? Anyway, that is a side quest for someone with more side quest time than I.</p>
</section>
</section>
<section id="references" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="references"><span class="header-section-number">4</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Azizi2026PowerSMC" class="csl-entry">
Azizi, Potraghloo, Ahmadi, et al. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2602.10273">Power-SMC: Low-Latency Sequence-Level Power Sampling for Training-Free LLM Reasoning</a>.”</span>
</div>
<div id="ref-Bigelow2024Forking" class="csl-entry">
Bigelow, Holtzman, Tanaka, et al. 2024. <span>“Forking Paths in Neural Text Generation.”</span>
</div>
<div id="ref-Bigelow2026Forking" class="csl-entry">
Bigelow, Zur, Grant, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2608.19611">Forking Fast: Efficiently Estimating Uncertainty Dynamics in Text Generation</a>.”</span>
</div>
<div id="ref-Emara2026Faster" class="csl-entry">
Emara, da Costa, Chang, et al. 2026. <span>“<a href="https://arxiv.org/abs/2604.15672v1">Faster LLM Inference via Sequential Monte Carlo</a>.”</span>
</div>
<div id="ref-Hwangbo2025InFerActive" class="csl-entry">
Hwangbo, Lee, Jeon, et al. 2025. <span>“<a href="https://arxiv.org/abs/2512.10234">InFerActive: Interactive Tree-Based Exploration of LLM Sampling for Safety Evaluation</a>.”</span>
</div>
<div id="ref-Lew2023Sequential" class="csl-entry">
Lew, Zhi-Xuan, Grand, et al. 2023. <span>“<a href="https://arxiv.org/abs/2306.03081">Sequential Monte Carlo Steering of Large Language Models Using Probabilistic Programs</a>.”</span>
</div>
<div id="ref-Loula2025Syntactic" class="csl-entry">
Loula, LeBrun, Du, et al. 2025. <span>“<a href="https://arxiv.org/abs/2504.13139">Syntactic and Semantic Control of Large Language Models via Sequential Monte Carlo</a>.”</span>
</div>
<div id="ref-Macar2025Thought" class="csl-entry">
Macar, Bogdan, Rajamanoharan, et al. 2025. <span>“<a href="https://openreview.net/forum?id=dkwVlQ0s7b">Thought Branches: Interpreting LLM Reasoning Requires Resampling</a>.”</span> In.
</div>
<div id="ref-Wang2023SelfConsistency" class="csl-entry">
Wang, Wei, Schuurmans, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2203.11171">Self-Consistency Improves Chain of Thought Reasoning in Language Models</a>.”</span>
</div>
<div id="ref-Yao2023Tree" class="csl-entry">
Yao, Yu, Zhao, et al. 2023. <span>“<a href="https://arxiv.org/abs/2305.10601">Tree of Thoughts: Deliberate Problem Solving with Large Language Models</a>.”</span>
</div>
<div id="ref-Zhao2024Probabilistic" class="csl-entry">
Zhao, Brekelmans, Makhzani, et al. 2024. <span>“<a href="http://arxiv.org/abs/2404.17546">Probabilistic Inference in Language Models via Twisted Sequential Monte Carlo</a>.”</span> In <em>Proceedings of the 41st International Conference on Machine Learning</em>.
</div>
</div>


</section>

 ]]></description>
  <category>compsci</category>
  <category>language</category>
  <category>machine learning</category>
  <category>meta learning</category>
  <category>Monte Carlo</category>
  <category>neural nets</category>
  <category>NLP</category>
  <category>particle</category>
  <category>state space models</category>
  <category>stochastic processes</category>
  <category>time series</category>
  <guid>https://danmackinlay.name/notebook/llm_sampling_stunts.html</guid>
  <pubDate>Thu, 30 Jul 2026 16:17:52 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/gideon_selects_RP-P-1904-3567.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Agent frontends</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/agent_frontends.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-perspectivizer" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-perspectivizer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/perspectivizer_RP-P-OB-1490.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/perspectivizer_RP-P-OB-1490.avif" id="fig-perspectivizer" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-perspectivizer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>The frontend is <a href="../notebook/agent_harness_design.html#the-stack">the outermost layer of the stack</a> — the human-facing surface, and the boundary that says what a harness — the loop running underneath — is not. It is also the most loosely coupled: the model neither knows nor cares what renders its tokens, so picking one is cheap and reversible. Which is exactly why it is worth deciding once and forgetting about, rather than drifting between four of them.</p>
<p>A frontend has three jobs, and they are not equally hard to come by.</p>
<ol type="1">
<li><strong>Render what the model emits.</strong> Prose is free. Equations, proof state, diagrams and structured output are not.</li>
<li><strong>Reach the tools</strong>, which in practice means <a href="../notebook/ai_agents_applied.html#skills">MCP or skills</a>. Everything below does this, so it never separates them.</li>
<li><strong>Set the sampling parameters.</strong> The one people forget, and the one the choice comes down to.</li>
</ol>
<p>Getting documents <em>in</em> looks like a fourth job, but it is not really one. The Anthropic bundle reads the binary natively, Open WebUI and Qwen-Agent run their own <a href="../notebook/ai_agents_applied.html#documents">extraction-plus-RAG pipelines</a>, and Goose, OpenCode and Jan bring no pipeline at all — but all of that is moot for anything with real notation in it, since the built-in extractors mangle display equations, dense notation and scans alike. <a href="../notebook/pdf_ingestion.html">Convert deliberately first</a> and feed the markdown to whichever frontend; extraction quality belongs to the converter, not to the chat window.</p>
<p>When a hosted general agent is doing the orchestrating there is not much to decide: we read the output in whatever client we already use. The three bite when we move onto open models, or own the loop ourselves.</p>
<section id="options" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="options"><span class="header-section-number">1</span> The options</h2>
<p>Documents are in the table as reference data rather than as a fourth job, per above. “Needs a system-prompt line”, in the rendering column, is the LaTeX delimiter fix below.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th>Rendering</th>
<th>Tools</th>
<th>Documents</th>
<th>Sampling</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="../notebook/ai_agents_applied.html#claude-desktop">Anthropic bundle</a> (Desktop, Code, Cowork)</td>
<td>out of the box</td>
<td>MCP + skills</td>
<td>native multimodal, drag-and-drop</td>
<td>no knob exposed</td>
</tr>
<tr class="even">
<td><a href="../notebook/ai_agents_applied.html#open-webui-setup">Open WebUI</a></td>
<td>out of the box, every delimiter</td>
<td>MCP, Tools, Python interpreter</td>
<td>built-in RAG</td>
<td>full parameter set, per preset</td>
</tr>
<tr class="odd">
<td><a href="../notebook/ai_agents_applied.html#qwen-agent">Qwen-Agent</a> <code>WebUI</code></td>
<td>delimiters registrable</td>
<td>a plain Python function</td>
<td>built-in RAG (<code>files=[…]</code>)</td>
<td>it is a library — whatever we put in <code>llm_cfg</code></td>
</tr>
<tr class="even">
<td><a href="../notebook/ai_agents_applied.html#goose">Goose</a></td>
<td>needs a system-prompt line</td>
<td>MCP; skills</td>
<td>BYO via MCP</td>
<td>temperature only</td>
</tr>
<tr class="odd">
<td><a href="../notebook/ai_agents_applied.html#opencode">OpenCode</a></td>
<td>needs a system-prompt line</td>
<td>MCP</td>
<td>BYO via MCP</td>
<td>arbitrary keys per model, once past a <code>temperature</code> key that is a boolean flag rather than a value</td>
</tr>
<tr class="even">
<td><a href="../notebook/local_llm_mac.html#jan">Jan</a></td>
<td>needs a system-prompt line</td>
<td>MCP</td>
<td>BYO via MCP</td>
<td>the full set, on a custom provider</td>
</tr>
<tr class="odd">
<td>an editor, over ACP</td>
<td>the editor’s own diffs and buffers</td>
<td>whatever the agent has</td>
<td>the files already open</td>
<td>the agent’s</td>
</tr>
<tr class="even">
<td><a href="../notebook/automatic_coding.html#waku">Waku</a>, over each agent’s native protocol</td>
<td>the agent’s transcript and diffs, every agent in one window</td>
<td>whatever the agent has</td>
<td>the workspace</td>
<td>model and reasoning effort per session; otherwise the agent’s</td>
</tr>
<tr class="odd">
<td>our own script, usually Gradio</td>
<td>ours to render</td>
<td>a plain function call</td>
<td>ours to parse</td>
<td>ours to set</td>
</tr>
</tbody>
</table>
<p>The Anthropic bundle gets rendering and documents for free with no configuration at all, and tool calls once MCP is wired up. It is also closed, hosted, and priced at someone else’s discretion, so the interesting choosing happens among the self-hosted options — where Open WebUI and Qwen-Agent are the two that answer yes to everything. Rendering separates those two only a little: Qwen-Agent’s delimiters are registrable, where Open WebUI’s are already right. The last column separates them properly, and it is what the next section is about.</p>
<section id="gradio" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="gradio"><span class="header-section-number">1.1</span> The bottom row is Gradio</h3>
<p>“Our own script” sounds more heroic than it is. In practice it usually means <a href="../notebook/python_gui.html#gradio">Gradio</a>, the <em>de facto</em> standard: <code>gr.ChatInterface</code> wraps a generator function into a streaming chat window in about a dozen lines, and it behaves in all kinds of sensible ways. For example <code>gr.Chatbot(latex_delimiters=…)</code> registers the math delimiters at the renderer rather than nagging the model to emit dollars. That is not a coincidental resemblance to <a href="../notebook/ai_agents_applied.html#qwen-agent">Qwen-Agent</a> — Qwen-Agent’s <code>WebUI</code> <em>is</em> Gradio, and so were <a href="../notebook/python_gui.html#gradio">oobabooga</a> and <a href="../notebook/image_ai_clients.html#automatic1111">AUTOMATIC1111</a> before it. Local-model UIs keep turning out to be Gradio underneath.</p>
<p>As such, while Gradio is very useful as a frontend, it is a little <em>thinner</em> than most other options here. Let us claim it as infrastructure.</p>
<p>Gradio is not a frontend we choose <em>against</em> Open WebUI; it is a toolkit we might build an alternative with. What we do not get for writing it is everything <em>beyond</em> the chat box: no model picker fed from <code>/v1/models</code>, no preset store, no conversation history surviving a restart, no user accounts. Those are the parts of Open WebUI that are tedious rather than hard, which is the worst kind of thing to reimplement. So Gradio is the right answer when the loop is the point and the chat window is a viewport onto it — and the wrong answer when we want somewhere to live.</p>
</section>
</section>
<section id="sampling" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="sampling"><span class="header-section-number">2</span> Sampling, which is the one that decides it</h2>
<p>Most chat clients expose a temperature slider, some expose nothing, and a few expose the whole parameter set. That sounds like a minor convenience until the loop depends on it.</p>
<p><a href="../notebook/automatic_maths.html#fan-out"><code>maj@k</code></a> is a <em>sampling</em> technique: greedy decoding returns <img src="https://latex.codecogs.com/png.latex?k"> identical answers and there is nothing to vote on, so a client that cannot set temperature cannot run the technique at all. Worse, the solver drawing the samples and the loop driving it want different temperatures while sharing one endpoint. A local server like vllm-mlx <a href="../notebook/local_llm_mac.html#sampling-real">has no per-model sampling in its registry</a>, so the value has to come from the client or not at all — and most clients cannot send it.</p>
<section id="sampling-clients" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="sampling-clients"><span class="header-section-number">2.1</span> What each client can send</h3>
<p>Whether we can send a value turns out to be a property of the <a href="../notebook/agent_harness_design.html#the-stack">harness</a> rather than of the server, and the harnesses differ more than I expected.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Harness</th>
<th>What it can send</th>
<th>Snags</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="../notebook/ai_agents_applied.html#goose">Goose</a></td>
<td>temperature, nothing else</td>
<td>its binary contains <code>GOOSE_TEMPERATURE</code> and not the strings <code>top_p</code>, <code>min_p</code> or <code>repetition_penalty</code>, so no configuration would make it send them</td>
</tr>
<tr class="even">
<td><a href="../notebook/ai_agents_applied.html#opencode">OpenCode</a></td>
<td>arbitrary keys per model, from a <code>models.&lt;id&gt;.options</code> block its <code>@ai-sdk/openai-compatible</code> transport spreads into the body last</td>
<td><code>models.&lt;id&gt;.temperature</code> is a <em>boolean capability flag</em>, not a value — without it no temperature is sent at all; and defaults are injected by substring-matching the model id, so anything with <code>qwen</code> in the name silently acquires temperature 0.55</td>
</tr>
<tr class="odd">
<td><a href="../notebook/ai_agents_applied.html#open-webui-setup">Open WebUI</a></td>
<td>a full pinned parameter set per <em>model preset</em></td>
<td>the penalty field is misnamed against any non-Ollama server (<a href="../notebook/local_llm_mac.html#sampling-clients-vllm-mlx">detail</a>)</td>
</tr>
<tr class="even">
<td><a href="../notebook/local_llm_mac.html#jan">Jan</a></td>
<td>the full sampler set — but only on a <em>custom provider</em>; the controls are hidden for the built-in cloud providers</td>
<td>a local endpoint is not one of Jan’s “local” options, which are its own <a href="../notebook/local_llm_mac.html#jan-custom-provider">engines</a>; it has to be added by hand</td>
</tr>
</tbody>
</table>
<p>Between Open WebUI and Qwen-Agent, it is Open WebUI that actually fits the shape of the problem, which is not what I expected from a <a href="../notebook/ai_agents_applied.html#open-webui">pile of Node packages with peculiar version requirements</a>. The unit is a <em>model preset</em>: a base model plus a system prompt plus a pinned parameter set, saved as its own entry in the picker. So one base model can carry several presets — the same weights appearing twice in the picker, a Qwen3.6 at temp 1.0 for thinking beside a Qwen3.6 at 0.6 for coding — which is precisely the distinction <a href="../notebook/local_llm_mac.html#models-agentic">the model tables</a> draw and that neither a server-side registry nor Goose can express. It also reads <code>/v1/models</code>, so the model list cannot drift out of sync the way a hand-maintained one does.</p>
<p>A preset is one row in <code>webui.db</code>, and the part that matters is small:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource json number-lines code-with-copy"><code class="sourceCode json"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cascade-2-maths"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"base_model_id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nemotron-cascade-2"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cascade-2 (maths)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-5">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"params"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"temperature"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"top_p"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"min_p"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"repetition_penalty"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>That preset mechanism turns out to be a better reason to run Open WebUI than the rendering I originally chose it for — though it does that too, being the only frontend here that gets mathematics right without being told how. So the endorsement goes there, reluctantly, Node packages and all. What it looks like against one particular local server — including a parameter name that silently does not survive the trip — is <a href="../notebook/local_llm_mac.html#sampling-clients-vllm-mlx">on the Mac page</a>.</p>
</section>
</section>
<section id="acp" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="acp"><span class="header-section-number">3</span> Driving an agent from the editor</h2>
<p>For coding, and increasingly for anything where the artefact is a file, the frontend people actually want is the editor they already have. The <a href="https://agentclientprotocol.com/?ref=danmackinlay.name">Agent Client Protocol</a> (ACP) is how the two talk. <a href="../notebook/zed.html">Zed</a> wrote it, and the pitch is <a href="https://agentclientprotocol.com/get-started/introduction?ref=danmackinlay.name">modelled on the Language Server Protocol</a>: rather than every editor hand-rolling an integration for every agent — and every agent re-implementing each editor’s API — both sides speak one JSON-RPC dialect, and ideally any ACP editor can drive any ACP agent.</p>
<p>Mechanically, the agent runs as a subprocess of the editor and they talk JSON-RPC over stdio (or HTTP/WebSocket for a remote agent); the editor is the boss of the files and the terminal, so the agent’s edits arrive as native diffs and its shell commands run in the editor’s own terminal. It reuses MCP’s JSON types where it can and adds a few agentic-UX types of its own.</p>
<p>On the editor side the adopters are Zed and JetBrains, with community bridges for Neovim, Emacs, and VS Code. On the agent side, <a href="../notebook/ai_agents_applied.html#goose">Goose</a> ships a <code>goose acp</code> server, Google’s Gemini CLI likewise I think, and Claude Code and Codex have adapters, I think; Goose’s <a href="https://goose-docs.ai/docs/guides/acp-clients?ref=danmackinlay.name">ACP-clients guide</a> is the least stale list I have found.</p>
</section>
<section id="diffusion-ui" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="diffusion-ui"><span class="header-section-number">4</span> When the model wants a different interface entirely</h2>
<p>Everything above assumes an autoregressive model: we append a prompt, the model appends an answer, and the frontend’s job is to draw the appending nicely. A <a href="../notebook/nn_diffusion_language.html">diffusion language model</a> like <a href="../notebook/local_llm_mac.html#models-diffusion">DiffusionGemma</a> instead fixes a block of text and denoises the whole thing at once, every token effectively attending to all the others. Streaming that into a chat box, we are watching a printing press pretend to be a typewriter.</p>
<p>It would better suit an interface built around <em>editing text</em> rather than <em>chatting</em> — much as <a href="../notebook/nn_generative_image.html">image diffusions</a> get <a href="../notebook/image_ai_clients.html">inpainting</a> in their GUIs. Rather than only appending, we’d want to <em>regenerate selections</em>, <em>infill gaps</em>, <em>rewrite under constraints</em>. The closest thing today is <a href="../notebook/automatic_coding.html#aider">Aider</a>’s <code>AI!</code> / <code>AI?</code> scoped edits, plus the inline-diff autocomplete most sidebar extensions ship — both of which already mutate selected text, though I’m unclear whether either explicitly supports the diffusion behaviour, and I don’t fully trust the process at arbitrary infill anyway. Also, the actual prompt window for each generation looks quite short (hundreds of tokens) which seems… not enough?</p>
<p>This is the one case where the frontend is not the loosest-coupled layer, and it is worth flagging because everything else on this page assumes it is.</p>
</section>
<section id="maths" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="maths"><span class="header-section-number">5</span> The maths case</h2>
<p>Mathematics is the domain that demands all three jobs at once, which is why <a href="../notebook/automatic_maths.html">I ended up caring</a>. Notation has to render, the <a href="../notebook/automatic_maths.html#oracle"><code>solve()</code> oracle</a> has to be reachable, <code>maj@k</code> needs the temperature knob, and if we are <a href="../notebook/automatic_maths.html#lean">proving</a> there is a fourth thing no chat window does at all.</p>
<section id="maths-rendering" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="maths-rendering"><span class="header-section-number">5.1</span> Rendering notation</h3>
<p>Hosted clients render LaTeX without our help, so this only bites self-hosted frontends, and there it bites in two places: delimiters and streaming. Delimiters first. Models like to emit <code>\(…\)</code> and <code>\[…\]</code>, while most chat UIs only wire <code>$…$</code> and <code>$$…$$</code> into their renderer, so the equation arrives as raw backslashes. The fix is a system-prompt line telling the model to emit dollar delimiters; Goose, OpenCode, and Jan all need it. <a href="../notebook/ai_agents_applied.html#open-webui-setup">Open WebUI</a> does not: its KaTeX pass <a href="https://github.com/open-webui/open-webui/blob/main/src/lib/utils/marked/katex-extension.ts?ref=danmackinlay.name">registers every delimiter style</a> — <code>$…$</code>, <code>$$…$$</code>, <code>\(…\)</code>, <code>\[…\]</code>, even <code>\begin{equation}</code> — behind guards that keep prose dollar signs out of maths mode.<sup>1</sup> <a href="../notebook/ai_agents_applied.html#qwen-agent">Qwen-Agent</a> attacks it from the other end: its Gradio UI lets us register the delimiters at the renderer (<code>gr.Chatbot(latex_delimiters=…)</code>) instead of nagging the model. Streaming is the second fumble.<sup>2</sup></p>
</section>
<section id="oracle-calls" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="oracle-calls"><span class="header-section-number">5.2</span> Calling the oracle</h3>
<p>Reaching a <a href="../notebook/automatic_maths.html#oracle"><code>solve()</code> oracle</a> reduces to <a href="../notebook/ai_agents_applied.html#skills">MCP versus skills</a>, and every row in the table has one or the other. The one frontend-level trap is the timeout: a synchronous <code>solve()</code> that fans out for three minutes will exceed some clients’ tool-use time limit, which is what <a href="../notebook/automatic_maths.html#oracle">the async handle pattern</a> is for.</p>
</section>
<section id="lean-state" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="lean-state"><span class="header-section-number">5.3</span> Lean state</h3>
<p>No chat frontend renders Lean proof state, so this question has one good answer and it is not a chat UI. The <a href="https://live.lean-lang.org?ref=danmackinlay.name">Lean 4 web editor</a> runs a Lean server with a live goal panel, so we keep it open beside the chat rather than build our own. When we <a href="../notebook/automatic_maths.html#iterate">own the loop</a> we at least see what the loop sees: the compiler’s complaints are the loop’s food, and printing them is free. A <a href="https://www.npmjs.com/package/react-markdown?ref=danmackinlay.name">custom React app</a> with an embedded editor buys full control for a day or two of plumbing; I’d live in Open WebUI first and defer that until the loops earn it.</p>
</section>
<section id="pipelines" class="level3" data-number="5.4">
<h3 data-number="5.4" class="anchored" data-anchor-id="pipelines"><span class="header-section-number">5.4</span> Our loop behind Open WebUI</h3>
<p>Owning the loop is one thing, but building a whole new frontend sounds tedious. Fortunately we need not do both. <a href="../notebook/ai_agents_applied.html#open-webui">Open WebUI straddles two layers</a> — it is frontend <em>and</em> harness — and the two come apart, so we can keep the frontend and replace the harness underneath it. That is, we can run <a href="../notebook/automatic_maths.html#iterate">our own orchestrator</a> and still inherit the KaTeX rendering, the RAG, and the chat history without writing any of it. There are three extension points that seem appropriate, in order of increasing maintenance burden:</p>
<ul>
<li>A <strong><a href="https://docs.openwebui.com/features/plugin/functions/pipe/?ref=danmackinlay.name">Pipe Function</a></strong> is the basic option: a Python function that registers as a custom “model” in the chat dropdown, intercepts the prompt, and returns whatever the function produces. Enough for a single custom agent.</li>
<li>A <strong><a href="https://github.com/open-webui/pipelines?ref=danmackinlay.name">Pipeline</a></strong> is similar but elaborated: a standalone Python <em>service</em> that Open WebUI talks to as if it were a model, so, for example, the <a href="../notebook/automatic_maths.html#iterate">generate-run-verify loop</a> (routing, retries, fan-out, the oracle call) can run in a separate process. Upload a <code>.py</code> file, or point it at a raw GitHub URL, and it shows up as a model in the UI. Looks like enough power for the current purpose.</li>
<li><strong><a href="https://docs.openwebui.com/features/extensibility/mcp/?ref=danmackinlay.name">MCP</a></strong> (native since v0.6.31) is to let the loop itself use tools — the <a href="../notebook/automatic_maths.html#oracle">mathx oracle</a>, a (wrapped) Lean server. It can compose with either of the above.</li>
</ul>
<p>Logical escalation: keep the stock UI, put the loop in a Pipeline, integrate more needful tools via MCP. I suspect we can defer the custom React app until we hit something outside the scope of Open WebUI, such as proof-state rendering.</p>


</section>
</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Recent, though: <a href="https://github.com/open-webui/open-webui/issues/21612?ref=danmackinlay.name">inline <code>$…$</code> was deliberately excluded</a> into early 2026, so an older install still wants the system-prompt line.↩︎</p></li>
<li id="fn2"><p>A <code>$$</code> arrives before its closing pair, so the equation shows as raw text until the delimiter lands — the maths case of the <a href="https://developer.chrome.com/docs/ai/render-llm-responses?ref=danmackinlay.name">flash-of-incomplete-markdown</a> problem, fixed by buffering until the delimiters balance. Rendering a <em>document</em> rather than a chat stream sidesteps it entirely.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>computers are awful together</category>
  <category>DIY</category>
  <category>machine learning</category>
  <category>NLP</category>
  <category>premature optimization</category>
  <category>slop</category>
  <category>UI</category>
  <guid>https://danmackinlay.name/notebook/agent_frontends.html</guid>
  <pubDate>Sat, 25 Jul 2026 22:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/perspectivizer_RP-P-OB-1490.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Web search for machines</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/search_for_machines.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-bear2hunni" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-bear2hunni-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/bear2hunni_RP-P-1996-70.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/bear2hunni_RP-P-1996-70.avif" id="fig-bear2hunni" class="illustration full img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-bear2hunni-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>An <a href="../notebook/internet_search.html#ai-augmented-search">internet search engine for humans</a> returns an HTML page of links, on the reasonable assumption that a human will click a few of them to read. An <a href="../notebook/agent_harness_design.html">agent</a> does not click. It would much rather not have to parse the HTML itself, and instead get text that’s already stripped of nonsense, chunked, and ideally carrying enough provenance that the model can say where a claim came from. There is a whole market of vendors selling that AI-facing product now.</p>
<p>For the retrieval machinery underneath — chunking, embeddings, BM25, reranking — see <a href="../notebook/ai_search.html">AI search</a>.</p>
<section id="jina" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="jina"><span class="header-section-number">1</span> Jina</h2>
<p>Below I mention Firecrawl as a web scraping tool. It is kind of the default, mostly because it is famous, etc.</p>
<p>However, it is too gold-plated for me. As far as I can tell, they charge a premium for using browsers for scraping, which is a good way to get clean content from dynamic, JS-first websites, but it’s also slow and resource-intensive, and overkill for 80% of my use cases.</p>
<p>Jina is way cheaper.</p>
<ul>
<li><a href="https://jina.ai/reader/?ref=danmackinlay.name">Jina Reader API</a></li>
<li><a href="https://github.com/jina-ai/MCP?ref=danmackinlay.name#usage">jina-ai/MCP: Official Jina AI Remote MCP Server</a></li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">claude</span> mcp add <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> user <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--transport</span> http jina https://mcp.jina.ai/v1 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--header</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Authorization: Bearer </span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${JINA_API_KEY}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span></code></pre></div></div>
</section>
<section id="firecrawl" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="firecrawl"><span class="header-section-number">2</span> Firecrawl</h2>
<p><a href="https://www.firecrawl.dev/?ref=danmackinlay.name">Firecrawl</a> is a web scraping and content extraction service that converts web pages (even <a href="../notebook/javascript_apps.html">javascript-heavy webapps</a>) into clean <a href="../notebook/markdown.html">markdown</a> — the native language of LLMs. It exposes an <a href="https://github.com/firecrawl/firecrawl-mcp-server?ref=danmackinlay.name">MCP server</a> with tools for scraping single pages, batch processing, site crawling, and web search, so an LLM agent can pull in web content as context. It supports both cloud and self-hosted deployment (of the MCP server, I mean, not the internet-scale scraping).</p>
<p>Its main features AFAICT</p>
<ul>
<li>converting challenging JS webapps into clean markdown</li>
<li>charging extortionately for it relative to its competitors</li>
</ul>
</section>
<section id="tavily" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="tavily"><span class="header-section-number">3</span> Tavily</h2>
<p><a href="https://tavily.com/?ref=danmackinlay.name">Tavily</a> is search-forward rather than scrape-forward like Jina. It bills itself as a search engine for agents and RAG: a <a href="https://docs.tavily.com/?ref=danmackinlay.name">single API</a> exposing <code>search</code>, <code>extract</code>, <code>crawl</code> (graph-based parallel traversal), <code>map</code>, and a bundled <code>research</code> endpoint. The <code>search</code> call returns relevance-scored, pre-chunked snippets sized for a model’s context window — and optionally a synthesized answer with citations. Its <a href="https://docs.tavily.com/documentation/about?ref=danmackinlay.name">argument framing</a> is that an agent will proceed more efficiently with grounded facts-plus-provenance, rather than search results that are just a list of links to follow. For that reason, plus maybe early-mover advantage, it seems to be baked into many toolkits (Langchain) and cited in many papers.</p>
</section>
<section id="and-still-more-machine-search" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="and-still-more-machine-search"><span class="header-section-number">4</span> And still more machine search</h2>
<p>All the search options seem to have converged on similar affordances now. Firecrawl now has its own <code>search</code> endpoint that finds pages <em>and</em> returns their full markdown in one call; Jina has <code>s.jina.ai</code> for search, plus <a href="https://jina.ai/deepsearch/?ref=danmackinlay.name">DeepSearch</a>, an iterative search-read-reason loop behind an OpenAI-compatible endpoint. Tavily is more pre-processed, trading efficiency for precision and/or control. And that’s just the start. There are ever more things!</p>
<ul>
<li><a href="https://exa.ai/?ref=danmackinlay.name">Exa</a> (embeddings/neural search over the web),</li>
<li><a href="https://www.linkup.so/?ref=danmackinlay.name">Linkup</a> (weighted towards premium and licensed sources),</li>
<li><a href="https://brave.com/search/api/?ref=danmackinlay.name">Brave Search API</a> (an independent index).</li>
</ul>


</section>

 ]]></description>
  <category>agents</category>
  <category>computers are awful together</category>
  <category>NLP</category>
  <category>provenance</category>
  <category>search</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/search_for_machines.html</guid>
  <pubDate>Fri, 24 Jul 2026 07:40:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/bear2hunni_RP-P-1996-70.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Welcome to cybersec hell</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/cybersec_hell.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-battering-vram" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-battering-vram-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/battering_vram_RP-P-1883-A-7073.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/battering_vram_RP-P-1883-A-7073.avif" id="fig-battering-vram" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-battering-vram-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>It begins. Agentic AIs now automate cybercrime faster and better than humans can.</p>
<section id="ai-agents-are-good-at-this" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="ai-agents-are-good-at-this"><span class="header-section-number">1</span> AI Agents are good at this</h2>
<ul>
<li><a href="https://www.anthropic.com/news/disrupting-AI-espionage?ref=danmackinlay.name">Disrupting the first reported AI-orchestrated cyber espionage campaign &nbsp;Anthropic</a></li>
<li><a href="https://www.bbc.com/news/articles/c3ek3gvdnj3o?ref=danmackinlay.name">OpenAI says its AI went rogue and launched ‘unprecedented’ cyber-attack</a> / <a href="https://www.astralcodexten.com/p/the-hugging-face-incident?ref=danmackinlay.name">The Huggingface Incident</a></li>
</ul>
</section>
<section id="business-models" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="business-models"><span class="header-section-number">2</span> Business models</h2>
<p><a href="../notebook/prediction_markets.html">Prediction markets</a> provide a new business model for cybercrime. What could go wrong?</p>
<p><a href="https://stephenfollows.com/p/what-just-happened-to-thenumberscom-should-worry-us-all?ref=danmackinlay.name">What just happened to TheNumbers.com should worry us all</a>.</p>


</section>

 ]]></description>
  <category>adversarial</category>
  <category>agents</category>
  <category>AI safety</category>
  <category>catastrophe</category>
  <category>computers are awful together</category>
  <category>confidentiality</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>tail risk</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/cybersec_hell.html</guid>
  <pubDate>Fri, 24 Jul 2026 00:18:17 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/battering_vram_RP-P-1883-A-7073.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Agent harness design</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/agent_harness_design.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-human-frame" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-human-frame-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/human_frame_RP-P-OB-84.594.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/human_frame_RP-P-OB-84.594.avif" id="fig-human-frame" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-human-frame-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Making an <a href="../notebook/ai_agents_applied.html">LLM behave agentically</a> is not just about the (large language) model but the <em>harness</em> around it. We <em>could</em> ask a language model to fix a bug in some software but… well, in the absence of anything else, it is just a machine that says things. Words said into the void cannot fix bugs. <em>Something</em> has to run the code it writes, watch the test fail, and communicate the error back for another go. That something is the harness.</p>
<p>Harnesses look different in <a href="../notebook/automatic_coding.html">coding agents</a>, <a href="../notebook/automatic_maths.html">maths agents</a> and <a href="../notebook/ai_agents_applied.html">general-purpose assistants</a>, but they mostly share the same design space, hit similar problems, and reach similar solutions. They are built mostly from the same parts library (loops! tools! etc!) and similar design patterns (parallel vs sequential, context memory), and as with many things in AI, this regularity now means the harness itself has become a subject of optimization.</p>
<div class="callout callout-style-simple callout-caution">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>This page is mostly AI slop, i.e.&nbsp;LLM descriptions of my decisions in some recent exploratory projects. However, it is useful, so I am publishing it now rather than waiting for a more polished version.</p>
</div>
</div>
</div>
<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Axiomatic AI’s brilliant <em>minimal agent</em> paper <span class="citation" data-cites="Requena2026Minimal">(Requena et al. 2026)</span> supplies the bulk of the worked examples below. Maybe just read that? It is a super simple attempt to cut through proprietary lab secret-sauce hype and measure what is actually helpful in a loop. They wrap an off-the-shelf frontier model — elderly Claude Opus, no fine-tuning — in a custom harness with compiler feedback, a memory, and search tools that compete well against specialist provers that were trained for the job. So it is clear the harness design is super important. They then take the harness apart piece by piece to see which pieces did the work. That is an unusually clean measurement of something we mostly treat as folklore. Their domain is mathematical theorem proving, which is unusually “nice” in that the answers are verifiable, so the results are less clean in other domains. No excuse not to do the experiment though.</p>
</div>
</div>
</div>
<section id="the-stack" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-stack"><span class="header-section-number">1</span> The stack</h2>
<p>The terms of art are a mess. People disagree what to call things, which results in endless confusion even between specialists. I’ll declare local vocabulary by fiat for this piece.</p>
<dl>
<dt>Model</dt>
<dd>
the weights themselves — Qwen, DeepSeek, mxbai-embed, Claude and GPT (closed weights). Open models are distributed as <code>.safetensors</code> or an equivalent, typically from Hugging Face, and <a href="../notebook/nn_quantization.html">quantized</a> to whatever precision the hardware will bear.
</dd>
<dt>Runtime / inference engine</dt>
<dd>
the code that uses the weights for inference — <code>llama.cpp</code>, MLX, vLLM, SGLang, <code>mlx-lm</code>, <a href="../notebook/local_llm_mac.html#dwarfstar"><code>ds4</code></a>. Where the compute happens.
</dd>
<dt>Server / daemon</dt>
<dd>
a long-lived process wrapping the runtime in an HTTP endpoint, almost always OpenAI- or Anthropic-compatible — <code>ollama serve</code>, <code>llama-server</code>, <a href="../notebook/local_llm_mac.html#osaurus">Osaurus</a>, vLLM, or a hosted endpoint at Anthropic / OpenAI / Google. Stateless from the application’s point of view.
</dd>
<dt>Harness / agent loop (<em>scaffold</em>, in the research literature)</dt>
<dd>
everything that decides <em>how</em> the model gets called and what becomes of its output. The sampling parameters, the system prompt and the tool definitions are harness decisions rather than model ones, and nothing stops them from varying from step to step. This page is about this layer.
</dd>
<dt>Frontend / chat client</dt>
<dd>
the human-facing surface — a desktop chat window, a text-mode UI, a messaging bridge, an editor plugin, a web UI. It’s useful to separate this from the harness, even though doing so is complicated <a href="../notebook/agent_frontends.html">because it has a lot of local complexity depending on where we run it</a>.
</dd>
</dl>
<p>Putting it all together, we get an…</p>
<dl>
<dt>Agent</dt>
<dd>
running model + harness.
</dd>
</dl>
<p>The compute layers (model, runtime, server) are often tightly coupled to each other and are someone else’s problem entirely if we only ever call hosted models; they start to matter when <a href="../notebook/local_llm_mac.html">we run the models ourselves</a>. The layers nearer the user tend to be more loosely connected: the same harness can talk to several servers, and the same server can back several harnesses at once. Many turnkey products are vertical bundles across several layers regardless — <a href="../notebook/local_llm_mac.html#osaurus">Osaurus</a> is frontend + harness + server + runtime in one (although each piece can be run separately), <a href="../notebook/local_llm_mac.html#ollama">Ollama</a> is server + runtime, Claude Desktop is a frontend + harness pointed at Anthropic’s hosted server.</p>
<p>See also <a href="../notebook/automatic_coding.html#models">the LLM models behind the agents</a>, <a href="../notebook/ai_agents_applied.html">actual harness implementations</a> | <a href="../notebook/automatic_coding.html">code agents and assistants</a>,<a href="../notebook/automatic_maths.html">building a maths agent</a>, <a href="../notebook/agent_frontends.html">agent frontends</a>, <a href="../notebook/ai_reasoning.html">Reasoning and proof models</a>.</p>
</section>
<section id="loop" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="loop"><span class="header-section-number">2</span> The loop</h2>
<p>The minimum viable agent is essentially a conversational loop between human and LLM.</p>
<p>Each <span id="def-turn">turn</span>, there are several types of text exchanged, and which pass through the model. Firstly, there is what <em>I</em> say to the model. Secondly, there is what the model responds to me, the human.</p>
<p>Thirdly there are tool use instructions from the model to my machine. Tool use is a request to run something — a shell command, a patch to apply, a Python snippet, a proof to compile. The harness contains an <span id="def-executor"><em>executor</em></span> to, well, execute those requested actions. There are executors of various kinds— a shell, a Python sandbox, a Lean compiler, or whatever. Potentially such commands may be executed on a different machine than the one I am using, <a href="../notebook/ai_agents_applied.html#moving-parts">e.g a sandboxed remote</a>. Fourthly the results of these tool use instructions are packaged up and sent back to the LLM before my next instructions, so the LLM can see the results of its commands.</p>
<p>Although this sounds simple, there can be a lot of texture to even this basic setup: holding a log of messages, parsing and dispatching requests, injecting project instructions and skill descriptions, choosing settings such as sampling temperature or token budget… More sophisticated harnesses extend the design space somewhat.</p>
<section id="executor-kinds" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="executor-kinds"><span class="header-section-number">2.1</span> Kinds of executor</h3>
<p>An executor is an interpreter for some vocabulary of actions, and the vocabulary is the main thing that distinguishes one executor from another. Some executors offer a fixed menu of typed calls — <code>read_file</code>, <code>search</code>, whatever an MCP server exposes. The model’s instructions invoke one from that menu, passing values that fill in the arguments. More generally, some executors are powered by a general-purpose programming language, such as a shell or a Python interpreter, and the model writes whole programs for those. To my mind the mathematical agents are another kind again: here the agent writes formal proofs rather than programs and the results are checked and analyzed by a Lean compiler or a <a href="../notebook/computational_symbolic_maths.html">computer algebra system</a>. The controlled vocabulary option is presumably safer (don’t put dangerous things on the list, simple rules check each), but sacrifices flexibility. A general-purpose language is, by contrast, extremely flexible and arbitrarily dangerous, since code itself is dangerous and hard to monitor.</p>
<p>Which vocabulary to offer is ours to choose, and it is a dispatch decision like any other choice of tool. Two other things differ from one executor to the next — the state its actions land on, and what its return value can tell us about the attempt — but those are set by the task rather than by the interpreter, and they are where the design space starts.</p>
</section>
</section>
<section id="design-space" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="design-space"><span class="header-section-number">3</span> Design space</h2>
<p>A harness is designed for a task, and the task settles two things before we choose anything:</p>
<ol type="1">
<li><strong>State</strong> — whether what the executor acts on can be thrown away.</li>
<li><strong>Verifiers</strong> — what, if anything, can judge a candidate, and how far its verdicts can be trusted.</li>
</ol>
<p>Given those, a harness design makes three choices:</p>
<ol type="1">
<li><strong>Wiring</strong> — the control flow, and in particular whether repeated attempts run in parallel or in sequence.</li>
<li><strong>Context</strong> — what each call sees: which slice of history, retrieved content, and notes.</li>
<li><strong>Dispatch</strong> — who fills each slot: which model, which tool, which verifier, at which price.</li>
</ol>
<p>There is no separate choice about verification: which verifiers a harness consults is dispatch, and where in the call graph it consults them is wiring.</p>
<p>A terminal coding agent is given a real repository and whatever test suite comes with it, and chooses: a sequential loop that runs the suite after each edit; grep and read on demand, with a summary when the window fills; one big model throughout. Every harness has made all three choices, whether or not anyone decided them. The three are not independent of each other or of the two givens, and those dependencies are most of the design work.</p>
<section id="state" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="state"><span class="header-section-number">3.1</span> State</h3>
<p>The first given is the state that the executor’s actions land on. A <span id="def-side-effecting"><em>side-effecting</em></span> executor changes something in the world (writes some code, buys me a flight to Bali…) and thus needs to take into account how its past actions have changed the world. A <span id="def-side-effect-free"><em>side-effect-free</em></span> one has outputs that are pure functions of the inputs. This can be somewhat relative: The same shell could be side-effecting in my working copy of the code but effectively side-effect-free if I run it to see the outputs in <a href="../notebook/automatic_coding.html#workflows">a throwaway container</a>. A mathematical proof checker OTOH has no side-effects; different attempts at the same proof do not interfere with one another.</p>
<p>Side-effect-free executions have the useful property of being repeatable and parallelizable: fifty attempts at a proof cost fifty times the tokens and nothing else, where fifty attempts at a database migration is some kind of slow nightmare. That asymmetry is most of why maths loops sample so freely and fan out so much.</p>
</section>
<section id="verification" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="verification"><span class="header-section-number">3.2</span> Verifiers</h3>
<p>The second given is what, if anything, can judge an attempt. The thing being judged is a <span id="def-candidate"><em>candidate</em></span> — whatever the model has offered as an answer or as progress toward one, a patch, a proof, a plan.</p>
<p>Every execution returns an <em>observation</em>, the output of whatever it did. This might be anything — a directory listing, a stack trace, a compiler error. All of it is information the model can use on its next attempt. Some observations can additionally be read as a <span id="def-verdict"><em>verdict</em></span>, i.e.&nbsp;a judgement on whether the candidate just submitted was “correct” in some well-defined sense, e.g.&nbsp;the tests passed, the proof compiled. A <span id="def-verifier"><em>verifier</em></span> is whatever produces verdicts — a compiler, a test suite, a judge model, me. A <a href="../notebook/proof_assistants.html">Lean</a> compile that fails is a verdict (meaning “not proven”), but it still reports where it failed and which goals were left unsolved at that point. <span class="citation" data-cites="Requena2026Minimal">Requena et al. (2026)</span>’s prover leans on this: Lean will compile a proof with declared gaps, reporting what remains to be shown for each, which turns an unfinished attempt into a to-do list for the next one. A compile with no gaps and no errors, by contrast, ends the loop with a verdict of “successful”.</p>
<p>Verifiers differ in <em>grade</em> — how far their verdicts can be trusted — and the best grade on offer is mostly a fact about the domain: a proof has a compiler, a patch has whatever tests someone wrote, and a flight booking has only my opinion of it. There is no sense making multiple attempts at something if we cannot choose which attempts are better, so the grade limits most of what follows. It is two questions rather than one, and they have different consequences. Is the verifier <em>complete</em> — does it accept everything that is actually correct? And is it <em>incorruptible</em> — can a wrong candidate talk it round?</p>
<p>Incompleteness costs recall, and nothing else. An incomplete verifier throws away some correct candidates — a <a href="../notebook/computational_symbolic_maths.html">computer algebra system</a> (CAS) refuses a pair it cannot simplify — but it cannot be <em>selected against</em>: no amount of sampling teaches a candidate how to be wrong in a way a CAS likes. A corruptible verifier can be. Sample far enough against a judge and we begin selecting for candidates that fool the judge rather than candidates that are right. So incompleteness sets how much of the budget gets wasted, and incorruptibility sets how large the budget can usefully be — which is to say, the second question is the one that licenses sampling wide.</p>
<p>Four combinations, four names:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th>Complete?</th>
<th>Incorruptible?</th>
<th>The usual example</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Exact</strong></td>
<td>yes</td>
<td>yes</td>
<td>the Lean compiler: a proof typechecks or it does not, and a plausible-but-wrong proof still fails</td>
</tr>
<tr class="even">
<td><strong>Heuristic-symbolic</strong></td>
<td>no</td>
<td>yes</td>
<td>a CAS equality check: better than nothing, <a href="../notebook/automatic_maths.html#answer-equivalence">but incomplete</a></td>
</tr>
<tr class="odd">
<td><strong>Empirical</strong></td>
<td>within coverage</td>
<td>no</td>
<td>a test suite, gameable by the time-honoured method of editing the tests</td>
</tr>
<tr class="even">
<td><strong>Soft</strong></td>
<td>no</td>
<td>no</td>
<td>a majority vote, an LLM judge, a <a href="../post/llm_hacks_2026.html#cloud-consensus">consensus panel</a>: the verdict is another model’s opinion</td>
</tr>
</tbody>
</table>
<p>Against an exact verifier, success keeps climbing with the number of attempts (<code>Pass@k</code> in the benchmark jargon: the chance that at least one of <img src="https://latex.codecogs.com/png.latex?k"> samples is the real deal), and prover loops sample in the thousands. The soft grade has a ceiling no judge clears: wherever aggregating means deciding that two outputs say the same thing, that decision is a semantic equality test, and <a href="../notebook/ai_reasoning.html#majk">semantic equality is not in general decidable</a>.</p>
<section id="verifier-use" class="level4" data-number="3.2.1">
<h4 data-number="3.2.1" class="anchored" data-anchor-id="verifier-use"><span class="header-section-number">3.2.1</span> Using verifiers</h4>
<p>The best grade on offer is given; how a harness uses the verifiers it has is not. Every wiring needs a verdict of some sort, since parallel needs one to <em>select</em> among attempts and sequential needs one to <em>inform</em> the next attempt and to decide when to stop.</p>
<p>Which verifiers to consult is a dispatch choice, and grades can be combined. <span class="citation" data-cites="Requena2026Minimal">Requena et al. (2026)</span> back the compiler with deterministic checks for the known dodges (which apparently is a thing) and then use an LLM reviewer for the residue — statement-weakening and metaprogramming tricks that compile without proving the theorem. A soft verdict layered over an exact one buys something neither has alone.</p>
<p>So is whether to spend on the verifier or on the search when the best verifier on offer is soft. Against a soft judge, if we are concerned about reward hacking at inference time, it becomes reasonable to allocate effort into improving the judge instead of widening the search. <a href="../notebook/ai_reasoning.html#judging">Nomos</a> is the worked example of taking that option: a judge trained alongside the proposer, and a pairwise tournament where the vote used to be.</p>
<p>Where in the call graph the verifier gets called is a wiring choice, and it fixes the <em>unit</em> of the verdict — whether it is about a whole candidate, one claim inside it, one sub-goal, or one step of the argument. A verdict on a whole candidate tells the loop only whether to keep it; a verdict on one claim inside the candidate tells the loop which part to fix, which is what gives a sequential wiring something specific to iterate on. The grade constrains the unit: a fine unit is only affordable when the grade is cheap and incorruptible, because a fine unit means calling the verifier hundreds of times per candidate, on fragments, and a judge asked to grade a single step answers no better than it grades the whole thing. That is why the finest-grained systems all sit behind a compiler and the coarse ones are where soft verdicts live. Mathematics has climbed further down this axis than anywhere else — <a href="../notebook/ai_reasoning.html#test-time-scaling">the rungs, and which systems sit on them</a>.</p>
</section>
</section>
<section id="wiring" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="wiring"><span class="header-section-number">3.3</span> Wiring</h3>
<p>Wiring is the control flow of the harness — how many steps, in what order, feeding what. A <span id="def-step">step</span> here is one node in that graph, which is not the same unit as a turn: fifty steps can run at once, where turns only ever come one after another. A harness is a program, and its control flow can be anything a program’s can: it branches, retries, hands pieces of the problem to sub-agents. The popular arrangements have <a href="https://www.anthropic.com/research/building-effective-agents?ref=danmackinlay.name">vendor names</a> — <em>prompt chaining</em>, <em>routing</em>, <em>evaluator–optimizer</em> and the rest — and surveys catalogue more <span class="citation" data-cites="Zhang2025Survey">(Zhang et al. 2025)</span>. I do not think there is a useful taxonomy of all that, any more than there is of call graphs in general.</p>
<p>One decision does recur across harnesses, though: how to spend more than one attempt on the same problem. <em>Parallel</em> wiring samples many attempts at once and picks among them; <em>sequential</em> wiring runs one attempt at a time and feeds each result into the next.</p>
<p>Parallel pays when attempts fail independently and a verdict can cheaply pick out the winners; sequential pays when each failure teaches the next attempt something. The two also spend differently: parallel buys attempts with tokens and gets them back in wall-clock, while sequential buys them with wall-clock and a context window that grows every turn. Fan-out width is a budget decision as much as an accuracy one. It can be a resumability decision too, once a runtime is checkpointing: <a href="https://code.claude.com/docs/en/workflows?ref=danmackinlay.name">Claude Code’s workflow runtime</a> replays cached results in the order agents <em>started</em>, stopping at the first one that did not finish, so everything launched after it re-runs even if it completed. Stopping mid-fan-out therefore costs less the finer the fan-out is, and “many small agents or one long one” turns out to be a question about interrupted runs as well as about accuracy.</p>
<p>The parallel half of this is only available where the executor is side-effect-free. Fifty proof attempts are fifty times the tokens; fifty attempts at the same repository are fifty conflicting sets of edits. So the domain sets the default, and a designer who wants the parallel half has to pay for the isolation that makes it safe, a decision most people never notice making.</p>
<p>Parallel sampling needs an aggregation step to pick among the attempts — majority vote, best-of-<img src="https://latex.codecogs.com/png.latex?n"> against a verifier, filtering out the false answers, <a href="../post/llm_hacks_2026.html#cloud-consensus">LLM-based consensus</a>.</p>
<p>Aggregation is not always available, and whether it is depends on what the branches return. Comparable outputs — a number, a label, a classification, a boxed answer — have a mode, and the mode is a signal. Arguments do not. <img src="https://latex.codecogs.com/png.latex?k"> candidate proofs, <img src="https://latex.codecogs.com/png.latex?k"> design proposals, <img src="https://latex.codecogs.com/png.latex?k"> code-review write-ups are <img src="https://latex.codecogs.com/png.latex?k"> different objects, and a set of arguments has no modal element to take. Where the branches return arguments, parallelism still buys candidates, but the aggregation step has to become a judge or a checker rather than a tally — a different and much more expensive component, and one that can be wrong in ways a tally cannot. The fan-out is the cheap part; the thing on the end of it is not, and what that thing can be depends on the verifiers available. <a href="../notebook/ai_reasoning.html#from-the-target">The maths case is where this bites hardest</a>, but it is not a maths-specific problem.</p>
<p>The two forms compose rather than compete. <span class="citation" data-cites="Requena2026Minimal">Requena et al. (2026)</span> test single-shot parallel sampling against sequential refinement, and find that adding refinement to a parallel fan-out loop improves <em>sample efficiency</em> — more theorems proven per token spent. Fanning out partial attempts, keeping the promising ones and extending those is also the shape of <a href="../notebook/llm_sampling_stunts.html">the particle-style sampling tricks</a>, which do it at the scale of tokens and paragraphs rather than whole attempts.</p>
<p>The other thing wiring fixes is how much of the control flow is guaranteed. A hand-written script that branches and retries does the same thing twice; an agent asked to follow a <code>SKILL.md</code> mostly does. That determinism trades against the flexibility that made an agent worth using in the first place, and it is what an agent emitting a script instead of taking turns is buying.</p>
</section>
<section id="context" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="context"><span class="header-section-number">3.4</span> Context</h3>
<p>What each call sees, i.e.&nbsp;<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents?ref=danmackinlay.name"><em>context engineering</em></a>. Every call to a model endpoint is a fresh call, so the “history” the model brings to the current conversation is a choice, remade each turn. Traditionally that is the system prompt and any skills, some tool definitions, retrieved material, the history so far, and whatever notes the loop has kept. We have considerable scope to vary it.</p>
<p>This breaks in all kinds of interesting ways. Too little context and the model wastes time re-deriving what it worked out an hour ago. Too much context, aside from being expensive, can fail in various complicated ways: we can just run out of context window, attention to the middle of a long context degrades (the <em>lost-in-the-middle</em> effect), and we increase the risk of propagating bad ideas <em>ad infinitum</em>.</p>
<p>The common responses are below, grouped under three verbs for convenience. The list is not exhaustive, but most of what this page later calls <em>memory</em> is built out of these moves.</p>
<p><em>Fetch</em> — leave it on disk, pull it in when wanted.</p>
<dl>
<dt><span id="def-rag">RAG</span></dt>
<dd>
pulls in knowledge the model never had in the weights — Mathlib search, <a href="../notebook/automatic_coding.html#code-search">repo search</a>, <a href="../notebook/ai_search.html">web search</a>.
</dd>
<dt><span id="def-externalization">Externalization</span></dt>
<dd>
does the same for knowledge the loop produced itself: write the state to a file, keep a pointer in the window.
</dd>
</dl>
<p><em>Shed</em> — cross a window boundary by throwing something away.</p>
<dl>
<dt><span id="def-compaction">Compaction</span></dt>
<dd>
summarizes the conversation so far, discards the originals, and starts a fresh window with the summary at the front.
</dd>
<dt><span id="def-rebuild">Rebuild</span></dt>
<dd>
discards the window outright and seeds a fresh one from externalized files, so nothing is ever a summary of a summary. Xiaomi’s term, and the move that session memory turns on.
</dd>
</dl>
<p><em>Delegate</em> — spend a different window.</p>
<dl>
<dt><span id="def-subagent">Sub-agents</span></dt>
<dd>
take a self-contained sub-task to an isolated child — its own conversation history, its own tool budget — and return a summary, so the dirty work (grepping code, searching the web, writing throwaway spikes) happens in a context we then throw away and the parent’s stays lean enough to keep planning. Claude Code supports these natively as <a href="https://code.claude.com/docs/en/agent-teams?ref=danmackinlay.name">agent teams</a>; the <a href="../notebook/ai_agents_applied.html#pi-subagents">pi harness</a> leaves them out deliberately, for the reason below.
</dd>
</dl>
<p>As the timescale stretches, the same moves keep turning up, composed differently and run by different actors, which is the long-horizon problem.</p>
<p><span class="citation" data-cites="Requena2026Minimal">Requena et al. (2026)</span> ablate this choice against no memory at all, a rolling history of recent attempts, and a self-managed note the model writes for itself. <a href="../notebook/automatic_maths.html#scratchpad">The note wins on both theorems proven and cost</a>.</p>
<section id="observability" class="level4" data-number="3.4.1">
<h4 data-number="3.4.1" class="anchored" data-anchor-id="observability"><span class="header-section-number">3.4.1</span> Observability</h4>
<p>Some of those moves throw evidence away. Compaction destroys the record of what happened, a rebuild discards the window it replaces, and a sub-agent’s reasoning is invisible to the parent by construction. An externalized note keeps its record, but it is a summary written by the very thing we want to audit. The bill comes due when something goes wrong and we want to know which turn did it. pi’s objection to sub-agents is exactly this — a child that reports back one paragraph has thrown away the trace we would need to work out why it was wrong — and it is why <a href="../notebook/automatic_coding.html#mimo-code">MiMo Code</a> keeps an unindexed log of every message underneath its structured memory. Observability tends to get discovered after the harness is built, when we want it and no longer have it.</p>
<p><a href="../notebook/automatic_coding.html#dsh">DeepSeek Harness</a> (DSH), DeepSeek’s open-source harness, claims to build it in from the start. Everything the model sees — system prompts, reasoning, tool calls and their results, sub-agent scheduling, every context injection — goes to an append-only session log, and resume, fork, search and replay are described as operations on that one event stream. Where MiMo’s trace sits underneath the summaries as a hedge, DSH makes the log the primary record and everything else a view of it. That goes some way towards pi’s objection, since the scheduling of a sub-agent and what it was handed stay on the record; the landing page does not say whether the child’s own reasoning is kept as well.</p>
</section>
</section>
<section id="dispatch" class="level3" data-number="3.5">
<h3 data-number="3.5" class="anchored" data-anchor-id="dispatch"><span class="header-section-number">3.5</span> Dispatch</h3>
<p>We could use a single frontier model for every step, but typically need not. Specialized models cover specialized capabilities, and cheap ones cover the easy steps to keep the cost down. In the hand-built loops the answer is hard-coded. We <a href="../notebook/automatic_maths.html#oracle">give the agent a <code>solve()</code> oracle</a> and let it decide when to call it, or wire the specialist into the script ourselves, or pick a fast model for the sub-agents and a slow one for the planner. Dispatch is often the cheapest of the three to change, since swapping the model behind a step is frequently a configuration edit, where rewiring or reworking the context means changing the harness’s code. <a href="../notebook/automatic_coding.html#dsh">DeepSeek Harness</a> is built to make the rest cheap to change too, on paper at least: every capability in it — models, tools, skills, sessions, sandboxes, storage, loops, scheduling — is a plugin chosen in a configuration file. That reaches below the tool the model sees: a capability with interchangeable back ends, such as shell execution, is packaged as a service definition, a provider that implements it, and a tool that consumes it, so local Bash could be swapped for another executor without changing anything on the model’s side. We are also free to learn to make this decision rather than hard-code it.</p>
</section>
<section id="interactions" class="level3" data-number="3.6">
<h3 data-number="3.6" class="anchored" data-anchor-id="interactions"><span class="header-section-number">3.6</span> How the givens bound the choices</h3>
<p>Most of the dependencies I keep running into go through wiring, and both givens bound it. The state decides whether the parallel shape is available at all without paying for isolation. The grade decides how wide a parallel wiring can usefully sample and how fine a unit a sequential one gets to iterate on. So where the domain fixes what a verdict can be — as mathematics does, <a href="../notebook/ai_reasoning.html#from-the-target">emphatically</a> — the dependency runs the other way: the verdict we can get decides the shapes we can afford, and that was settled before we arrived.</p>
<p>Among the choices themselves, sequential wiring drags in context, because attempt <img src="https://latex.codecogs.com/png.latex?n+1"> that remembers nothing of attempts <img src="https://latex.codecogs.com/png.latex?1..n"> will make their mistakes again, so the memory design is what stops a sequential loop going in circles. Dispatch is the most nearly independent of the three — whatever the shape, we still choose who fills each slot — though a router makes dispatch part of the control flow, and a cheaper model in each slot changes how wide we can afford to fan out.</p>
<p>That may be why <a href="../notebook/ai_reasoning.html#from-the-target">verifiers and memory keep turning up together in ablations</a> despite looking unrelated: one is what any wiring needs in order to know whether it is getting anywhere, and the other is what the sequential kind needs in order to get anywhere at all.</p>
</section>
<section id="systems" class="level3" data-number="3.7">
<h3 data-number="3.7" class="anchored" data-anchor-id="systems"><span class="header-section-number">3.7</span> Systems compared</h3>
<p>Let us compare some harness design philosophies in terms of the three choices, plus the verifier each one works with, whose grade is the task’s doing rather than the designer’s.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>System</th>
<th>Wiring</th>
<th>Verifier</th>
<th>Context</th>
<th>Dispatch</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>a plain chat loop</td>
<td>none</td>
<td>none</td>
<td>fill the window, then truncate</td>
<td>one model</td>
</tr>
<tr class="even">
<td><a href="../notebook/automatic_coding.html">the terminal coding agents</a></td>
<td>sequential</td>
<td>empirical — did the tests pass</td>
<td><a href="../notebook/automatic_coding.html#code-search">repo RAG</a>, compaction, sub-agents</td>
<td>one model, plus a cheap screener</td>
</tr>
<tr class="odd">
<td><a href="../notebook/automatic_maths.html#compute">AxProverBase</a></td>
<td>sequential refinement</td>
<td>exact — the Lean compiler</td>
<td>an externalized note plus Mathlib RAG</td>
<td>one frontier model throughout</td>
</tr>
<tr class="even">
<td><a href="../notebook/ai_reasoning.html#judging">Nomos</a></td>
<td>parallel, then a tournament</td>
<td>soft — a judge trained alongside</td>
<td>per-worker, independent</td>
<td>one model in both roles</td>
</tr>
<tr class="odd">
<td><a href="../notebook/automatic_coding.html#mimo-code">MiMo Code</a></td>
<td>sequential, plus emitted scripts</td>
<td>soft — a natural-language done-condition</td>
<td>externalize and rebuild, written by a subagent</td>
<td>fan-out on planning turns only</td>
</tr>
<tr class="even">
<td><a href="../notebook/ai_agents_applied.html#hermes-agent">Hermes</a>, <a href="../notebook/ai_agents_applied.html#openclaw">OpenClaw</a></td>
<td>sequential</td>
<td><strong>none</strong> — the user is the verdict</td>
<td>persistent externalized memory plus self-written skills</td>
<td>one model, switchable mid-session</td>
</tr>
<tr class="odd">
<td>Ornith-1.0</td>
<td><em>learned</em></td>
<td>exact, plus a monitor and a frozen judge</td>
<td><em>learned</em></td>
<td><em>learned</em></td>
</tr>
</tbody>
</table>
<p>The <a href="../notebook/automatic_coding.html">coding harnesses</a> tend towards sequential design because they need to handle side effects in a controlled order, although MiMo Code introduces some weird twists. The <a href="../notebook/automatic_maths.html">maths agents</a> are <a href="../notebook/ai_reasoning.html#from-the-target">way less constrained</a> because theorems don’t interact with one another, unless the world is weirder than we thought. This makes them relaxing to design (<a href="../notebook/automatic_maths.html#path">I designed one for fun</a>). The always-on assistants are not really verifiable in any meaningful sense and thus <a href="../notebook/ai_agents_applied.html#agent-risks">dangerous left alone</a>. Ornith is kinda ill-defined, learning its own harness. <a href="../notebook/automatic_coding.html#dsh">DSH</a> is also ill-defined, the parameters being determined by the mounted plugins.</p>
</section>
</section>
<section id="long-horizon" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="long-horizon"><span class="header-section-number">4</span> Long horizon</h2>
<p>A long-running agent — one that holds a task across hundreds of turns, or where we return to a project after a week off — runs into problems a single turn does not show. Per-turn errors compound, the context window fills, and whatever the agent worked out in one session is gone by the next, because current LLMs are not <a href="../notebook/continual_learning.html">continual learners</a>. The subsections below take those in that order.</p>
<section id="reliability" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="reliability"><span class="header-section-number">4.1</span> Multi-turn reliability</h3>
<p>A long-horizon run is a chain of individual turns — read context, decide, call a tool, repeat. Each turn accumulates some additional chance of a wrong call: editing the wrong file, passing a test by changing the test… Those per-turn odds compound, so even a low rate dominates a long enough run — at an i.i.d. error rate of 2% per turn, a 200-turn task finishes clean only about 2% of the time (<img src="https://latex.codecogs.com/png.latex?0.98%5E%7B200%7D%5Capprox0.02">). A short interactive session is easy to correct — we, the humans, spot the bad turn and fix it. But the <a href="../notebook/ai_economics_automation.html">economic benefit that agents are being sold to us on</a> involves unattended multi-hour runs. In that case, we really want to juice that error rate down to top out the <a href="https://metr.org/blog/2025-03-19-measuring-ai-ability-to-complete-long-tasks?ref=danmackinlay.name">famous reliable task-length metric</a>.</p>
<p>Under that model there are three things to attack. We can make each turn less likely to go wrong, make the chain shorter so there are fewer turns available to go wrong, or catch the wrongness at the end and go round again.</p>
<ul>
<li><strong>Buy down the per-turn odds, and spend to do it.</strong> <a href="../notebook/automatic_maths.html#fan-out">Sample several candidate plans in parallel</a> and use a low-temperature judge to pick one, aimed at the planning turns where a wrong decision costs the most downstream. A marginal gain at a large cost, which might still be the right trade on a high-stakes task.</li>
<li><strong>Shorten the chain by emitting code instead of taking turns.</strong> The agent writes a script — spawn an agent, run these in parallel, pipeline those — and a sandbox runs it deterministically. This is the wiring choice again, with branch and retry logic guaranteed by code rather than by a model remembering to follow a <code>SKILL.md</code>, which means many fewer per-turn decisions are available to go wrong. That code-beats-prompt principle for predictable control flow is originally <a href="https://www.anthropic.com/research/building-effective-agents?ref=danmackinlay.name">Anthropic’s</a>, and Anthropic now ships the worked version of it too.</li>
<li><strong>Catch the failures at the end with a stopping-condition verifier.</strong> The user states a done-condition in natural language, and each time the agent tries to stop, an independent model call checks the whole history against it; if the work is not finished, it whinges about the shortcomings and tries again. This is the disciplined form of the Ralph Wiggum loop — the brute-force pattern of relaunching an agent on the same prompt until the job is done.</li>
</ul>
<p>All three ship in <a href="../notebook/automatic_coding.html#mimo-code">MiMo Code</a>, as <em>Max mode</em>, <em>Dynamic Workflow</em> and <em>Goal</em>, and <a href="https://mimo.xiaomi.com/blog/mimo-code-long-horizon?ref=danmackinlay.name">Xiaomi’s write-up</a> is where I got this way of dividing the problem.</p>
<p>Two systems have converged on the second one, down to the name. Xiaomi calls it <em>Dynamic Workflow</em>; Claude Code calls it <a href="https://code.claude.com/docs/en/workflows?ref=danmackinlay.name"><em>dynamic workflows</em></a>, also JavaScript, also <code>agent()</code> and <code>pipeline()</code>, written by the model and executed by a runtime outside the conversation. The docs frame the distinction better than I had: what separates a workflow from a subagent is <em>who holds the plan</em> — a subagent leaves Claude deciding turn by turn with every intermediate result landing in the window, where a script holds the loop, the branching and the intermediates itself, so only the final answer comes back. That makes it a context move as much as a wiring one: emitting code is externalization applied to control flow. It is also where the quality patterns get somewhere to live — adversarial cross-review of findings before they are reported, or drafting a plan from several angles and weighing them — because a script can express “then check this” in a way a prompt can only ask for.</p>
<p>DSH has a smaller-grained relative, <em>Code mode</em>, which exposes the tools through an SDK so the model can do several steps in one TypeScript program instead of one turn per tool call. It is the same bet — code rather than a sequence of turns holds the control flow — made over tool calls, where the two workflow runtimes make it over agents.</p>
<p>Note what the third one needs — an independent judgement of whether the work is actually done, stated in natural language and evaluated by a model. That is a soft verdict guarding a long and expensive run, with all the foolability that implies, and the longer the run the more opportunity the agent has had to talk itself into being finished.</p>
</section>
<section id="session-memory" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="session-memory"><span class="header-section-number">4.2</span> Memory within a session</h3>
<p>One long conversation about one cohesive project — the app I am building, my tax return — outlives its window well before it outlives its usefulness. Session memory is whatever policy we pick over the context moves to carry it across that boundary.</p>
<p>The default policy is compaction and nothing else. We might imagine this could even be beneficial, because the summary might skip over red herrings and false turns in the original, no? In practice many things can go wrong. Compaction degrades on long tasks, because each summary is a lossy compression of the previous summary and the losses compound the same way the per-turn errors do.</p>
<p>Two policies that do better.</p>
<p><strong>Let the human pick what survives.</strong> Still compaction, but the cut is chosen rather than automatic. pi’s <a href="../notebook/ai_agents_applied.html#pi-subagents"><code>/tree</code></a> jumps back to an earlier point in the history and collapses everything <em>after</em> into a précis, making what counts as “currently” relevant context an overt user choice.</p>
<p><strong>Move memory out of the main loop.</strong> Externalization and a sub-agent and a rebuild, with compaction dropped entirely. The main agent keeps almost no notes of its own; a separate <em>writer</em> subagent, dispatched by the runtime rather than invoked by the model, reads the conversation and commits structured checkpoints to disk. It fires early — well below the limit — so the extraction happens while there is still room to think, and before lost-in-the-middle erodes the thing being extracted. When the window does fill, the runtime rebuilds from those files instead of summarizing what is in it, so from the model’s gaslit point of view the conversation never breaks.</p>
<p><a href="../notebook/automatic_coding.html#mimo-code">MiMo Code</a> is the fullest worked example I know of, and <a href="https://mimo.xiaomi.com/blog/mimo-code-long-horizon?ref=danmackinlay.name">Xiaomi document it</a> in enough detail to copy. The writer fires at roughly 20%, 45% and 70% of the context budget, each pass an incremental update to the last rather than a fresh summary of a summary, which is what breaks the compounding-loss chain. Its output sits on four lifecycles: <code>checkpoint.md</code> for this session, <code>MEMORY.md</code> for this project, a global file for this user, and an unindexed SQLite trace of every message and tool call underneath. That bottom layer is the observability hedge, since everything above it is a summary written by the thing we would want to audit. Each structured file has exactly one writer, enforced in code rather than by prompt; the main agent’s one channel is a free-form scratchpad that the writer drains and clears at each checkpoint.</p>
<p>The general form is older than the coding agents and has a better name: Letta’s <a href="https://www.letta.com/blog/sleep-time-compute?ref=danmackinlay.name">sleep-time compute</a>, in which the primary agent is deliberately not given the tools to edit its own memory and a background agent gets them instead. <a href="../notebook/ai_agents_applied.html#openclaw">OpenClaw</a> cites the same work for its consolidation pass. <a href="../notebook/automatic_coding.html#qwen-code">Qwen Code</a> looks like this and is not, which cost me some confusion: it does run <a href="https://github.com/QwenLM/qwen-code/blob/main/docs/design/auto-memory/memory-system.md?ref=danmackinlay.name">a background extraction agent</a> over the transcript, but it fires once per turn rather than at budget fractions, it harvests durable facts about the user rather than what the agent is in the middle of, and when the window actually fills it compacts like everyone else.</p>
</section>
<section id="evolution" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="evolution"><span class="header-section-number">4.3</span> Learning across sessions</h3>
<p>OK, now <em>ultra</em>-long horizons. Not just tasks, but whole projects. Or whole careers, but let’s not get ahead of ourselves.</p>
<p>If we come back to the same project regularly and each time the agent has forgotten everything, it feels like we’re leaving performance on the table, re-deriving the same constraints and repeating the same mistakes. A new session starts empty, so everything it knows it reads off disk, and the interesting question is how anything got written there.</p>
<p>Not by being composed at project scale — by surviving. MiMo’s writer moves an observation from <code>checkpoint.md</code> up into <code>MEMORY.md</code> once it has held steady across several session checkpoints, so the project file is a filter over the session files rather than a separate act of authorship.</p>
<p>What is new at this timescale is rot. A session checkpoint is overwritten every few hours and never lives long enough to go stale, where a project memory accretes for months, its file references decay, and no one in the session loop is looking after it. So we get background agents grinding over the agent’s own history, and they want two clocks rather than one. The faster one <em>tends the facts</em> — MiMo calls it <em>Dream</em>: weekly, it reads past sessions and the memory files, merges and deduplicates, checks that file references still resolve, and compresses the result back down before it sprawls. The slower one <em>harvests process rather than facts</em> — <em>Distill</em>: monthly, it hunts for recurring work patterns and solidifies them into reusable skills, CLI commands, and standing procedure documents.</p>
<p>Only the first of those is still memory. A skill changes what the agent <em>does</em> rather than what it knows, which is why this section is learning and the one above is not — and it is the same move the next one makes on the harness itself.</p>
<p>The dreaming half is sleep-time compute again with the period stretched from minutes to weeks, and enough people have arrived at it independently that they have converged on the name: MiMo, <a href="../notebook/automatic_coding.html#qwen-code">Qwen Code</a> and Letta all ship a <em>dream</em> pass, with Qwen’s gated on a day having passed and five new sessions having accumulated. <a href="../notebook/ai_agents_applied.html#hermes-agent">Hermes</a> points the same machinery at the <em>user</em> rather than the <em>project</em>, accreting auto-generated skills, FTS5-searchable session history, and a persistent model of our preferences via Honcho’s dialectic user-modelling, on the pitch that the longer it runs the more it knows about us. <a href="../notebook/ai_agents_applied.html#openclaw">OpenClaw</a> keeps persistent memory in the same spirit, and cites Letta for it. Either way the library is now writing itself, which is <a href="../notebook/ai_agents_applied.html#agent-risks">a standing risk</a>, and a worse one here than within a session: a bad session checkpoint dies with the session, where a bad skill is loaded into every session after it.</p>
</section>
</section>
<section id="learned" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="learned"><span class="header-section-number">5</span> Learning the harness</h2>
<p>A system rewriting its own scaffold is the same loop as the background agents above, moved from deployment time to training time — tighter feedback signal, faster clock, and the same reason to audit what it mints.</p>
<p>The scaffold started as something hand-written and tuned by folklore. Then the pieces got <em>measured</em>: <span class="citation" data-cites="Requena2026Minimal">Requena et al. (2026)</span> ablate a prover harness bottom-up, removing one piece at a time to see what it was contributing, and <a href="../notebook/automatic_maths.html#path">rank what they find</a> — treating harness wirings as experimental subjects rather than as taste. DSH ships the same instinct as a product feature: a <em>Minimal mode</em> with only a shell and a file editor, pitched as a way to benchmark models without the rest of the harness in the way.</p>
<p>Then a component gets trained, and dispatch is the one that has fallen first. <span class="citation" data-cites="Su2025ToolOrchestra">Su et al. (2025)</span> make the routing itself the learned thing: an 8B <em>orchestrator</em> model, trained by RL with outcome-, efficiency-, and user-preference-aware rewards, that coordinates bigger models and tools rather than answering directly. They report it edging out GPT-5 on Humanity’s Last Exam at ~2.5× lower cost, and generalizing to tools unseen in training. It is an inversion: the intelligence is in the dispatched-to components, and the small model’s job is knowing what a query costs and who should handle it.</p>
<p>The end of that road is <a href="https://deep-reinforce.com/ornith_1_0.html?ref=danmackinlay.name">Ornith-1.0</a>, which makes the scaffold itself a learnable object.<sup>1</sup> In each RL step the model first proposes a refined scaffold for the task, then rolls out a solution under that scaffold. Reward flows to both stages, so scaffolds mutate and get selected toward whatever elicits higher-reward trajectories. No hand-engineered harness design at all, and their 397B model matches or passes Claude Opus 4.7 on Terminal-Bench 2.1 and SWE-Bench Verified, if we take their word for it. The three choices locate a harness as a point in a fixed design space; a self-scaffolding trainer searches over the space itself, and the wirings and context policies are what mutate.</p>
<p>The failure mode arrives with the capability, as usual. A self-authored scaffold learns to satisfy the verifier without doing the task — touching the checked-for file, hardcoding the expected output, copying an oracle solution lying about in the environment. Ornith’s defence is three layers: an immutable outer trust boundary the model cannot edit, a deterministic monitor that zeroes the reward on any attempt to touch withheld paths or verification scripts, and a frozen LLM judge that vetoes gaming attempts even when they stay inside the sanctioned tool surface. Which is the verification question again, seen from the training side: the softer the verdict, the more a learned harness will bend it, so the trust boundary has to be exactly as hard as the verifier is soft.</p>
<p>The other half of the problem is taking a bad scaffold back out, which is a software-architecture question rather than a training one. The Cordis kernel under DSH is built for adding and removing components at runtime, and its paper names self-evolving agent harnesses as one motivating case <span class="citation" data-cites="Shi2026Programming">(Shi, Zhang, and Cui 2026)</span>. Its <em>temporal composability</em> means that removing a component reverts every change that component made inside the system; its <em>spatial composability</em> means each component declares what it depends on and is switched on and off as those dependencies come and go. The paper’s evidence is Koishi, a chatbot framework with over four thousand community plugins; the self-evolving harness appears as a motivation and as future validation, not as something tested. DSH’s <em>Creator mode</em> is the product end of this: the agent inspects the running harness, tries plugins out in memory, and combines them into new modes. The <a href="https://deepseek-harness.github.io/deepseek-harness/en/develop/practice/dynamic-cordis?ref=danmackinlay.name">docs</a> say these model-authored plugins vanish when unmounted or when the process exits, and may affect other sessions in the same process meanwhile. If it works as described, this sounds like a useful answer to the self-written library: a bad self-authored skill packaged as a plugin could be unmounted without residue, rather than being loaded into every session after it. Two limits, both stated in the paper. First, reverting stops at what it calls the <em>system boundary</em>: a location is inside only if the system alone can modify it and restore it. Cordis cleans up what a plugin registered through <code>ctx</code> — listeners, tools, timers, and anything handed an explicit disposer with <code>ctx.effect()</code> — and a scratch file under a private path can count as inside, but a repository that git, an editor and a human also touch is outside. A write to it cannot be reverted, only withheld until it is safe or compensated for afterwards, and the paper’s guarantees do not carry over to compensation, so Cordis does not make a coding executor side-effect-free. Second, dependency declarations are access control, not a sandbox: the paper says untrusted component code needs an external sandbox, which matters when the untrusted author is the model.</p>
</section>
<section id="incoming" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="incoming"><span class="header-section-number">6</span> Incoming</h2>
<ul>
<li><p><a href="https://x.com/voooooogel/status/2015976774128341421?ref=danmackinlay.name">The always-perspicacious Theia Vogel</a></p>
<blockquote class="blockquote">
<p>some thoughts and speculation on future model harnesses</p>
<p>it’s fun to make jokes about gas town and other complicated orchestrators, and similarly probably correct to imagine most of what they offer will be dissolved by stronger models the same way complicated langchain pipelines were dissolved by reasoning. but how much will stick around?</p>
</blockquote></li>
<li><p><a href="https://www.microsoft.com/en-us/research/blog/skillopt-agent-skills-as-trainable-parameters/?ref=danmackinlay.name">SkillOpt: Agent skills as trainable parameters</a></p>
<blockquote class="blockquote">
<p>In our recent paper, <a href="https://www.microsoft.com/en-us/research/publication/skillopt-executive-strategy-for-self-evolving-agent-skills/?ref=danmackinlay.name">SkillOpt: Executive Strategy for Self-Evolving Agent Skills</a> <span class="citation" data-cites="Yang2026SkillOpt">(Yang et al. 2026)</span>, we reframe the question from “how do we write a better prompt?” to “how do we train the skill?” SkillOpt treats the skill file as a trainable parameter living outside a frozen target model, bringing a training-style optimization loop, consistent gains across 52 evaluation cells, and a compact skill file that stays readable, auditable, and transferable.</p>
</blockquote></li>
<li><p><a href="https://github.com/J-Rosser-UK/AgentBreeder?tab=readme-ov-file&amp;ref=danmackinlay.name">J-Rosser-UK/AgentBreeder: Mitigating the AI Safety Impact of Multi-Agent Scaffolds</a> <span class="citation" data-cites="Rosser2025AgentBreeder">(Rosser and Foerster 2025)</span> — the safety-flavoured cousin of Ornith’s scaffold search: we use evolutionary search over multi-agent scaffolds, aimed either at jailbreaking the base model or at combining safety with task reward.</p></li>
<li><p><a href="https://alexzhang13.github.io/blog/2026/harness/?ref=danmackinlay.name">Language model harnesses are compositional generalizers | Alex L. Zhang</a></p>
<blockquote class="blockquote">
<p>Concretely, we think a good harness is one that shapes each call to the underlying Transformer so that every observation is <em>locally in-distribution</em>, i.e.&nbsp;each Transformer call handles a prompt that is in-distribution with respect to its training data. In fact, a good harness can frequently reduce problems that seem to require breakthroughs in post-training into almost mundane capabilities of the existing generation of language models. We first showed a version of this for long context processing <a href="https://alexzhang13.github.io/blog/2025/rlm/?ref=danmackinlay.name">nearly a year ago</a> and, in this post, we show that this principle extends to the efficiency of <em>learning</em> itself. That is, what a model learns through a well-designed harness generalizes across task lengths and across domains far better than training the neural network on its own does.</p>
<p>We test this by using reinforcement learning (RL) to train a Recursive Language Model (RLM), a harness in which the model offloads its context and defers execution to programmatic decomposition and recursive sub-calls. The results are summarized in Figure 1. Training on only short tasks generalizes to held-out tasks 8–32x longer, with roughly 10x the eval lift with the same train lift over training the underlying Transformer directly. Moreover, training on one domain transfers to other domains at a far better rate than that of a vanilla Transformer.</p>
</blockquote></li>
</ul>
</section>
<section id="references" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="references"><span class="header-section-number">7</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Park2026Scaling" class="csl-entry">
Park, Kontonis, Garg, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2609.21032">Scaling Discovery Through Test-Time Communication</a>.”</span>
</div>
<div id="ref-Requena2026Minimal" class="csl-entry">
Requena, Letson, Nowakowski, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2602.24273">A Minimal Agent for Automated Theorem Proving</a>.”</span>
</div>
<div id="ref-Rosser2025AgentBreeder" class="csl-entry">
Rosser, and Foerster. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2502.00757">AgentBreeder: Mitigating the AI Safety Impact of Multi-Agent Scaffolds via Self-Improvement</a>.”</span>
</div>
<div id="ref-Shi2026Programming" class="csl-entry">
Shi, Zhang, and Cui. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2608.25512">A Programming Paradigm for Spatiotemporal Composability</a>.”</span>
</div>
<div id="ref-Su2025ToolOrchestra" class="csl-entry">
Su, Diao, Lu, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.21689">ToolOrchestra: Elevating Intelligence via Efficient Model and Tool Orchestration</a>.”</span>
</div>
<div id="ref-Yang2026SkillOpt" class="csl-entry">
Yang, Gong, Huang, et al. 2026. <span>“<a href="https://www.microsoft.com/en-us/research/publication/skillopt-executive-strategy-for-self-evolving-agent-skills/">SkillOpt: Executive Strategy for Self-Evolving Agent Skills</a>.”</span>
</div>
<div id="ref-Zhang2025Survey" class="csl-entry">
Zhang, Lyu, Sun, et al. 2025. <span>“<a href="https://arxiv.org/abs/2503.24235">A Survey on Test-Time Scaling in Large Language Models: What, How, Where, and How Well?</a>”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>DeepReinforce ships an Ornith <em>family</em>: the scaffold-learning result below is their 397B, while the <a href="../notebook/local_llm_mac.html#models-math">9B and 35B we can actually download</a> are ordinary agentic models trained the same way. Same name, very different objects.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>faster pussycat</category>
  <category>machine learning</category>
  <category>neural nets</category>
  <category>NLP</category>
  <category>premature optimization</category>
  <category>slop</category>
  <category>technology</category>
  <guid>https://danmackinlay.name/notebook/agent_harness_design.html</guid>
  <pubDate>Thu, 16 Jul 2026 22:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/human_frame_RP-P-OB-84.594.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Renormalization for ML</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/renormalization_for_ml.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>LLM-drafted — unverified
</div>
</div>
<div class="callout-body-container callout-body">
<p>Drafted by an LLM as a quick orientation aid: a telegraphic dictionary mapping renormalization-group (RG) and QFT vocabulary onto Bayesian-ML concepts. Not yet checked, deduplicated against the rest of the notebook, or rewritten. 🚧TODO🚧</p>
</div>
</div>
<div id="fig-dishes-stack" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-dishes-stack-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/dishes_stack_RP-P-OB-80.401A.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/dishes_stack_RP-P-OB-80.401A.avif" id="fig-dishes-stack" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-dishes-stack-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Prompted by <a href="https://www.lesswrong.com/s/3fknGqkujhGrnRodA?ref=danmackinlay.name">Renormalizing interpretability</a> (PIBBSS; Greenspan &amp; Vaintrob), which proposes importing renormalization into neural-network interpretability. Their plan has a fever-dream shape for me, since I am not a physicist, and I don’t know their vocabulary, for all that I recognize some parts of it. It <em>looks like</em> <a href="../notebook/variational_inference.html">variational approximation</a> of probabilistic estimates (the “momentum-space, perturbative” flavour — literally so <span class="citation" data-cites="Mehta2014Exact">(Mehta and Schwab 2014)</span>) and like <a href="../notebook/graphical_models_factor.html">graph marginalization</a> (the “real-space, block-spin” flavour).</p>
<section id="dictionary" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="dictionary"><span class="header-section-number">1</span> Translation table</h2>
<p>A physics “model” is a log-density: <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bconfig%7D)%20=%20e%5E%7B-%5Cbeta%20H%7D/Z">. Read the energy/action <img src="https://latex.codecogs.com/png.latex?H"> as a negative log joint, the partition function <img src="https://latex.codecogs.com/png.latex?Z"> as the evidence, and the free energy <img src="https://latex.codecogs.com/png.latex?-%5Ctfrac%7B1%7D%7B%5Cbeta%7D%5Clog%20Z"> as the negative log evidence. Then:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Renormalization / QFT</th>
<th>Bayesian ML</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Boltzmann distribution <img src="https://latex.codecogs.com/png.latex?e%5E%7B-%5Cbeta%20H%7D/Z"></td>
<td>posterior (or any model) <img src="https://latex.codecogs.com/png.latex?%5Cpropto%20e%5E%7B-%5Ctext%7Benergy%7D%7D"></td>
</tr>
<tr class="even">
<td>Hamiltonian / action / energy <img src="https://latex.codecogs.com/png.latex?H"></td>
<td>negative log joint (unnormalized log-density)</td>
</tr>
<tr class="odd">
<td>Partition function <img src="https://latex.codecogs.com/png.latex?Z"></td>
<td>normalizing constant / marginal likelihood (evidence)</td>
</tr>
<tr class="even">
<td>Free energy <img src="https://latex.codecogs.com/png.latex?-%5Ctfrac1%5Cbeta%5Clog%20Z"></td>
<td>negative log evidence (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%201">)</td>
</tr>
<tr class="odd">
<td>Variational free energy</td>
<td>negative ELBO (<img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D_q%5BH%5D-S%5Bq%5D">); gap <img src="https://latex.codecogs.com/png.latex?=%5Cmathrm%7BKL%7D(q%5CVert%20p)"></td>
</tr>
<tr class="even">
<td>Mean-field theory</td>
<td>mean-field (factorized-<img src="https://latex.codecogs.com/png.latex?q">) variational inference — same self-consistency equations</td>
</tr>
<tr class="odd">
<td>Saddle point / classical solution</td>
<td>MAP / posterior mode</td>
</tr>
<tr class="even">
<td>Loop expansion (powers of <img src="https://latex.codecogs.com/png.latex?%5Chbar">)</td>
<td>expansion around the Laplace approximation (powers of posterior variance)</td>
</tr>
<tr class="odd">
<td>Temperature <img src="https://latex.codecogs.com/png.latex?1/%5Cbeta"></td>
<td>likelihood tempering; <img src="https://latex.codecogs.com/png.latex?1/n">; observation noise <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2"></td>
</tr>
<tr class="even">
<td>Integrating out / coarse-graining</td>
<td>marginalization</td>
</tr>
<tr class="odd">
<td>Real-space (block / decimation) RG</td>
<td>variable elimination on an MRF, then project back to a fixed family (<img src="https://latex.codecogs.com/png.latex?%5Capprox"> expectation propagation)</td>
</tr>
<tr class="even">
<td>Effective Hamiltonian</td>
<td>negative log marginal density of the retained variables</td>
</tr>
<tr class="odd">
<td>Couplings <img src="https://latex.codecogs.com/png.latex?g">; running couplings <img src="https://latex.codecogs.com/png.latex?g(%5CLambda)"></td>
<td>model / natural parameters; effective parameters as a function of resolution</td>
</tr>
<tr class="even">
<td>Cutoff <img src="https://latex.codecogs.com/png.latex?%5CLambda"></td>
<td>resolution / bandwidth / level of detail retained</td>
</tr>
<tr class="odd">
<td>RG flow</td>
<td>trajectory of parameters under iterated marginalize-and-rescale</td>
</tr>
<tr class="even">
<td>Fixed point</td>
<td>scale-invariant (self-similar) model</td>
</tr>
<tr class="odd">
<td>Gaussian fixed point</td>
<td>Gaussian / <a href="../notebook/gp.html">GP</a> / <a href="../notebook/laplace_approx.html">Laplace</a> / <a href="../notebook/nn_wide.html">NNGP</a> limit</td>
</tr>
<tr class="even">
<td>Relevant operator</td>
<td>parameter direction that grows under coarse-graining (matters macroscopically)</td>
</tr>
<tr class="odd">
<td>Irrelevant operator</td>
<td>direction that shrinks / washes out (microscopic detail)</td>
</tr>
<tr class="even">
<td>Universality class</td>
<td>basin of attraction of a fixed point (micro-models sharing macro behaviour)</td>
</tr>
<tr class="odd">
<td>Free theory + propagator</td>
<td>Gaussian reference + prior covariance (kernel)</td>
</tr>
<tr class="even">
<td>Perturbation theory / Feynman diagrams</td>
<td>cumulant / <a href="../notebook/approximating_dists.html">Edgeworth expansion</a> around the Gaussian; diagrams = cumulant bookkeeping</td>
</tr>
<tr class="odd">
<td>Interactions / non-Gaussianity</td>
<td>higher cumulants / non-Gaussian corrections</td>
</tr>
<tr class="even">
<td>Effective field theory</td>
<td>low-dimensional parametric surrogate for the marginal / predictive</td>
</tr>
<tr class="odd">
<td>Renormalizability</td>
<td>the coarse model stays in a finite-dimensional family across scales (closed <a href="../notebook/expectation_propagation.html">assumed-density recursion</a>)</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>The CLT is an RG flow.</strong> The map “sum <img src="https://latex.codecogs.com/png.latex?n"> iid, recentre, divide by <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7Bn%7D">” has the Gaussian as its attracting fixed point; finite-variance laws are its universality class; the Edgeworth/cumulant corrections (skew <img src="https://latex.codecogs.com/png.latex?%5Csim%20n%5E%7B-1/2%7D">, excess kurtosis <img src="https://latex.codecogs.com/png.latex?%5Csim%20n%5E%7B-1%7D">) are the irrelevant operators that decay along the flow; centring is tuning the one relevant direction to criticality. (This is Vaintrob’s <em>Laws of Large Numbers</em> post.)</li>
<li><strong>Decimation is variable elimination plus EP.</strong> Real-space RG sums out a sublattice — exactly variable elimination on a Markov random field — then projects the induced all-order couplings back onto a chosen family (e.g.&nbsp;nearest-neighbour), a moment-matching / assumed-density step. So an RG step is <em>(marginalize → project-to-family)</em>, i.e.&nbsp;expectation propagation indexed by scale.</li>
</ul>
</section>
<section id="new" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="new"><span class="header-section-number">2</span> Renormalization stuff that I have not seen in ML</h2>
<p>Most of the table seems to be <em>relabelling</em> familiar machinery — evidence, negative log evidence, mean-field VI, Laplace, cumulant expansions, Gaussian processes. However, some ideas have no routine Bayesian analogue AFAIK:</p>
<ol type="1">
<li><strong>A flow on model space.</strong> Iterated coarse-graining traces a <em>trajectory of models across scales</em>, not one approximation at one scale. Variational inference gives the single approximation; RG gives the path and its dynamics.</li>
<li><strong>Relevant vs irrelevant directions.</strong> A Jacobian-eigenvalue (local sensitivity) analysis of that flow at a fixed point: which parameters grow under coarse-graining (survive to the macro-scale) and which decay (microscopic detail).</li>
<li><strong>Universality.</strong> Many micro-models flow to the same macro-model — a principled reason coarse descriptions are robust to microscopic detail.</li>
</ol>
<p>Plus one mechanical ingredient ordinary Bayes lacks: the <strong>rescaling</strong> step after each marginalization (the <img src="https://latex.codecogs.com/png.latex?%5Cdiv%5Csqrt%7Bn%7D"> below) that makes the iteration a flow with fixed points rather than a sequence of ever-coarser marginals.</p>
</section>
<section id="nn" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="nn"><span class="header-section-number">3</span> Neural-network connections</h2>
<ul>
<li><strong>Infinite width</strong> → the Gaussian fixed point: <a href="../notebook/nn_wide.html">a GP prior over functions (NNGP)</a>, neurons non-interacting, propagator = NNGP kernel.</li>
<li><strong><img src="https://latex.codecogs.com/png.latex?1/%5Ctext%7Bwidth%7D"></strong> → the coupling constant; finite width → weakly-interacting (perturbative) non-Gaussian corrections.</li>
<li><strong>Depth</strong> → the RG flow itself: each layer is one marginalize-and-rescale step (PDLT’s <img src="https://latex.codecogs.com/png.latex?d/w"> is the step size).</li>
<li>something about <a href="../notebook/neural_tangent_kernel.html">Neural tangent kernel</a></li>
</ul>
</section>
<section id="incoming" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="incoming"><span class="header-section-number">4</span> Incoming</h2>
<ul>
<li><p><a href="../notebook/singular_learning_theory.html">Singular learning theory</a> — Watanabe’s free energy and the overparametrized (singular) posterior; Vaintrob’s <em>Basics of Bayesian learning</em> is SLT setup.</p></li>
<li><p><a href="https://www.lesswrong.com/posts/9Ff4y4ahNjrGHMdqL/progress-update-synthetic-models-of-natural-data?ref=danmackinlay.name">Progress update: synthetic models of natural data — LessWrong</a></p></li>
<li><p><a href="https://www.symmetrybroken.com/transformer-as-renormalization-group-flow/?ref=danmackinlay.name">The Transformer as Renormalization Group Flow</a></p>
<blockquote class="blockquote">
<p>The forward pass through a transformer implements a Kadanoff-Wilson renormalization group flow, coarse-graining microscopic token representations into stable semantic attractors.</p>
</blockquote></li>
</ul>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Halverson2021Neural" class="csl-entry">
Halverson, Maiti, and Stoner. 2021. <span>“<a href="https://doi.org/10.1088/2632-2153/abeca3">Neural Networks and Quantum Field Theory</a>.”</span> <em>Machine Learning: Science and Technology</em>.
</div>
<div id="ref-KochJanusz2018Mutual" class="csl-entry">
Koch-Janusz, and Ringel. 2018. <span>“<a href="https://doi.org/10.1038/s41567-018-0081-4">Mutual Information, Neural Networks and the Renormalization Group</a>.”</span> <em>Nature Physics</em>.
</div>
<div id="ref-Mehta2014Exact" class="csl-entry">
Mehta, and Schwab. 2014. <span>“<a href="https://doi.org/10.48550/arXiv.1410.3831">An Exact Mapping Between the Variational Renormalization Group and Deep Learning</a>.”</span>
</div>
<div id="ref-Roberts2022Principlesa" class="csl-entry">
Roberts, Yaida, and Hanin. 2022. <em><a href="http://arxiv.org/abs/2106.10165">The Principles of Deep Learning Theory</a></em>.
</div>
</div>


</section>

 ]]></description>
  <category>Bayes</category>
  <category>machine learning</category>
  <category>physics</category>
  <category>sciml</category>
  <category>statmech</category>
  <guid>https://danmackinlay.name/notebook/renormalization_for_ml.html</guid>
  <pubDate>Mon, 29 Jun 2026 02:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/dishes_stack_RP-P-OB-80.401A.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>The unreasonable effectiveness of empirical risk minimization</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/bitter_lesson_erm.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-world-sticks-detail" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-world-sticks-detail-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/world_sticks_detail_RP-P-2016-1059.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/world_sticks_detail_RP-P-2016-1059.avif" id="fig-world-sticks-detail" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-world-sticks-detail-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Very WIP 🚧TODO🚧</p>
</div>
</div>
</div>
<p>Loss functions! The engine of modern machine learning.</p>
<p>Recall that modern machine learning is built around loss functions, and they are in practice scalar valuations of the <em>badness</em> of a model’s output. Formally, in standard machine learning we usually assume data points <img src="https://latex.codecogs.com/png.latex?(x,%20y)"> are drawn i.i.d. from some fixed but unknown distribution <img src="https://latex.codecogs.com/png.latex?P"> over input-label pairs, where the covariates or predictors <img src="https://latex.codecogs.com/png.latex?x"> are real vectors in <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5Ed"> for some fixed, known <img src="https://latex.codecogs.com/png.latex?d">.</p>
<p>A loss function <img src="https://latex.codecogs.com/png.latex?%5Cell"> is then a map from predictions and observed targets/labels to some real number that tells us “how bad” the prediction was:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cell:%20%5Cmathcal%7BY%7D%20%5Ctimes%20%5Cmathcal%7BY%7D%20%5Cto%20%5Cmathbb%7BR%7D"></p>
<p>Given a sample <img src="https://latex.codecogs.com/png.latex?(x,%20y)%20%5Csim%20P">, where <img src="https://latex.codecogs.com/png.latex?x%20%5Cin%20%5Cmathbb%7BR%7D%5Ed"> is the input vector, <img src="https://latex.codecogs.com/png.latex?y%20%5Cin%20%5Cmathcal%7BY%7D"> is the true label, and <img src="https://latex.codecogs.com/png.latex?%5Chat%7By%7D%20=%20h_%5Ctheta(x)%20%5Cin%20%5Cmathcal%7BY%7D"> is the model’s prediction under parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta">, the loss at a single example is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cell(y,%20h_%5Ctheta(x))%20%5Cin%20%5Cmathbb%7BR%7D"></p>
<p>The quantity we actually care about is the expected risk under the data-generating distribution:</p>
<p><img src="https://latex.codecogs.com/png.latex?R(%5Ctheta)%20=%20%5Cmathbb%7BE%7D_%7B(x,y)%20%5Csim%20P%7D%5B%5Cell(y,%20h_%5Ctheta(x))%5D"></p>
<p>Since <img src="https://latex.codecogs.com/png.latex?P"> is unknown, we replace it with a finite sample <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D%20=%20%5C%7B(x_i,%20y_i)%5C%7D_%7Bi=1%7D%5En"> and minimise the empirical risk:</p>
<p><img src="https://latex.codecogs.com/png.latex?L(%5Ctheta)%20=%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum_%7Bi=1%7D%5En%20%5Cell(y_i,%20h_%5Ctheta(x_i))"></p>
<p>This is the finite-sample approximation to expected risk minimisation, usually called empirical risk minimisation (ERM). The choice of <img src="https://latex.codecogs.com/png.latex?%5Cell"> encodes our assumptions about the noise model and the cost of errors.</p>
<p>There are a few things to note about these bad boys</p>
<ol type="1">
<li>we are, as a civilisation, <em>really good</em> at choosing <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> to minimise <img src="https://latex.codecogs.com/png.latex?L(%5Ctheta)">, even in high dimensions, even for horrible <img src="https://latex.codecogs.com/png.latex?P">, even for very weird data, thanks to gradient-based optimisation and stochastic approximations.</li>
<li>We generally choose <img src="https://latex.codecogs.com/png.latex?%5Cell"> for computational convenience, not because it is a perfect reflection of the true cost of errors in the real world, which is part of why we are so good at #1</li>
<li>Notice we assumed that the data distribution <img src="https://latex.codecogs.com/png.latex?P"> is fixed and static. This is a <em>strong</em> assumption. Sometimes we can make it nearly true by choosing static pieces of the world to model, or conditioning in some clever way, or <a href="../notebook/legibility_automation.html">changing the world to make it true</a> but in general it is not very true and we jus’ frontin’.</li>
<li>If you can approximate your real problem with an optimisation like this, you generally win, in the sense of shipping fancy products fast.</li>
</ol>
<p>This notebook is about that pressure to remake the world into parts that can be well-described by static distributions with scalar loss functions, and to wonder what that does to us.</p>
<section id="let-us-release-some-woo-pressure" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="let-us-release-some-woo-pressure"><span class="header-section-number">1</span> Let us release some woo pressure</h2>
<p>Point of order: I am not here to pitch you on the more mystical arguments against ever conflating a human being with a number. Sometimes quantification and empirical risk minimisation works. Quantitative methods are real methods. Measuring the population is a great way to work out if we have enough food for the population. Predicting the weather is a great way to work out if I should take an umbrella.</p>
<p>That this is unreasonably effective, and brings great benefit is, in a sense, the “problem” I want to address here. So many things can be made to go via ERM, so many things now depend upon it, that I wonder if this is in itself selection pressure against the things which cannot be made to go better by the application of a loss function and an NN.</p>
</section>
<section id="relaxations-of-classic-erm" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="relaxations-of-classic-erm"><span class="header-section-number">2</span> Relaxations of classic ERM</h2>
<p>Various of the ERM assumptions have been relaxed in diverse ways across modern AI infrastructure. This is the local variant of Sutton’s famous bitter lesson about “methods that leverage computation” being the most effective; the <em>way we leverage that computation</em> is to <em>make things more like ERM</em>.</p>
<ol type="1">
<li>We don’t always take the distribution <img src="https://latex.codecogs.com/png.latex?P"> as fixed. For example, <a href="../notebook/online_learning.html">online methods</a> learn something like a “recently good” model, and are happy to forget older things.</li>
<li>We let the model range over more interesting spaces than a fixed-size input-output distribution. For example LLMs do this, predicting whole conversations. This is because they ingeniously decompose the sentence prediction problem into smaller, manageable components, allowing them to handle complex, structured data.</li>
<li>We let the models be more than passive predictors. For example, they can learn to <em>choose actions</em>, in, e.g.&nbsp;<a href="../notebook/reinforcement_learning.html">reinforcement learning</a> or <a href="../notebook/design_of_experiments_adaptive.html">Bayesian optimisation</a>.</li>
<li>Online/continual learning totally works, although no-one has made it efficient for LLMs yet, I have seen online fine-tuning pop up in, e.g.&nbsp;<a href="../notebook/ai_reasoning.html">automated reasoning</a> so this is still live. Anyway, this lets <img src="https://latex.codecogs.com/png.latex?P"> change over time, and lets the model adapt to that.</li>
</ol>
<p>These can get ERM to do surprising things; still within, I argue, basically the same formalism, with all the unearned tractability that gives us.</p>
</section>
<section id="that-which-is-susceptible-to-erm-is-remarkably-general" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="that-which-is-susceptible-to-erm-is-remarkably-general"><span class="header-section-number">3</span> That which is susceptible to ERM is remarkably general</h2>
<p>RL is super interesting. Sutton famously argued very hard that minimising scalar losses was fine for producing general agents <span class="citation" data-cites="Silver2021Reward">Silver et al. (2021)</span>.<sup>1</sup> This was controversial; there have been various responses, for example, demanding that the reward be at least vector-valued <span class="citation" data-cites="Vamplew2022Scalar">(Vamplew et al. 2022)</span>. The formal version of that exchange — what vector reward does and does not repair — is in inference in an open world.</p>
<p>Anyway, it certainly produces agents who can use ERM-like training to learn “how to act” which seems very magical, and nearly open-ended.</p>
<p>RL’s greatest successes, however, have been in domains where we already have a great simulator, run it for a long time, and where we can get a lot of data. RL agents commonly require subjective millenia of experience of playing computer games to get good at them, so they are not yet practical for many real-world tasks. Which is to say, we need to work damned hard to get them to look like ERM, and then they work very well.</p>
<p>The most gob-smackingly, boundary-pushingly astonishing place that ERM works, of course, is famously <a href="../notebook/nn_transformers.html">Large Language Models</a> whereminimising the error on next word prediction might yet learn to supplant all human intellectual labour. I am still astonished that we live in the transformers-work world.</p>
<p>Once again, this is because we have made the world look like ERM: we have a static distribution of text, and we have a scalar loss function (cross-entropy) that is tractable to optimise. That this generalises is astonishing.</p>
<p>LLMs explore an interesting space: they seem to be able to describe an <a href="https://kk.org/thetechnium/latent-space-as-a-new-medium/?ref=danmackinlay.name">effectively-unlimited world of imagination</a>. Their training technology still looks a hell of a lot like ERM.</p>
<p>How do they do this wizardry?</p>
</section>
<section id="against-utility" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="against-utility"><span class="header-section-number">4</span> Against utility</h2>
<p>As I am prone to rant to strangers at the bus-stop, a cognitive danger of <a href="../notebook/utility_if_we_must.html">machine learning</a> is reification of utility functions. Utility functions are an interesting method of analysis, but clearly maladapted for some purposes.</p>
<p>For one thing, if we look at their usage in economics, utilities are induced valuations over allocations of goods.</p>
<p>It is one thing to note that humans don’t think this way. It is another to note that also the world does not even operate this way.</p>
<p>My entire life does not cash out in my acquiring a Pareto-optimal allocation of goods subject to the initial endowment. Rather, my life is some complicated exploration of ways of being, learning, acquiring tastes and losing them, changing and burning out, falling in love, fighting my nemesis, building friend groups and families and institutions and ultimately dying — and what the fuck did <em>that</em> just optimise? From what distribution <img src="https://latex.codecogs.com/png.latex?P"> was my life drawn? Could it exist even notionally? Was it the same <img src="https://latex.codecogs.com/png.latex?P"> as the generation before? The one that comes next?</p>
<p>Human beings strive for, not bundles of goods, but complicated, contingent, path-dependent, interacting situations that they co-create with the world itself. If trading apples for oranges helps along the way, all the better, but this seems only ever an incidental goal.</p>
<p>Elsewhere this rejection of an underlying utility function has been described as a <a href="../notebook/human_values.html#thick-models-of-value">thick model of value</a>, although I won’t do so here, because it is not <em>just</em> the <em>thickness</em> of this concept that I want to consider, but the open-endedness, the contingency.</p>
</section>
<section id="empowerment-must-concern-outcomes-outside-the-data-distribution" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="empowerment-must-concern-outcomes-outside-the-data-distribution"><span class="header-section-number">5</span> Empowerment must concern outcomes outside the data distribution</h2>
<p>The minimizers have only interpreted <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D">-world in various ways; the point, however, is to change <img src="https://latex.codecogs.com/png.latex?P">. Some of this is addressed in <a href="../notebook/open_ended_intelligence.html">open-ended intelligence</a>.</p>
</section>
<section id="edge-cases" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="edge-cases"><span class="header-section-number">6</span> Edge cases</h2>
<p>The formal side of this complaint — what a bounded observer can estimate about a world its sample space does not contain — is now taxonomized in inference in an open world; this list stays as a trailhead.</p>
<ul>
<li><a href="../notebook/bayes_misspecified.html">mis-specified Bayes</a> — There are totally theorems for open world Bayesian inference, and some of them are computable, at least in restricted domains. What can we learn from there?</li>
<li>The closely-related <a href="../notebook/bayes_imprecise.html">infrabayesianism</a> and other imprecise Bayes methods provide a way of reasoning about Knightian uncertainty.</li>
<li>(Generative) Variational Search <span class="citation" data-cites="Steinberg2024Variational">(Steinberg et al. 2024)</span> seems to me to gesture towards somewhere interesting; by going back to <em>search</em> rather than <em>inference</em> (or rather, inferring “where to look next”) we get something like a “Bayesian” version of <a href="../notebook/genetic_programming.html">evolutionary search</a> that can be applied to open-ended problems.</li>
<li><a href="../notebook/artificial_life.html">Artificial life</a> is a whole field of study that is about open-endedness, and has been for decades.</li>
<li><a href="../notebook/renormalization_for_ml.html">Renormalization group</a> seems to be a live agenda for <a href="../notebook/coarse_graining.html">coarse-graining</a> in ML, and suggests some useful approaches to the the problem of unobservable detail.</li>
<li>Reactivity: what if the world responds to us? There are some attempts to model this, e.g.&nbsp;<a href="../notebook/performative_prediction.html">performative prediction</a>, or even full-blown <a href="../notebook/learning_with_theory_of_mind.html">theory of mind</a>.</li>
</ul>
</section>
<section id="incoming" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="incoming"><span class="header-section-number">7</span> Incoming</h2>
<p>This should connect to</p>
<ul>
<li><a href="../notebook/performative_prediction.html">performative prediction</a></li>
<li><a href="../notebook/legibility_automation.html">legibility and automation</a></li>
<li><a href="../notebook/ai_disempowerment.html">disempowerment</a></li>
<li><a href="../notebook/open_ended_intelligence.html">open-endedness in intelligence</a></li>
<li><a href="../notebook/goodharts_law.html">Goodhart’s law</a></li>
<li>I think maybe <a href="../notebook/human_domestication.html">domestication</a> also.</li>
</ul>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Bergadano1991Problem" class="csl-entry">
Bergadano. 1991. <span>“<a href="https://dl.acm.org/doi/abs/10.5555/1631552.1631621">The Problem of Induction and Machine Learning</a>.”</span> In <em>Proceedings of the 12th International Joint Conference on Artificial Intelligence-Volume 2</em>.
</div>
<div id="ref-Boyd2023Structuring" class="csl-entry">
<span class="nocase">boyd</span>. 2023. <span>“<a href="https://doi.org/10.1162/daed_a_01983">The Structuring Work of Algorithms</a>.”</span> <em>Daedalus</em>.
</div>
<div id="ref-Egan2026Political" class="csl-entry">
Egan. 2026. <span>“<a href="https://doi.org/10.1177/03098168251326189">Towards a Political Economy of Algorithmic Capitalism</a>.”</span> <em>Capital &amp; Class</em>.
</div>
<div id="ref-Farrell2023Moral" class="csl-entry">
Farrell, and Fourcade. 2023. <span>“<a href="https://doi.org/10.1162/daed_a_01982">The Moral Economy of High-Tech Modernism</a>.”</span> <em>Daedalus</em>.
</div>
<div id="ref-Habaraduwa2024Inductive" class="csl-entry">
Habaraduwa. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2401.09011">Inductive Models for Artificial Intelligence Systems Are Insufficient Without Good Explanations</a>.”</span>
</div>
<div id="ref-Hardt2026Emerging" class="csl-entry">
Hardt. 2026. <em><a href="https://mlbenchmarks.org">The Emerging Science of Machine Learning Benchmarks</a></em>.
</div>
<div id="ref-Hesselberth2018Legibility" class="csl-entry">
Hesselberth, Houwen, Peeren, et al. 2018. <em><a href="https://doi.org/10.1163/9789004376175">Legibility in the Age of Signs and Machines</a></em>.
</div>
<div id="ref-Kasy2026Political" class="csl-entry">
Kasy. 2026. <span>“<a href="https://doi.org/10.1093/9780191996030.003.0006">The Political Economy of AI: Toward Democratic Control of the Means of Prediction</a>.”</span> In <em>The Oxford Handbook of Algorithmic Governance and the Law</em>.
</div>
<div id="ref-Laan2026Researchers" class="csl-entry">
Laan. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2602.21501">A Researcher’s Guide to Empirical Risk Minimization</a>.”</span>
</div>
<div id="ref-Mason2026Scalars" class="csl-entry">
Mason. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2604.09602">From Scalars to Tensors: Declared Losses Recover Epistemic Distinctions That Neutrosophic Scalars Cannot Express</a>.”</span>
</div>
<div id="ref-NortonMaterial" class="csl-entry">
Norton. 2021. <span>“The Material Theory of Induction.”</span>
</div>
<div id="ref-Prado2023Automated" class="csl-entry">
Prado. 2023. <span>“Automated Patterns of Culture: Philosophy and Machine Learning.”</span>
</div>
<div id="ref-Raghavan2021Societal" class="csl-entry">
Raghavan. 2021. <span>“<a href="https://doi.org/10.7298/eewn-0e35">The Societal Impacts of Algorithmic Decision-Making</a>.”</span>
</div>
<div id="ref-Shah2022Goal" class="csl-entry">
Shah, Varma, Kumar, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2210.01790">Goal Misgeneralization: Why Correct Specifications Aren’t Enough For Correct Goals</a>.”</span>
</div>
<div id="ref-Silver2021Reward" class="csl-entry">
Silver, Singh, Precup, et al. 2021. <span>“<a href="https://doi.org/10.1016/j.artint.2021.103535">Reward Is Enough</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Steinberg2024Variational" class="csl-entry">
Steinberg, Oliveira, Ong, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2409.06142">Variational Search Distributions</a>.”</span>
</div>
<div id="ref-Steinberg2025Amortized" class="csl-entry">
Steinberg, Wijesinghe, Oliveira, et al. 2025. <span>“<a href="https://openreview.net/forum?id=gSpYenanRZ">Amortized Active Generation of Pareto Sets</a>.”</span> In.
</div>
<div id="ref-Sudhakar2026Biological" class="csl-entry">
Sudhakar. 2026. <span>“Biological Neural Networks as Substrate for Philosophical Constructs: A Unified Framework.”</span>
</div>
<div id="ref-Taylor2020Rise" class="csl-entry">
Taylor, and Dorin. 2020. <em>Rise of the Self-Replicators: Early Visions of Machines, AI and Robots That Can Reproduce and Evolve</em>.
</div>
<div id="ref-Trivedi2026Solipsistic" class="csl-entry">
Trivedi, Jaques, Cross, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2606.03237">Solipsistic Superintelligence Is Unlikely to Be Cooperative</a>.”</span>
</div>
<div id="ref-Valizada2026Artificial" class="csl-entry">
Valizada. 2026. <span>“<a href="https://doi.org/10.69760/aghel.026001001">Artificial Intelligence and the Design of Politics in the Modern World</a>.”</span> <em>Acta Globalis Humanitatis Et Linguarum</em>.
</div>
<div id="ref-Vamplew2022Scalar" class="csl-entry">
Vamplew, Smith, Källström, et al. 2022. <span>“<a href="https://doi.org/10.1007/s10458-022-09575-5">Scalar Reward Is Not Enough: A Response to Silver, Singh, Precup and Sutton (2021)</a>.”</span> <em>Autonomous Agents and Multi-Agent Systems</em>.
</div>
<div id="ref-Venkatasubramanian2021Fairness" class="csl-entry">
Venkatasubramanian, Scheidegger, Friedler, et al. 2021. <span>“<a href="https://doi.org/10.1145/3447548.3470821">Fairness in Networks: Social Capital, Information Access, and Interventions</a>.”</span> In <em>Proceedings of the 27th ACM SIGKDD Conference on Knowledge Discovery &amp; Data Mining</em>. KDD ’21.
</div>
<div id="ref-ZhiXuan2025Preferences" class="csl-entry">
Zhi-Xuan, Carroll, Franklin, et al. 2025. <span>“<a href="https://doi.org/10.1007/s11098-024-02249-w">Beyond Preferences in AI Alignment</a>.”</span> <em>Philosophical Studies</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>FWIW, I cannot even work out what the thesis of <span class="citation" data-cites="Silver2021Reward">Silver et al. (2021)</span> is. It seems too under-specified to even agree with.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>bounded compute</category>
  <category>classification</category>
  <category>collective knowledge</category>
  <category>culture</category>
  <category>ethics</category>
  <category>incentive mechanisms</category>
  <category>machine learning</category>
  <category>optimization</category>
  <category>performative prediction problems</category>
  <category>sociology</category>
  <category>statmech</category>
  <category>utility</category>
  <category>when to compute</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/bitter_lesson_erm.html</guid>
  <pubDate>Wed, 24 Jun 2026 04:39:22 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/world_sticks_detail_RP-P-2016-1059.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Algorithmic collective action</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/algorithmic_collective_action.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-mars-venus-feast" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-mars-venus-feast-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/mars_venus_feast_RP-P-OB-46.795.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/mars_venus_feast_RP-P-OB-46.795.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-mars-venus-feast-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p><a href="../notebook/coalition_games.html">Coalition game</a> and <a href="../notebook/collective_action.html">collective action</a> problems, played between people and algorithms.</p>
<section id="collective-action-against-a-model" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="collective-action-against-a-model"><span class="header-section-number">1</span> Collective action against a model</h2>
<p>A collective of users pools their data and coordinates how each member modifies it, to push a platform’s deployed learning algorithm where the collective wants it <span class="citation" data-cites="Hardt2024Algorithmic">(Hardt et al. 2024)</span>. Even a tiny fractional collective can exert outsized control over what the model learns, because the firm keeps retraining on data the collective has shaped.</p>
<p>The reciprocal quantity is <em>performative power</em> <span class="citation" data-cites="Hardt2022Performative">(Hardt, Jagadeesan, and Mendler-Dünner 2022)</span>: how far the platform can steer the population back, rather than settling for the best fit to whatever data it currently sees. A monopolist has the most of it; competition and outside options erode it. The collective’s leverage and the platform’s performative power jointly determine who steers whom. For more on this see <a href="../notebook/performative_prediction.html">strategic classification and performative prediction</a>.</p>
</section>
<section id="coalitional-marl" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="coalitional-marl"><span class="header-section-number">2</span> Coalitional MARL</h2>
<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Much of this is slop rn; publishing because it is useful, but expect a lot of edits as I clean it up.</p>
</div>
</div>
</div>
<p>A <a href="../notebook/coalition_games.html">coalition game</a> asks who teams up with whom and how the joint surplus is split. If we could teach algorithms to do collective formation with one another, that would be interesting, don’t you think?</p>
<p>We might recall that the classical answer to “what is the worth of this coalition?” comes from Shapley fairness, which cashes out optimal coalitions in the <em>characteristic function</em> <img src="https://latex.codecogs.com/png.latex?v(S)"> — the worth of each coalition <img src="https://latex.codecogs.com/png.latex?S">, evaluated in isolation. For more than ~20 agents that’s hopeless: evaluating optimal coalitions involves <img src="https://latex.codecogs.com/png.latex?O(2%5En)"> subproblems, each of which might be hard in itself (e.g.&nbsp;each <img src="https://latex.codecogs.com/png.latex?v(S)"> is its own counterfactual estimation problem). Scaling to a thousand agents would mean evaluating <img src="https://latex.codecogs.com/png.latex?10%5E%7B301%7D"> such problems. Can we learn approximations to optimal coalitions using the RL formalism?</p>
<section id="decentralised-partner-selection-and-gain-sharing" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="decentralised-partner-selection-and-gain-sharing"><span class="header-section-number">2.1</span> Decentralised partner selection and gain-sharing</h3>
<p>Multi-Agent Gain Sharing (MAGS) pushes the feasible agent count up by learning an approximation to the characteristic function. It trains (transformer-based) agents to negotiate fair gain shares directly in mixed-motive logistics environments, notionally scaling to &gt;1000 concurrent agents <span class="citation" data-cites="Mak2023Fair">(Mak et al. 2023)</span>.</p>
<p><span class="citation" data-cites="Chalkiadakis2003Coordination">Chalkiadakis and Boutilier (2003)</span> treats unknown opponent capabilities as a POMDP and has agents do Bayesian belief updates over opponent types during coalition formation, which lets them anticipate (and sometimes exploit) irrational or static partners.</p>
<p>Both approaches experience a failure mode called <em>transductivity</em>: agents learn to coordinate with the opponents they trained against, not with arbitrary new ones (which would be <em>inductivity</em>).</p>
</section>
<section id="credit-assignment-via-the-nucleolus" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="credit-assignment-via-the-nucleolus"><span class="header-section-number">2.2</span> Credit assignment via the nucleolus</h3>
<p>A naïve way of doing MARL is to force a <em>grand coalition</em> — all agents share one global reward — and rely on credit-assignment heuristics like COMA <span class="citation" data-cites="Foerster2018Counterfactual">(Foerster et al. 2018)</span> to back out individual contributions. In asymmetric environments (e.g.&nbsp;few cooperators vs.&nbsp;many adversaries in SMAC) the grand coalition might be suboptimal. <span class="citation" data-cites="Li2025Nucleolus">Li et al. (2025)</span> import the concept of the <em>nucleolus</em> from cooperative game theory into Q-learning and approximate that. For an allocation <img src="https://latex.codecogs.com/png.latex?x">, a coalition <img src="https://latex.codecogs.com/png.latex?S">’s <em>excess</em> is <img src="https://latex.codecogs.com/png.latex?e(S,%20x)%20=%20v(S)%20-%20%5Csum_%7Bi%20%5Cin%20S%7D%20x_i"> — the gap between what <img src="https://latex.codecogs.com/png.latex?S"> could earn alone and what its members get under <img src="https://latex.codecogs.com/png.latex?x">. Positive excess means there are grounds to defect. The nucleolus is the allocation that makes the most-aggrieved coalition as un-aggrieved as possible, then the next, and so on. A nucleolus-Q operator with convergence guarantees lets agents autonomously fracture the grand coalition into smaller, task-specific sub-teams, each stable in the sense that no member wants to defect.</p>
</section>
<section id="model-based-marl-for-stable-partitions" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="model-based-marl-for-stable-partitions"><span class="header-section-number">2.3</span> Model-based MARL for stable partitions</h3>
<p>Traditionally, MARL is model-free. Model-based MARL — where agents learn (or are given) a predictive model of environment dynamics — seems like it might garner us something the model-free version can’t: theoretical stability guarantees.</p>
<p>This does not seem to be commonly used, but there is a specific example in radar-network multitarget tracking <span class="citation" data-cites="Xiong2023Coalition">(Xiong et al. 2023)</span>. Several geographically separated radar stations, each with limited beam-time, jointly track many more moving targets than any one station can cover. Two or three radars pointing at the <em>same</em> target triangulate to a lower-variance estimate than any one alone — (i.e.&nbsp;cooperation has benefits). A coalition is the subset of radars agreeing to illuminate one target; there is an implicit global partition that assigns radars to targets. We are in a “Transferable utility” setting, which means the joint tracking-quality improvement is a scalar reward that may be allocated among coalition members. No central control dictates assignments; each radar runs a model-based RL policy that learns the value of joining one coalition versus another, and the authors prove convergence to a Nash-stable partition at which no single radar wants to defect.</p>
<p>The model-based part seems important. Nash-stability is a statement about <em>counterfactual</em> defections — for every alternative coalition each radar could switch to, its simulated payoff must be no better than staying put. Model-free MARL only has reliable value estimates along trajectories it actually sampled, so the off-policy counterfactuals are guesses, and the stability claim looks much harder. A learned dynamics model lets each radar roll out counterfactual predictions along the lines of “<em>what if</em> I retargeted at <img src="https://latex.codecogs.com/png.latex?k'">”, without physically doing so, and check the deviation locally. The pattern (model-constrained dynamics + transferable-utility coalitions + model-based RL → stability proof) probably generalizes beyond radar.</p>
</section>
<section id="noisy-observation-of-partner-intent" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="noisy-observation-of-partner-intent"><span class="header-section-number">2.4</span> Noisy observation of partner intent</h3>
<p>In a mixed-motive game where every agent is purely selfish, MARL notoriously finds a mutually-defecting equilibrium and stays there. One fix is to hard-code prosocial weights — give each agent a reward of the form <img src="https://latex.codecogs.com/png.latex?r_i%20+%20%5Calpha%20%5Csum_%7Bj%20%5Cneq%20i%7D%20r_j">, with <img src="https://latex.codecogs.com/png.latex?%5Calpha"> tuned so cooperation pays. That works, apparently, but is not satisfying. It bakes the cooperation in rather than letting it be discovered; it tends to be fragile to defectors, doesn’t generalize off-distribution, and gives no principled story for <em>who</em> should cooperate with <em>whom</em>.</p>
<p>The Randomized Uncertain Social Preferences (RUSP) framework <span class="citation" data-cites="Baker2020Emergent">(Baker 2020)</span> solves this with <a href="../notebook/opponent_shaping.html#rusp">noisy observations of partner intent</a>. I’m intrigued by this as an example of <a href="../notebook/cooperation_uncertainty.html">cooperation via uncertainty</a>.</p>
</section>
</section>
<section id="open-source-game-theory" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="open-source-game-theory"><span class="header-section-number">3</span> Open-source game theory</h2>
<p>MARL is one possible operationalization of <a href="../notebook/game_theory_open_source.html">open-source game theory</a>, in which agents exchange policy source code (or some interpretable proxy) before acting and cooperate by mutual verification.</p>
</section>
<section id="references" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="references"><span class="header-section-number">4</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Baker2020Emergent" class="csl-entry">
Baker. 2020. <span>“<a href="https://doi.org/10.48550/ARXIV.2011.05373">Emergent Reciprocity and Team Formation from Randomized Uncertain Social Preferences</a>.”</span> In.
</div>
<div id="ref-Chalkiadakis2007Bayesian" class="csl-entry">
Chalkiadakis. 2007. <span>“<a href="https://www.cs.utoronto.ca/~gehalk/PhDThesis/gehalk_thesis.pdf">A Bayesian Approach to Multiagent Reinforcement Learning and Coalition Formation Under Uncertainty</a>.”</span>
</div>
<div id="ref-Chalkiadakis2003Coordination" class="csl-entry">
Chalkiadakis, and Boutilier. 2003. <span>“<a href="https://doi.org/10.1145/860575.860689">Coordination in Multiagent Reinforcement Learning</a>.”</span> In.
</div>
<div id="ref-Dong2018Strategic" class="csl-entry">
Dong, Roth, Schutzman, et al. 2018. <span>“<a href="https://doi.org/10.1145/3219166.3219193">Strategic Classification from Revealed Preferences</a>.”</span> In <em>Proceedings of the 2018 ACM Conference on Economics and Computation</em>.
</div>
<div id="ref-Foerster2018Counterfactual" class="csl-entry">
Foerster, Farquhar, Afouras, et al. 2018. <span>“<a href="https://doi.org/10.1609/aaai.v32i1.11794">Counterfactual Multi-Agent Policy Gradients</a>.”</span> <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Hardt2022Performative" class="csl-entry">
Hardt, Jagadeesan, and Mendler-Dünner. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2203.17232">Performative Power</a>.”</span>
</div>
<div id="ref-Hardt2024Algorithmic" class="csl-entry">
Hardt, Mazumdar, Mendler-Dünner, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2302.04262">Algorithmic Collective Action in Machine Learning</a>.”</span>
</div>
<div id="ref-Hardt2016Strategic" class="csl-entry">
Hardt, Megiddo, Papadimitriou, et al. 2016. <span>“<a href="https://doi.org/10.1145/2840728.2840730">Strategic Classification</a>.”</span> In <em>Proceedings of the 2016 ACM Conference on Innovations in Theoretical Computer Science</em>.
</div>
<div id="ref-Levanon2021Strategic" class="csl-entry">
Levanon, and Rosenfeld. 2021. <span>“<a href="https://proceedings.mlr.press/v139/levanon21a.html">Strategic Classification Made Practical</a>.”</span> In <em>Proceedings of the 38th International Conference on Machine Learning</em>.
</div>
<div id="ref-Li2025Nucleolus" class="csl-entry">
Li, Cao, Qiao, et al. 2025. <span>“<a href="https://doi.org/10.65109/YYKI4123">Nucleolus Credit Assignment for Effective Coalitions in Multi-Agent Reinforcement Learning</a>.”</span> In.
</div>
<div id="ref-Mak2023Fair" class="csl-entry">
Mak, Xu, Pearce, et al. 2023. <span>“<a href="https://arxiv.org/abs/2310.17485">Fair Collaborative Vehicle Routing: A Deep Multi-Agent Reinforcement Learning Approach</a>.”</span>
</div>
<div id="ref-Miller2019Strategic" class="csl-entry">
Miller, Milli, and Hardt. 2019. <span>“Strategic Classification Is Causal Modeling in Disguise.”</span> In <em>International Conference on Machine Learning</em>.
</div>
<div id="ref-Pant2026CoopetitionGym" class="csl-entry">
Pant, and Yu. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2605.02063">Coopetition-Gym V1: A Formally Grounded Platform for Mixed-Motive Multi-Agent Reinforcement Learning Under Strategic Coopetition</a>.”</span>
</div>
<div id="ref-Sharma2021Survey" class="csl-entry">
Sharma, Fernandez, Zaroukian, et al. 2021. <span>“<a href="https://doi.org/10.1117/12.2585808">Survey of Recent Multi-Agent Reinforcement Learning Algorithms Utilizing Centralized Training</a>.”</span> In <em>Artificial Intelligence and Machine Learning for Multi-Domain Operations Applications III</em>.
</div>
<div id="ref-Weis2026Multiagent" class="csl-entry">
Weis, Wołczyk, Nasser, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2602.16301">Multi-Agent Cooperation Through in-Context Co-Player Inference</a>.”</span>
</div>
<div id="ref-Wolpert2002Designing" class="csl-entry">
Wolpert, and Lawson. 2002. <span>“<a href="https://doi.org/10.1145/545056.545074">Designing Agent Collectives for Systems with Markovian Dynamics</a>.”</span> In.
</div>
<div id="ref-Xiong2023Coalition" class="csl-entry">
Xiong, Zhang, Cui, et al. 2023. <span>“<a href="https://doi.org/10.1109/TAES.2022.3208865">Coalition Game of Radar Network for Multitarget Tracking via Model-Based Multiagent Reinforcement Learning</a>.”</span> <em>IEEE Transactions on Aerospace and Electronic Systems</em>.
</div>
</div>


</section>

 ]]></description>
  <category>agents</category>
  <category>AI safety</category>
  <category>bounded compute</category>
  <category>collective knowledge</category>
  <category>computers are awful together</category>
  <category>distributed</category>
  <category>economics</category>
  <category>edge computing</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>machine learning</category>
  <category>networks</category>
  <guid>https://danmackinlay.name/notebook/algorithmic_collective_action.html</guid>
  <pubDate>Mon, 15 Jun 2026 08:13:02 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/mars_venus_feast_RP-P-OB-46.795.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Performative prediction</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/performative_prediction.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-stock-n-thunder" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-stock-n-thunder-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/stock_n_thunder_RP-P-OB-84.581.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/stock_n_thunder_RP-P-OB-84.581.avif" id="fig-stock-n-thunder" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-stock-n-thunder-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>An ML formalization of interesting phenomena such as <a href="../notebook/legibility_automation.html">legibilizing</a> and <a href="../notebook/myths.html">hyperstition</a>, likely implicit in <a href="../notebook/recommender_dynamics.html">recommender dynamics</a>, adversarial classification and some types of <a href="../notebook/external_validity.html">external validity</a>, among other places. HT <a href="https://x.com/laparticle?lang=en&amp;ref=danmackinlay.name">TJ</a> for the pointer.</p>
<p>In classical supervised learning we fit parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> to minimize expected loss over a distribution <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D"> <img src="https://latex.codecogs.com/png.latex?%5Ctheta_%7B%5Cmathrm%7BSL%7D%7D%20=%20%5Carg%5Cmin_%5Ctheta%20%5Cmathbb%7BE%7D_%7BZ%5Csim%5Cmathcal%7BD%7D%7D%5C,%5Cell(Z;%5Ctheta),"> with <img src="https://latex.codecogs.com/png.latex?Z=(X,Y)"> a feature–outcome pair and <img src="https://latex.codecogs.com/png.latex?%5Cell"> the loss. We assume that the distribution <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D"> is fixed and that the model acts as a passive observer—a measuring instrument. Poetically, this assumes that while the model watches the world, the world does not watch back. <em>Performative prediction</em> <span class="citation" data-cites="Perdomo2020Performative Hardt2025Performative">(Perdomo et al. 2020; Hardt and Mendler-Dünner 2025)</span> is a formalization of a different regime where deploying the model <em>changes</em> the subsequent data distribution.</p>
<p>The stock example (familiar from <a href="../notebook/model_fairness.html">fairness</a>) is assessing people for credit-worthiness. If we predict someone is at high risk of credit default, we might protectively assign them a punishing interest rate, which in turn increases their propensity to default, thereby confirming our prediction.</p>
<p>In performative prediction we model this directly, formally by assuming that each choice of parameters <em>produces</em> a perturbed data distribution <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D(%5Ctheta)">—the data we <em>would</em> see if we deployed <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> to do things in the world. In the case that the perturbation ‘looks like a self-fulfilling prophecy’, we call that a <em>hyperstition</em>.</p>
<p>We define the <em>performative risk</em>, <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BPR%7D(%5Ctheta)%20=%20%5Cmathbb%7BE%7D_%7BZ%5Csim%5Cmathcal%7BD%7D(%5Ctheta)%7D%5C,%5Cell(Z;%5Ctheta)."> Here <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> appears twice: once as the model whose predictions we assess, and again as an influence on the distribution it produces when deployed. That second appearance puts us at odds with the techniques of ordinary regression — we cannot simply descend the gradient of the loss in isolation, because moving <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> also shifts the target.</p>
<p>There are two extensions to the definition that recover something like that classical supervised, static setting.</p>
<p>A <em>performative optimum</em> <img src="https://latex.codecogs.com/png.latex?%5Ctheta_%7B%5Cmathrm%7BPO%7D%7D%20=%20%5Carg%5Cmin_%5Ctheta%20%5Cmathrm%7BPR%7D(%5Ctheta)"> minimizes the performative risk of this coupling via <img src="https://latex.codecogs.com/png.latex?%5Ctheta">, solving for the implicit dependence of the distribution on the parameters. We derive this by applying the chain rule to the performative risk: <img src="https://latex.codecogs.com/png.latex?%5Cnabla_%5Ctheta%20%5Cmathrm%7BPR%7D(%5Ctheta)%20=%20%5Cunderbrace%7B%5Cmathbb%7BE%7D_%7BZ%5Csim%5Cmathcal%7BD%7D(%5Ctheta)%7D%5Cbig%5B%5Cnabla_%5Ctheta%20%5Cell(Z;%5Ctheta)%5Cbig%5D%7D_%7B%5Ctext%7Bfit%20term%7D%7D%20%5C;+%5C;%20%5Cunderbrace%7B%5Cnabla_%5Ctheta%5C,%5Cmathbb%7BE%7D_%7BZ%5Csim%5Cmathcal%7BD%7D(%5Ctheta)%7D%5Cbig%5B%5Cell(Z;%5Ctheta')%5Cbig%5D%5CBig%7C_%7B%5Ctheta'=%5Ctheta%7D%7D_%7B%5Ctext%7Breshaping%20term%7D%7D."> The fit term is the gradient we would calculate if the distribution were static. The <em>reshaping</em> term is an innovation of performative prediction: it measures how perturbing <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> deforms the distribution <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D(%5Ctheta)"> itself, and how much that deformation costs us in expected loss. This “optimum” is where the two cancel — where the marginal gain from fitting the data better is exactly offset by the marginal cost it produces.</p>
<p>A <em>performatively stable</em> point <img src="https://latex.codecogs.com/png.latex?%5Ctheta_%7B%5Cmathrm%7BPS%7D%7D"> is a different solution. This satisfies a fixed-point condition, <img src="https://latex.codecogs.com/png.latex?%5Ctheta_%7B%5Cmathrm%7BPS%7D%7D%20=%20%5Carg%5Cmin_%5Ctheta%20%5Cmathbb%7BE%7D_%7BZ%5Csim%5Cmathcal%7BD%7D(%5Ctheta_%7B%5Cmathrm%7BPS%7D%7D)%7D%5C,%5Cell(Z;%5Ctheta),"> i.e.&nbsp;we are satisfied with a <img src="https://latex.codecogs.com/png.latex?%5Ctheta_%7B%5Cmathrm%7BPS%7D%7D"> if, given an induced world, the model is already optimal for it, so refitting recovers the same parameters. This is similar to the result we get if we train the predictor, then use it in the wild, then update the predictor based on the new state of the world, and so on, until convergence. Practitioners already talk about this as <em>retraining</em>: refit on whatever distribution the last model produced, <img src="https://latex.codecogs.com/png.latex?%5Ctheta_%7Bt+1%7D%20=%20%5Carg%5Cmin_%5Ctheta%20%5Cmathbb%7BE%7D_%7BZ%5Csim%5Cmathcal%7BD%7D(%5Ctheta_t)%7D%5C,%5Cell(Z;%5Ctheta)."></p>
<p>The reason we are here is that these notions do not coincide; the stable model is not, in general, the <em>best</em> one.</p>
<p>Perdomo and co-authors call this <em>repeated risk minimization</em> and derive a contraction guarantee: if the loss is smooth and strongly convex, and the map <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D(%5Ccdot)"> is <em>sufficiently Lipschitz-smooth</em> in Wasserstein distance, then the iteration converges to a stable point at a linear rate, with the error shrinking by a constant factor each step. Put another way, “does the hyperstition converge or explode?” turns upon the question of how sensitive the world is to the model, and sometimes the answer is yes.</p>
<section id="strategic-classification" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="strategic-classification"><span class="header-section-number">1</span> Strategic classification</h2>
<p>So far I have been generic and just talked about regression problems, remaining agnostic about <em>why</em> the distribution moves, and describing <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D(%5Ctheta)"> as just some distribution whose shape determines the reshaping term.</p>
<p><em>Strategic classification</em> <span class="citation" data-cites="Hardt2016Strategic">(Hardt et al. 2016)</span> is a specific generative interpretation: the distribution moves because the people being classified are gaming a classifier.</p>
<p>The setup is a <a href="https://en.wikipedia.org/wiki/Stackelberg_competition?ref=danmackinlay.name">Stackelberg game</a>. The institution moves first, publishing a classifier <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> (credit worthiness, food stamp worthiness, tax credits, etc.) where the value of the classifier is the score it assigns to the features <img src="https://latex.codecogs.com/png.latex?x"> of an applicant <img src="https://latex.codecogs.com/png.latex?s_%5Ctheta(x)">. Each agent with features <img src="https://latex.codecogs.com/png.latex?x">, seeing the rule, attempts to land on the favourable side of the class boundary by adjusting the features they present to the algorithm. Each agent pays some cost <img src="https://latex.codecogs.com/png.latex?c(x,%20x')"> for presenting features <img src="https://latex.codecogs.com/png.latex?x'"> in place of their “true” (default?) <img src="https://latex.codecogs.com/png.latex?x">. We could imagine this corresponds to the cost of buying a new suit when applying for that loan, and so on. Now each agent best-responds, trading the score improvement <img src="https://latex.codecogs.com/png.latex?s_%5Ctheta(x')%20-%20s_%5Ctheta(x)"> against manipulation costs: <img src="https://latex.codecogs.com/png.latex?%5CDelta_%5Ctheta(x)%20=%20%5Carg%5Cmax_%7Bx'%7D%5Cbig%5B%5C,%20s_%5Ctheta(x')%20-%20c(x,%20x')%20%5C,%5Cbig%5D."> The induced distribution <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D(%5Ctheta)"> is then the base distribution pushed forward through this best-response map. Boom! Economic microfoundations of performativity. We can, in principle, deduce how perturbing <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> deforms the new observations, because we have written down the optimization each agent aims to solve.</p>
<p>In the basic version, the features move but the “true” labels do not. I dress up in a fancy suit <img src="https://latex.codecogs.com/png.latex?x'"> to clear the credit-worthy bar without becoming any more “fundamentally creditworthy” underneath, by, e.g., saving more money. This is pure adversarial <a href="../notebook/goodharts_law.html">Goodhart</a> where the metric is prized loose from the thing it was meant to measure.</p>
<p>There exist richer variants with more complicated dynamics. If perturbing some feature actually moves the outcome — studying for the exam rather than buying the answers — then the manipulation might be actual <em>improvement</em> rather than <em>gaming</em>, and we might in fact aim to design the game <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> so that the cheap perturbations of <img src="https://latex.codecogs.com/png.latex?x"> are the improving ones. Whether a given classifier rewards gaming or improvement is one of the things this formalism is designed to resolve.</p>
<p><span class="citation" data-cites="Miller2019Strategic">Miller, Milli, and Hardt (2019)</span> observes that drawing this distinction at all drags us out of pure prediction and into <a href="../notebook/causal_inference.html">causal inference</a>. Whether nudging a feature games the classifier or improves the outcome depends on whether that feature <em>causes</em> <img src="https://latex.codecogs.com/png.latex?Y"> or is merely correlated with it — so an institution that wants to reward improvement cannot blithely use the correlations (between, e.g., academic achievement and test score) but needs to learn the causal graph relating features to the outcome. That is to say, a classifier that incentivizes improvement is a designed <em>intervention</em> on the population. Interpreting causal effects of incentives in games is something <a href="../notebook/causality_multi_agent.html">we fortunately have tools for</a>. That is in general harder than merely fitting <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> — it inherits every identifiability headache of <a href="../notebook/causal_inference.html">causal inference</a>. And yet, we need to do it: Naïve retraining can happily converge to a classifier that everyone games and no one improves under, e.g., where people who want loans max out their credit cards buying fancy suits.</p>
<p>Mapping this strategic play back onto the performative prediction formalism: The <em>reshaping term</em> of the performative gradient in this setting is the derivative of the agents’ best response <img src="https://latex.codecogs.com/png.latex?%5CDelta_%5Ctheta">, measuring how much harder everyone games as we move the classification rule. A <em>performatively stable</em> classifier is one that stays optimal against the gaming perturbations it provokes: we have already priced in how people will respond, so refitting buys us nothing. The catch is that <img src="https://latex.codecogs.com/png.latex?%5CDelta_%5Ctheta"> includes an <img src="https://latex.codecogs.com/png.latex?%5Carg%5Cmax">, so that reshaping term need not be at all well-behaved or, for example, smooth. <span class="citation" data-cites="Levanon2021Strategic">Levanon and Rosenfeld (2021)</span> get the chain rule to run in practice by swapping the hard best-response for a smooth surrogate, to make the objective differentiable end-to-end. And of course, that is even assuming we know the cost function. If we only see the gamed features that come back, we are in the revealed-preferences regime, learning the game while we are playing it, which sounds punishingly hard <span class="citation" data-cites="Dong2018Strategic">(Dong et al. 2018)</span>.</p>
<p>There are some interesting extensions. When the classified agents do not act in isolation but rather <em>coordinate</em> to change the classifier, we are doing <a href="../notebook/algorithmic_collective_action.html">algorithmic collective action</a> <span class="citation" data-cites="Hardt2024Algorithmic">(Hardt et al. 2024)</span>. Conversely, we can consider <em>performative power</em> <span class="citation" data-cites="Hardt2022Performative">(Hardt, Jagadeesan, and Mendler-Dünner 2022)</span>, the ability of the classifying institution’s makers to shape the population’s behaviour. When we want to quantify whether a classifying institution can instead keep the players from coordinating, that is the <a href="../notebook/coalition_games.html">divide-and-rule coalition game</a>.</p>
</section>
<section id="seeing-like-a-model" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="seeing-like-a-model"><span class="header-section-number">2</span> Seeing like a model</h2>
<p>Step back far enough and the whole performative setup looks familiar from the classic observation that a measuring instrument is also an instrument of government as made famous by James C. Scott in <em>Seeing Like a State</em>, who cares greatly about <a href="../notebook/legibility_automation.html">legibility and automation</a> not to mention the tense relationship between the irreproducible tacit <a href="../notebook/metis_modernity.html"><em>metis</em> and high modernity</a>: to run a forest, a city, or a workforce at scale, the state first has to make it <em>legible</em>, which looks a hell of a lot like <em>classifiable</em>. We can improve classification accuracy in two ways: either by learning a more precise model, or by flattening the teeming specificities into more easily separable groups for the central office. Indeed, a governed population does not persist unchanged beneath the gaze of the classifiers of the state, and we might expect both performative power and strategic classification to be in play.</p>
<div id="fig-apartheid-museum" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-apartheid-museum-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/apartheid_museum.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: Francois de Halleux at the Apartheid Museum."><img src="https://danmackinlay.name/images/apartheid_museum.jpg" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-apartheid-museum-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: <a href="https://www.flickr.com/photos/fanz/97926083?ref=danmackinlay.name">Francois de Halleux</a> at the Apartheid Museum.
</figcaption>
</figure>
</div>
<p>Scott’s photogenic example is a measurement procedure that structures the forest it was intended to analyze. The <em>Normalbaum</em> is a standardized tree of a given size class, its volume of saleable wood tabulated by size and age; the Norway spruce in straight rows came afterwards, planted to match the table, bending reality to the convenience of the predictor. The forest is not a strategic responder, however. Where the classified can act, as the credit applicant or the exam-sitter can, gaming the classifier is strategic action (Goodhart again, or <a href="../notebook/bullshit.html#tufta">tufta</a>). Where they cannot, as a forest cannot, the reshaping term is still there and yet nobody is best-responding to us; that is a garden-variety unstrategic bad performative equilibrium.</p>
<p>We are accustomed to the idea, in modern technocracies, that once resources, status and power flow through a category its definition becomes a contested thing. Gender, obscenity, creditworthiness, refugee status, tax bracket: these are essential categories for modern states and thus contested. What changes in performative prediction is not that we “solve” for the “true” categories, nor that we throw up our hands in despair and decry that this is not what we were told in statistics class. Performative prediction gives us a means, rather, to understand and name how much we are seeking to understand reality, and how much to change it.</p>
</section>
<section id="incoming" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="incoming"><span class="header-section-number">3</span> Incoming</h2>
<ul>
<li><a href="https://www.lesswrong.com/posts/p2HQKoW39Ew6updtq/expectation-intention-setpoint?ref=danmackinlay.name">Expectation = intention = setpoint</a></li>
<li>🚧TODO🚧: very excited about <span class="citation" data-cites="Heitzig2026Unbiased">Heitzig (2026)</span> which has an application of this idea to performative forecasting</li>
</ul>
</section>
<section id="references" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="references"><span class="header-section-number">4</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Bengio2026Safety" class="csl-entry">
Bengio, Richardson, Gavenčiak, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2606.29657">Safety from Honesty in a Disinterested AI Predictor</a>.”</span>
</div>
<div id="ref-Dong2018Strategic" class="csl-entry">
Dong, Roth, Schutzman, et al. 2018. <span>“<a href="https://doi.org/10.1145/3219166.3219193">Strategic Classification from Revealed Preferences</a>.”</span> In <em>Proceedings of the 2018 ACM Conference on Economics and Computation</em>.
</div>
<div id="ref-Hardt2022Performative" class="csl-entry">
Hardt, Jagadeesan, and Mendler-Dünner. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2203.17232">Performative Power</a>.”</span>
</div>
<div id="ref-Hardt2024Algorithmic" class="csl-entry">
Hardt, Mazumdar, Mendler-Dünner, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2302.04262">Algorithmic Collective Action in Machine Learning</a>.”</span>
</div>
<div id="ref-Hardt2016Strategic" class="csl-entry">
Hardt, Megiddo, Papadimitriou, et al. 2016. <span>“<a href="https://doi.org/10.1145/2840728.2840730">Strategic Classification</a>.”</span> In <em>Proceedings of the 2016 ACM Conference on Innovations in Theoretical Computer Science</em>.
</div>
<div id="ref-Hardt2025Performative" class="csl-entry">
Hardt, and Mendler-Dünner. 2025. <span>“<a href="https://doi.org/10.1214/25-STS986">Performative Prediction: Past and Future</a>.”</span> <em>Statistical Science</em>.
</div>
<div id="ref-Heitzig2026Unbiased" class="csl-entry">
Heitzig. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2606.26418">Unbiased Canonical Set-Valued Oracles Via Lattice Theory</a>.”</span>
</div>
<div id="ref-Jacobs1992Death" class="csl-entry">
Jacobs. 1992. <em>The Death and Life of Great American Cities</em>.
</div>
<div id="ref-Levanon2021Strategic" class="csl-entry">
Levanon, and Rosenfeld. 2021. <span>“<a href="https://proceedings.mlr.press/v139/levanon21a.html">Strategic Classification Made Practical</a>.”</span> In <em>Proceedings of the 38th International Conference on Machine Learning</em>.
</div>
<div id="ref-MendlerDunner2022Anticipating" class="csl-entry">
Mendler-Dünner, Ding, and Wang. 2022. <span>“<a href="https://openreview.net/forum?id=80RnitDehg_">Anticipating Performativity by Predicting from Predictions</a>.”</span> In <em>Advances in Neural Information Processing Systems</em>.
</div>
<div id="ref-MendlerDunner2020Stochastic" class="csl-entry">
Mendler-Dünner, Perdomo, Zrnic, et al. 2020. <span>“<a href="http://arxiv.org/abs/2006.06887">Stochastic Optimization for Performative Prediction</a>.”</span> In <em>Proceedings of the 34th International Conference on Neural Information Processing Systems</em>. NIPS ’20.
</div>
<div id="ref-Miller2019Strategic" class="csl-entry">
Miller, Milli, and Hardt. 2019. <span>“Strategic Classification Is Causal Modeling in Disguise.”</span> In <em>International Conference on Machine Learning</em>.
</div>
<div id="ref-Perdomo2020Performative" class="csl-entry">
Perdomo, Zrnic, Mendler-Dünner, et al. 2020. <span>“<a href="http://arxiv.org/abs/2002.06673">Performative Prediction</a>.”</span> In <em>Proceedings of the 37th International Conference on Machine Learning</em>.
</div>
<div id="ref-Scott1998Seeing" class="csl-entry">
Scott. 1998. <em>Seeing Like a State: How Certain Schemes to Improve the Human Condition Have Failed</em>. Veritas Paperbacks Ser.
</div>
</div>


</section>

 ]]></description>
  <category>agents</category>
  <category>classification</category>
  <category>classification and society</category>
  <category>collective knowledge</category>
  <category>confidentiality</category>
  <category>culture</category>
  <category>ethics</category>
  <category>game theory</category>
  <category>how do science</category>
  <category>incentive mechanisms</category>
  <category>performative prediction problems</category>
  <category>sociology</category>
  <category>statistics</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/performative_prediction.html</guid>
  <pubDate>Tue, 09 Jun 2026 00:01:28 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/stock_n_thunder_RP-P-OB-84.581.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Building a maths agent</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/automatic_maths.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-caution">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>This page is mostly AI slop, i.e.&nbsp;LLM descriptions of my decisions in some recent exploratory projects. However, it is useful, so I am publishing it now rather than waiting for a more polished version.</p>
</div>
</div>
</div>
<div id="fig-star-clock" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-star-clock-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/star_clock_RP-P-1904-990.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/star_clock_RP-P-1904-990.avif" id="fig-star-clock" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-star-clock-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p><a href="../notebook/ai_reasoning.html">Mathematical reasoning agents</a> are a thing, and as a working mathematician, I want them. Or rather, if they are to exist, I don’t want to be the one without them. And if they do exist and I have one, I want one that lets me reason faster over new domains, check my work, and augment my feeble meat brain as smoothly as possible.</p>
<p>The theory and practice of how to do this are weirdly accessible, utterly not what I expected, and relatively simple to implement.</p>
<p>That last property is fortunate, as the marketplace is thin. As working mathematicians, we are resigned to using tools that predate modern conveniences. The field does not exactly seethe with polished, friendly software off-the-shelf, as it would if we were doing things more central to the profit chain, such as munging office memos, or for <a href="../notebook/automatic_coding.html">agentic coding</a>.</p>
<p>So — we build our own, I guess? I have been begrudgingly building my own as a learning exercise. This post is distilled from my attempt to speed-run the last two years of mathematical reasoning LLM advancements for my own benefit.</p>
<p>I wanted it to be a cogent and principled introduction to how to implement and optimize in practice for the mathematical skill that <a href="../notebook/ai_reasoning.html">we believe LLMs to have</a>. In practice it is more of a stream-of-consciousness lab notebook put through the AI blender. <strong>tl;dr</strong> good mathematical reasoning ends up being somewhat about models and much more about specialized <a href="../notebook/agent_harness_design.html">harness design</a>, with the design criteria determined by <a href="../notebook/ai_reasoning.html#from-the-target">whether we want answers, or proofs or <em>formal</em> proofs</a>. The first is <a href="../notebook/ai_reasoning.html#targets">solver</a>, the second is kinda the default if you use an LLM and the third requires special skills in an obscure language, <a href="../notebook/proof_assistants.html">Lean</a>.</p>
<p>I already run <a href="../notebook/ai_agents_applied.html">consumer-grade agent harnesses</a> for <a href="../notebook/automatic_coding.html">code</a> — a model and a sandbox and a loop that splices the two. What do I need to add to get sweet theorems out of those?</p>
<section id="path" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="path"><span class="header-section-number">1</span> The path through</h2>
<p>Suppose I already use some <a href="../notebook/automatic_coding.html">coding harness</a>. I can proceed step by step to add features, one at a time, <a href="../notebook/agent_harness_design.html#design-space">walking through harness design space</a> until it is more optimized for mathematics than for code. That space has four axes — <em>wiring</em> (how the calls connect to one another), <em>verification</em> (what gets to say an attempt was any good), <em>context</em> (what each call is shown) and <em>dispatch</em> (which model or tool fills which slot) — and every step below moves exactly one of them, which is what the second column of the table records. Some steps, in a recommended order (I won’t divulge whether I actually followed this order):</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Step</th>
<th>Axis moved</th>
<th>What it buys</th>
<th>Cost to build</th>
<th>Measured effect</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Easy mode</strong></td>
<td>—</td>
<td>an agent with a generalist backend, used cleverly</td>
<td>free</td>
<td>the baseline</td>
</tr>
<tr class="even">
<td><strong>Lend it an oracle</strong></td>
<td>dispatch</td>
<td>a maths model behind a <code>solve()</code> tool, for the sub-problems the generalist should not attempt</td>
<td>an afternoon</td>
<td>least of anything on offer</td>
</tr>
<tr class="odd">
<td><strong>Build the loop</strong></td>
<td>wiring → sequential</td>
<td>an explicit generate-run-verify system, specialist models throughout if we want</td>
<td>a weekend</td>
<td><strong>largest single contributor</strong></td>
</tr>
<tr class="even">
<td><strong>A scratchpad</strong></td>
<td>context</td>
<td>what failed carried across attempts, so the loop stops repeating itself</td>
<td>hours, once the loop exists</td>
<td><strong>second largest</strong></td>
</tr>
<tr class="odd">
<td><strong>Fan-out</strong></td>
<td>wiring → parallel</td>
<td>many samples, cross-checked — compute traded for accuracy</td>
<td>cheap to build, dear to run</td>
<td>large, but only as good as whatever picks the winner</td>
</tr>
<tr class="even">
<td><strong>Lean</strong></td>
<td>verification → exact</td>
<td>the problem formalized into machine-checkable form</td>
<td>days of toolchain plumbing</td>
<td>changes what every other step can do</td>
</tr>
</tbody>
</table>
<p>Maths, as an automatically-verifiable problem class, is amenable to all kinds of nice tricks. A maths executor by default only ever checks a calculation, which is <a href="../notebook/agent_harness_design.html#def-side-effect-free">side-effect-free</a>. As such, fifty attempts will not clobber each other, so we can, e.g.&nbsp;fan-out and try everything everywhere all at once.</p>
<p>These all cost different amounts of effort to build, and yield different amounts of effect. That last column is mostly not my measurement. The loop, scratchpad and fan-out rows come from AxProverBase <span class="citation" data-cites="Requena2026Minimal">(Requena et al. 2026)</span>, a deliberately stripped-down Lean-proving agent whose authors assembled it one component at a time and scored each addition separately — the nearest thing to a controlled experiment on any of this that I know of. Ranked by <em>effect</em>, they put iterative refinement first and memory second, which are the third and fourth steps here; building the oracle is nevertheless easier and more fun than either. The remaining two rows are mine. The oracle row reads across from their sweep over frontier models rather than from an ablation of anything oracle-shaped, and the Lean row is not a measurement at all — the compiler is the substrate their agent runs on, not a component they could take out and score.</p>
<p>I suspect that the table continues past my ability to build: <a href="../notebook/ai_reasoning.html#decomposition">the recursive, self-grading, re-formalizing orchestrators at the top of the mathematics leaderboard</a> have enough startup money behind them that I am not going to reproduce one in a weekend. That is not an argument for buying instead, though, and the numbers do not make one either. The home-built loop comes out around five times cheaper per problem and solves about half as many: cheaper per attempt, weaker per problem, and the trade is ours to make rather than theirs.</p>
<p>Two maths-harness gotchas:</p>
<ul>
<li><a href="../notebook/ai_reasoning.html#tir-vs-cot">TIR models speak their own tool-call dialect</a>, so the orchestrator has to parse fenced code templates and splice results back in the shape each model expects. Budget for writing that parser per model family; it is the thing that stops a generic harness working.</li>
<li>The model writes the code we run unobserved, so <a href="../notebook/ai_agents_applied.html#agent-risks">sandboxing is wise</a>. The only maths-specific detail is that the specialists assume a full scientific Python (SymPy, NumPy), so build the sandbox accordingly.</li>
</ul>
</section>
<section id="easy-mode" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="easy-mode"><span class="header-section-number">2</span> Easy mode</h2>
<p>The baseline.</p>
<p>We need not be galaxy-brained. Claude works for maths just as well as it does code, as do most generalist models — The latest DeepSeek, or a big Qwen3-Thinking are all respectable mathematicians; tell them to “do the maths”, then “check the working with Python/SymPy”.</p>
<p>NB local clients are less capable at rendering equations, so I find I want to build <a href="../notebook/agent_frontends.html">Open WebUI</a> to make equations easier on human eyes, but this is definitely lily-gilding.</p>
</section>
<section id="oracle" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="oracle"><span class="header-section-number">3</span> Lend it an oracle</h2>
<p><em>Dispatch tricky bits to specialist models</em>.</p>
<p>Now, if we want a little more juice, we can squeeze it from our existing agent, using it as an orchestrator but delegating mathematics (or the checking of mathematics) to a specialist maths model. Practically, this means something like building an <em>oracle</em> (a <code>solve()</code> tool) to grind on knotty maths problems, splicing the result back into the orchestrator’s own reasoning. <em>Oracle</em> is meant in a strict sense: the orchestrator does not attempt the sub-problem nor does it check the answer. Rather it asks and believes with whatever confidence it is instructed to have.</p>
<p>Python harnesses like Smolagents or <a href="../notebook/ai_agents_applied.html#qwen-agent">Qwen-Agent</a> can simply call a Python function which wraps structured invocation of specialist models. For other agents (Claude-like, Goose, Codex, Gemini CLI, pi, Hermes) we can expose the <code>solve()</code> as a CLI tool. Either way, we wrap it with an <a href="https://agentskills.io?ref=danmackinlay.name">agentskills.io</a> <code>SKILL.md</code> to help the calling agent. Skill-blind agents can alternatively use <a href="../notebook/ai_agents_applied.html#mcp">an MCP server</a>.</p>
<p>I whipped up <a href="https://github.com/danmackinlay/mathx?ref=danmackinlay.name">mathx</a> as an example of doing all three access paths — importable Python, CLI, MCP server — with the <code>SKILL.md</code> layered over the first two, in a relatively swappable manner.</p>
<p>We might wish to fan-out in such an oracle. Done synchronously, this can be slow and hit time limits on tool use, or just get boring. Asynchrony and parallelism depend on support in the orchestrating agent. When we own the loop, a fan-out is as simple as <code>asyncio.gather</code>. For tool-use agents, the <a href="https://dev.to/aws/fix-mcp-timeouts-async-handleid-pattern-8ek?ref=danmackinlay.name">async-handle pattern</a> is probably the goods: We set up a handle-and-poll loop in pair of ordinary tools: <code>submit_solve()</code> returns a job id at once and <code>check_solve(id)</code> returns status-or-result at once, so each call is fast. Some harnesses also dispatch background sub-agents of their own, and some MCP clients support <a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks?ref=danmackinlay.name"><em>Tasks</em></a> at the protocol level; that might work too.</p>
</section>
<section id="iterate" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="iterate"><span class="header-section-number">4</span> Build the loop</h2>
<p>Sequentially iterate solutions, learning each time.</p>
<p>If easy mode plus an oracle aren’t enough, we can write a custom loop that specializes <a href="../notebook/agent_harness_design.html#loop">the generic one</a> for mathematical needs.</p>
<p>What if we are trying to solve a hard problem? Intuitively, it makes sense to grind, learning from past mistakes at each attempt. Building a loop that supports this turns out to be kinda good actually. In the wild I have seen this as a two-role iterative system. A <em>proposer</em> writes a candidate answer or proof, and an <em>executor</em> runs or checks what the proposer wrote. The verdict — a compiler error, a failed numeric check — is fed into the next prompt as a minimal trace of what failed last time. Round it goes again until the check passes or the iteration budget runs out (AxProverBase allows 50 rounds per problem). This is not the same thing as fan-out: <em>there</em> the attempts are independent and mutually blind, whereas here attempt <img src="https://latex.codecogs.com/png.latex?n+1"> has seen what attempt <img src="https://latex.codecogs.com/png.latex?n"> got wrong. This is the single largest contributor to success increases in the AxProverBase ablations, and it is also where the maths usage diverges from the coding usage.</p>
</section>
<section id="scratchpad" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="scratchpad"><span class="header-section-number">5</span> A scratchpad</h2>
<p>Exploit <strong>context</strong> to record a longer narrative.</p>
<p>We can build more elaborate memory suited to an epic proof attempt, specifically, not just last turn, but of the whole proof journey thus far. The crude version pastes the whole transcript of past attempts into the prompt, but that bloats the context, and eventually overflows the window. The SOTA variant requires that after each attempt the proposer rewrites a running scratchpad of what it learned, pruned so it never grows without bound. In AxProverBase, that method buys around 7% more theorems at ~20% lower cost, and with lower variance than memoryless attempts and completist memory strategies.</p>
</section>
<section id="fan-out" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="fan-out"><span class="header-section-number">6</span> Fan-out</h2>
<p>Parallel proposals and checking.</p>
<p>Fan-out improves mathematical accuracy by evaluating many candidate solutions — <a href="../notebook/ai_reasoning.html#majk"><code>maj@k</code></a> or <a href="../notebook/ai_reasoning.html#proving-with-a-compiler"><code>Pass@k</code></a>, depending on our backend. We can run a lot of these fuckers at once, since the tokens stream over the network in parallel.</p>
<p>The goal here is overall <em>efficiency</em>: do more parallel copies of a cheaper model get us further than fewer copies of a more powerful one? For problems that are just on the cusp of being solvable, this turns out to be helpful.</p>
<p>Sampling wide is not too crazy. The hard bit is working out if any of the answers were good.</p>
<section id="answer-equivalence" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="answer-equivalence"><span class="header-section-number">6.1</span> Picking a winner from answers</h3>
<p>The classic idea is to vote on plain-language answers and pick the most common, which <a href="../notebook/ai_reasoning.html#majk">we call <code>maj@k</code></a>. Implementing it is not trivial, because deciding whether two answers <em>are</em> the same is <a href="../notebook/ai_reasoning.html#majk">a soft verification problem in its own right, and provably not a solvable one</a>. This was a PITA when I implemented <a href="https://github.com/danmackinlay/mathx?ref=danmackinlay.name">mathx</a>.</p>
<p>Is <code>0.5 \log \cos(ax-b)</code> the same as <code>\frac12 \ln \cos(b-ax)</code>? The standard fix (e.g.&nbsp;in <a href="https://huggingface.co/blog/math_verify_leaderboard?ref=danmackinlay.name">math-verify</a>) is to parse both into a <a href="../notebook/computational_symbolic_maths.html">computer algebra system</a> and test equality there. Better than nothing, not amazing: the LaTeX inconsistencies are each fixable one patch at a time and the supply of them is inexhaustible, while the heuristic escape hatches (<code>math-verify</code>’s <code>strict=False</code>) are way too permissive, counting answers as the same because they happen to share a greek letter.</p>
<p>A fuzzy but interesting fallback is to use LLMs for this too, delegating to the decision about whether two things are the “same” to yet another model invocation OpenAI’s <a href="https://github.com/openai/simple-evals?ref=danmackinlay.name">simple-evals</a>, for example, scores MATH with a model-based equality-checker prompt rather than rules; NVIDIA’s <a href="https://github.com/NVIDIA-NeMo/Skills?ref=danmackinlay.name">NeMo-Skills</a> ships both an LLM judge and sympy checks. There exist <em>trained</em> verifier models, but they are less useful than they sound: they grade candidate-against-ground-truth, not candidate-against-candidate, i.e.&nbsp;they don’t help with novel mathematics. xVerify has bad licensing, and the Omni-MATH-2 audit <span class="citation" data-cites="Ballon2026Benchmarks">(Ballon et al. 2026)</span> found Omni-Judge wrong in 96% of its disagreements. <a href="https://github.com/open-compass/CompassVerifier?ref=danmackinlay.name">CompassVerifier</a> (Apache-2.0) is a credible trained option, and its human-labelled VerifierBench is a free test set for verifying judge models in general. Clearly though, we might get worried about diminishing returns of endlessly layering LLMs on LLMs to patch imperfections in LLMs.</p>
<p>In the little toy <code>mathx</code> project, <a href="https://github.com/danmackinlay/mathx/blob/maximal/DESIGN_NOTES.md?ref=danmackinlay.name#answer-equivalence">I went for a tiered, audited approach</a> so <del>I can experience all the failure modes</del> so I can swiss-cheese my problems. Exact match wins, then CAS equality, then an opt-in LLM judge for any leftovers from the CAS. In all cases, we check both orders (<img src="https://latex.codecogs.com/png.latex?x=y"> and <img src="https://latex.codecogs.com/png.latex?y=x">) and reject anything inconsistent or unparseable. We disclose the methods used in each case. (e.g.&nbsp;<code>11/12 (3 judge merges)</code>). On one of my test problems (<code>maj@16</code>, of which 12 samples were parseable), CAS fixes got us from 4/12 naïve matches to 8/12, and an LLM judge up to 11/12.</p>
</section>
<section id="proof-judging" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="proof-judging"><span class="header-section-number">6.2</span> Picking a winner from proofs</h3>
<p>Everything so far was about <em>answers</em> (what is <img src="https://latex.codecogs.com/png.latex?7%5E%7B2026%7D%20%5Cbmod%2013">?). Hunting <em>proofs</em> instead (prove that <img src="https://latex.codecogs.com/png.latex?7%5E%7B2026%7D%20%5Cbmod%2013%20=%204">) removes the option of counting: absent a formalization, <a href="../notebook/ai_reasoning.html#judging">the aggregation step can only be a judge that reads them</a>. I have not built anything so elevated.</p>
<p>Possibly helpful for this: OpenRouter’s <a href="https://openrouter.ai/docs/guides/routing/routers/fusion-router?ref=danmackinlay.name">Fusion Router</a>, a pre-rolled, hosted fan-out-and-judge which looks like it might handle this kind of thing. Point a request at <code>openrouter/fusion</code>: a panel of models answers in parallel, and a judge model compares their answers to produce structured consensus/contradiction/blind-spot analysis that feeds a final answer. This seems fine.</p>
</section>
</section>
<section id="lean" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="lean"><span class="header-section-number">7</span> Lean</h2>
<p><em>Moves <strong>verification</strong> to an exact grade: the checker cannot be talked round.</em></p>
<p>So far we have been imagining english-language mathematical reasoning, which has ended up being kinda painful. Whacking in <a href="../notebook/ai_reasoning.html#proving-with-a-compiler">a proof assistant</a> (in practice, always <a href="../notebook/proof_assistants.html">Lean</a>) makes this much cleaner, at the cost of being weirder. Modal’s <a href="https://modal.com/blog/building-an-rl-theorem-proving-workflow-on-modal?ref=danmackinlay.name">theorem-proving case study</a> shows how it is done.</p>
<p>Here, the model emits candidate proofs in Lean, they are submitted to the Lean language server, and we get back <code>{"ok": bool, "errors": [...]}</code>, which tells us whether the proof landed.</p>
<p>A Lean toolchain can be tedious to set up, including gigabytes of “Mathlib” that takes hours to build and comprise brittle, version-sensitive plumbing. Various Lean technologies support this workflow (<a href="https://github.com/leanprover-community/repl?ref=danmackinlay.name"><code>lean-repl</code></a> (the lightest), <a href="https://github.com/lenianiva/Pantograph?ref=danmackinlay.name">Pantograph</a>, or <a href="https://leandojo.org?ref=danmackinlay.name">LeanDojo</a>, usually behind the <a href="https://github.com/project-numina/kimina-lean-server?ref=danmackinlay.name">Kimina Lean Server</a> to keep it warm).</p>
<p>The weirdness now, that is in the very manner we express mathematics. Lean is a <em>formal</em> language, guaranteeing formal proofs of formal problem statements, so obviously it needs formalization if we start with plain language problem statements — ideally <a href="../notebook/ai_reasoning.html#proving-with-a-compiler">autoformalization</a>. One-shot autoformalization models (Kimina-Autoformalizer, Goedel-Formalizer-V2) suck though. This formal proving thing seems to work fine for formalized proofs, but getting the problem into such a state is unreliable and difficult to debug.</p>
<section id="lea" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="lea"><span class="header-section-number">7.1</span> Lea</h3>
<p><a href="https://vida-nyu.github.io/Lea/?ref=danmackinlay.name">Lea</a> (<a href="https://github.com/VIDA-NYU/Lea?ref=danmackinlay.name">source</a>/<a href="https://github.com/danmackinlay/Lea?ref=danmackinlay.name">my fork</a>) is a Lean 4 agent backbone from NYU’s VIDA lab, with a standalone web client and a Chrome extension.</p>
<p>Since the whole thing is carved out by the NYU folks it all just works. <code>docker compose pull &amp;&amp; docker compose up</code> fetches a docker image with Lean and Mathlib already inside — about 3.7 GB down, 10.7 GB on disk. We can forget all this version pinning bullshit, as long as you are happy to live with VIDA’s preferred version of everything.</p>
<p>It interactively and iteratively formalizes labelled theorem blocks straight out of a locally-hosted Overleaf document, which is wayyyyy more civilised than rawdogging Lean for a n00b like me. All the decomposition into lemmas can be steered from inside a comfy mathematics markup system. This is some genius UX design, informed by their own research into mathematicians’ workflows <span class="citation" data-cites="Collins2026Human">(Collins et al. 2026)</span> including the revelation that 2/3 of mathematicians want to keep a steering hand on this tiller.</p>
<p>The harness has the classic minimal-mathematics-machine shape, with a small tool surface: read, write, edit, <code>lean_check</code>, shell, Mathlib search. We can register extra tools via a URL. Runs are persisted as “typed event streams”, supporting replays from dropped sessions, and persistent projects with accumulating scratchpad memory. Model routing is LiteLLM, so we can whack in a self-hosted vLLM endpoint as <code>hosted_vllm/&lt;model&gt;</code>.</p>
<p>Benchmark metrics (e.g.&nbsp;price) are unclear. AxProverBase publishes an ablation table, for example, although it is clearly going to be expensive and difficult to benchmark a Human-in-the-Loop mathematical workflow the same way we can benchmark the automatic ones.</p>
</section>
</section>
<section id="compute" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="compute"><span class="header-section-number">8</span> Where the compute comes from</h2>
<p>What the steps cost to <em>run</em> once built, where fan-out is the only one that costs much.</p>
<p>Build and debug the whole loop on a laptop first — a local IPython kernel for the solver, <code>lean-repl</code> on disk for the prover — and never pay for a GPU until we fan out.<sup>1</sup> <a href="https://modal.com?ref=danmackinlay.name">Modal</a> is a tidy cloud backend to fan out to.</p>
<p><strong>Tokens</strong> come from a model behind a scale-to-zero, OpenAI-compatible <code>vllm serve</code> endpoint — a few dozen lines mount a Volume so the weights download once, and the GPU drops to zero between requests, meaning we pay only for the seconds a request actually runs.</p>
<p><strong>Fan-out</strong> is then just a <code>.map</code> — one chain per call, each with its own sandbox, all sharing the one endpoint:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># maj@k as a fan-out</span></span>
<span id="cb1-2">answers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> solve_one.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>([problem] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 32 chains, 32 sandboxes, one endpoint</span></span>
<span id="cb1-3">winner  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(a <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> answers <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> a).most_common(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div></div>
<p>The same shape sweeps a whole problem set, or runs a prover’s <code>Pass@k</code> with the Lean verifier as the executor. A heavy tool — a Gröbner basis, a big symbolic integral, a Sage or PARI-GP call — goes on its own cheap CPU <a href="https://modal.com/docs/guide/sandbox?ref=danmackinlay.name"><code>modal.Sandbox</code></a> next to the GPU endpoint, so we aren’t paying for an idle H100 to run SymPy.</p>
<section id="what-it-costs-rented" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="what-it-costs-rented"><span class="header-section-number">8.1</span> What it costs, rented</h3>
<p>The frontier provers aren’t something we rent by the token: <a href="../notebook/ai_reasoning.html#decomposition">the systems at the top of the leaderboard</a> are pilot-only and priced in dollars per problem: on the <a href="https://trishullab.github.io/PutnamBench/leaderboard.html?ref=danmackinlay.name">PutnamBench leaderboard</a> as of 2026-08-31, <a href="https://logicalintelligence.com/aleph-prover.html?ref=danmackinlay.name">Aleph</a>’s two entries average $68 and $74 a problem, the second of them solving all 672 at up to $1468 for a single one.<sup>2</sup> That is the number a home-built loop has to beat, and one loop has published enough to compare against.</p>
<p><a href="https://github.com/Axiomatic-AI/ax-prover-base?ref=danmackinlay.name">AxProverBase</a> — the minimal agent whose ablations rank the steps above — ships as working code: an off-the-rack Claude Opus 4.5, no fine-tuning, in an iterative compile-and-retry loop with memory and Mathlib search, no recursive decomposition. It reaches 54.7% on PutnamBench, which is 672 problems from the Putnam undergraduate competition formalized in Lean and currently <a href="../notebook/ai_reasoning.html#benchmarks">the live proof benchmark</a>.<sup>3</sup> That is pass@1 parity with <a href="../notebook/ai_reasoning.html#decomposition">Apple’s decomposition agent Hilbert</a> — 55.9% against AxProverBase’s 54.7% — while spending about 450× fewer tokens per problem, 4.2M against 1880M, at roughly $12.6 a problem against Aleph’s $68–74 average. The parity is at one sample, mind: let Hilbert run out to pass@1840 and it reaches 70%. So the price of the amateur version lands within a factor of a few of the professional one.</p>
<p>What can we <em>rent</em> instead of self-hosting? Fewer options than I’d like. Few specialist maths models are hosted per token, and the one serverless home for the narrow solvers (OpenMath-Nemotron, AceMath<sup>4</sup>) is Featherless, which caps concurrency per plan — exactly the wrong constraint for fan-out. So sampling these widely usually means self-hosting, on <a href="https://runpod.io?ref=danmackinlay.name">RunPod</a> (from ~$0.27/hr) or Modal (scale-to-zero, which wins on bursty use).<sup>5</sup> The exception is at the top of the range: DeepSeek-Prover-V2-671B would need an eight-GPU node to self-host, but Novita meters it cheaply (~$0.70/$2.50 per 1M tokens), so pairing it with a local <code>lean-repl</code> loop is a cheap way into industrial proving.</p>
</section>
<section id="on-a-laptop" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="on-a-laptop"><span class="header-section-number">8.2</span> On a laptop</h3>
<p>When the model runs no code, the whole loop collapses onto a single Mac: <code>maj@k</code> is just <img src="https://latex.codecogs.com/png.latex?k"> chat calls and a <code>Counter</code> — no Modal, no <code>.map</code>, no per-chain sandbox. Point it at a local endpoint that can batch the samples and keep the model resident; <a href="../notebook/local_llm_mac.html#vllm-mlx">vllm-mlx</a> is what I actually run, with <a href="../notebook/local_llm_mac.html#omlx">oMLX</a> as the near-identical fork. A small-but-strong reasoner shines here — many copies for the price of one specialist — and <a href="https://huggingface.co/models?other=base_model:quantized:WeiboAI/VibeThinker-3B&amp;ref=danmackinlay.name">VibeThinker-3B</a> is the obvious candidate, reporting <a href="../notebook/ai_reasoning.html#co-design">frontier-level verifiable maths at 3B</a>. Treat that as a hypothesis to test on our own problems rather than a fact, for two reasons. The 3B has never been independently reproduced — every secondary source traces back to the same self-report, and MathArena has not run it. Its 1.5B sibling <em>has</em> been, under <a href="../notebook/ai_reasoning.html#benchmarks">a standardized harness across ten seeds</a>, where it holds up well but lands below the headline and carries error bars of ±4 to ±8 points. And the one small maths specialist that <em>has</em> been independently benchmarked, QED-Nano, takes 82.5% on AIME 2026 and then 14.06% on ArXivMath, last of twenty-seven: <a href="../notebook/ai_reasoning.html#specialize-or-not">these models generalize to the shape of problem they were tuned on</a> and fall off a cliff just outside it. Fine if our problems are AIME-shaped. A trap otherwise. We wrap it in a plain <code>solve()</code> and call it straight from the loop, not as an MCP tool — which is only needed when the agent is doing the orchestration.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># maj@k against a local oMLX endpoint — the reasoner emits no code, so no sandbox</span></span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> AsyncOpenAI</span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio, re</span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter</span>
<span id="cb2-5"></span>
<span id="cb2-6">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AsyncOpenAI(base_url<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://localhost:8000/v1"</span>, api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>)</span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># naïve: [^}]* stops at the first }, so \boxed{\frac{1}{2}} wants a brace-counting scanner</span></span>
<span id="cb2-8">BOXED  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\\</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">boxed</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\{</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">(</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[^}]</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb2-9"></span>
<span id="cb2-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sample(problem):</span>
<span id="cb2-11">    r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> client.chat.completions.create(</span>
<span id="cb2-12">        model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vibethinker-8bit"</span>,</span>
<span id="cb2-13">        temperature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, top_p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>,   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># per request; see below</span></span>
<span id="cb2-14">        messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: problem}], max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32000</span>)</span>
<span id="cb2-15">    hits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BOXED.findall(r.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="cb2-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> hits[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> hits <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># last boxed = the final answer</span></span>
<span id="cb2-17"></span>
<span id="cb2-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> solve(problem, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>):</span>
<span id="cb2-19">    answers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>(sample(problem) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(k)))</span>
<span id="cb2-20">    votes   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(a <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> answers <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> a)</span>
<span id="cb2-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (votes.most_common(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> votes <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>), votes</span></code></pre></div></div>
<p>Two details. Set the sampling parameters <em>per request</em>: greedy decoding returns the same answer <img src="https://latex.codecogs.com/png.latex?k"> times, so there is nothing to vote over, and the local servers won’t set them for us — vllm-mlx has no per-model sampling keys and falls back to a temperature of 0.7, cooler than VibeThinker wants, and <a href="../notebook/local_llm_mac.html#sampling-real">which servers honour which parameters is its own saga</a>. And extraction is easier than it looks — a reasoning model buries its answer under a long <code>&lt;think&gt;</code> trace, but most OpenAI-compatible servers expose the post-think conclusion separately (<code>reasoning_content</code>, given a <code>--reasoning-parser</code>), so the <code>content</code> we regex is the short conclusion and the last <code>\boxed{}</code> is reliably the answer the model is committing to. Return the whole <code>votes</code> tally, not just the winner: the margin is the loop’s confidence signal — 14/16 is a different thing to act on than a 6/5/5 split, where the driver can escalate <img src="https://latex.codecogs.com/png.latex?k"> or surface the disagreement instead of committing.</p>
</section>
</section>
<section id="taught" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="taught"><span class="header-section-number">9</span> What the build taught me</h2>
<p>Three things, in decreasing order of how much they surprised me.</p>
<p>The loop is the half I can build, and it is also the half that pays. The ablations put iterative refinement first and memory second, and both of those are a weekend of ordinary Python around models I did not train. Nothing further up the table needed a GPU either — the entire thing debugs on a laptop, and the first dollar goes on fan-out.</p>
<p>But the model I point the loop at cannot be chosen from a benchmark table, because <a href="../notebook/ai_reasoning.html#model-or-harness">responsiveness to a scaffold appears on no leaderboard</a>. The number that matters is how the model behaves <em>inside my loop</em> — whether it reads a compiler error and does something different next round, whether its scratchpad note is any good. So the shortlist comes from benchmarks and the choice comes from measurement, which means budgeting for the measurement.</p>
<p>There is a second reason, and it is sharper because somebody has measured it. <a href="../notebook/ai_reasoning.html#diversity-collapse">RL post-training buys pass@1 by spending the high-<img src="https://latex.codecogs.com/png.latex?k"> tail</a> — an RLVR-tuned model beats its own base at one sample and loses to it at hundreds — and hundreds is the regime fan-out lives in. Measured on a Lean prover, the SFT checkpoint often samples better than the RL checkpoint out of the same lab, purely because it stayed diverse. Standardized re-evaluation of the published gains points the same way, from a completely different direction: RL improvements are mostly modest and overfit small benchmarks, where supervised fine-tuning generalizes more consistently. So where a lab ships both, the SFT one is worth trying as a fan-out backend, and no leaderboard will suggest it: they report the single sample where the RL model wins.</p>
<p>And most of the fiddly work was not mathematics. Deciding whether two answers are the same, pinning Mathlib to the commit the model was trained against, getting a local server to honour a temperature — that is where the days went, and none of it is the part I found interesting. The mathematics was the easy bit, which is either encouraging or ominous depending on the hour.</p>
</section>
<section id="incoming" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="incoming"><span class="header-section-number">10</span> Incoming</h2>
<ul>
<li><a href="https://docs.google.com/document/d/1XUBjP75Fl-W3qo2NZa8bMJV-UJY5vt6LJFHcqQaV3CI/edit?pli=1&amp;tab=t.0&amp;ref=danmackinlay.name#heading=h.te8g8ohh2ysz">The Journal of Artificial Mathematics</a></li>
<li><a href="https://bolzano.app?ref=danmackinlay.name">Bolzano</a> (<a href="https://github.com/vaclavrozhon/problem-solver?ref=danmackinlay.name">source</a>) — a colleague’s “automatic researcher” aimed at open problems</li>
<li><a href="https://www.math.toronto.edu/mgualt/math-blog/posts/math-and-llm-2026/?ref=danmackinlay.name">Mathematics and the LLM: 2026 – Coordinate Change</a></li>
</ul>
<p>Which chat window we read all this in is a separable and much duller problem, and <a href="../notebook/agent_frontends.html">has its own page</a>.</p>
</section>
<section id="references" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="references"><span class="header-section-number">11</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Achim2025Aristotle" class="csl-entry">
Achim, Best, Bietti, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2510.01346">Aristotle: IMO-Level Automated Theorem Proving</a>.”</span>
</div>
<div id="ref-Akyurek2024Surprising" class="csl-entry">
Akyürek, Damani, Qiu, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2411.07279">The Surprising Effectiveness of Test-Time Training for Abstract Reasoning</a>.”</span>
</div>
<div id="ref-Ballon2026Benchmarks" class="csl-entry">
Ballon, Algaba, Verbeken, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2601.19532">Benchmarks Saturate When The Model Gets Smarter Than The Judge</a>.”</span>
</div>
<div id="ref-Chen2025SeedProver" class="csl-entry">
Chen, Jiangjie, Chen, Du, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2512.17260">Seed-Prover 1.5: Mastering Undergraduate-Level Theorem Proving via Learning from Experience</a>.”</span>
</div>
<div id="ref-Chen2025SeedProvera" class="csl-entry">
Chen, Luoxin, Gu, Huang, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2507.23726">Seed-Prover: Deep and Broad Reasoning for Automated Theorem Proving</a>.”</span>
</div>
<div id="ref-Collins2026Human" class="csl-entry">
Collins, Frieder, Bayer, et al. 2026. <span>“<a href="https://arxiv.org/abs/2606.04273">Human Agency in Initial Human-AI Proof Formalization Workflows</a>.”</span>
</div>
<div id="ref-Dekoninck2026Benchmarks" class="csl-entry">
Dekoninck, Jovanović, Gehrunger, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2605.00674">Beyond Benchmarks: MathArena as an Evaluation Platform for Mathematics with LLMs</a>.”</span>
</div>
<div id="ref-Goemans2026Comprehensive" class="csl-entry">
Goemans, Altman, Dreksler, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2606.00047">Comprehensive AI Governance Requires Addressing Non-Model Gains</a>.”</span>
</div>
<div id="ref-Hochlehnert2025Sober" class="csl-entry">
Hochlehnert, Bhatnagar, Udandarao, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2504.07086">A Sober Look at Progress in Language Model Reasoning: Pitfalls and Paths to Reproducibility</a>.”</span>
</div>
<div id="ref-Lin2025GoedelProverV2" class="csl-entry">
Lin, Tang, Lyu, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2508.03613">Goedel-Prover-V2: Scaling Formal Theorem Proving with Scaffolded Data Synthesis and Self-Correction</a>.”</span>
</div>
<div id="ref-Liu2025AceMath" class="csl-entry">
Liu, Chen, Shoeybi, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2412.15084">AceMath: Advancing Frontier Math Reasoning with Post-Training and Reward Modeling</a>.”</span>
</div>
<div id="ref-Ma2026OProver" class="csl-entry">
Ma, Ma, Guo, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2605.17283">OProver: A Unified Framework for Agentic Formal Theorem Proving</a>.”</span>
</div>
<div id="ref-Moshkov2025AIMO2" class="csl-entry">
Moshkov, Hanley, Sorokin, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2504.16891">AIMO-2 Winning Solution: Building State-of-the-Art Mathematical Reasoning Models with OpenMathReasoning Dataset</a>.”</span>
</div>
<div id="ref-Ren2025DeepSeekProverV2" class="csl-entry">
Ren, Shao, Song, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2504.21801">DeepSeek-Prover-V2: Advancing Formal Mathematical Reasoning via Reinforcement Learning for Subgoal Decomposition</a>.”</span>
</div>
<div id="ref-Requena2026Minimal" class="csl-entry">
Requena, Letson, Nowakowski, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2602.24273">A Minimal Agent for Automated Theorem Proving</a>.”</span>
</div>
<div id="ref-Richardson1968Undecidable" class="csl-entry">
Richardson. 1968. <span>“<a href="https://doi.org/10.2307/2271358">Some Undecidable Problems Involving Elementary Functions of a Real Variable</a>.”</span> <em>The Journal of Symbolic Logic</em>.
</div>
<div id="ref-Shao2025DeepSeekMathV2" class="csl-entry">
Shao, Luo, Lu, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.22570">DeepSeekMath-V2: Towards Self-Verifiable Mathematical Reasoning</a>.”</span>
</div>
<div id="ref-Shao2024DeepSeekMath" class="csl-entry">
Shao, Wang, Zhu, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2402.03300">DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models</a>.”</span>
</div>
<div id="ref-Su2025ToolOrchestra" class="csl-entry">
Su, Diao, Lu, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.21689">ToolOrchestra: Elevating Intelligence via Efficient Model and Tool Orchestration</a>.”</span>
</div>
<div id="ref-Varambally2026Hilbert" class="csl-entry">
Varambally, Voice, Sun, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2509.22819">Hilbert: Recursively Building Formal Proofs with Informal Reasoning</a>.”</span>
</div>
<div id="ref-Venhoff2025Base" class="csl-entry">
Venhoff, Arcuschin, Torr, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2510.07364">Base Models Know How to Reason, Thinking Models Learn When</a>.”</span>
</div>
<div id="ref-Wang2025KiminaProver" class="csl-entry">
Wang, Unsal, Lin, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2504.11354">Kimina-Prover Preview: Towards Large Formal Reasoning Models with Reinforcement Learning</a>.”</span>
</div>
<div id="ref-Yang2023LeanDojo" class="csl-entry">
Yang, Kaiyu, Swope, Gu, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2306.15626">LeanDojo: Theorem Proving with Retrieval-Augmented Language Models</a>.”</span>
</div>
<div id="ref-Yang2024Qwen25Math" class="csl-entry">
Yang, An, Zhang, Hui, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2409.12122">Qwen2.5-Math Technical Report: Toward Mathematical Expert Model via Self-Improvement</a>.”</span>
</div>
<div id="ref-Ye2026VERINA" class="csl-entry">
Ye, Yan, He, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2505.23135">VERINA: Benchmarking Verifiable Code Generation</a>.”</span>
</div>
<div id="ref-Yu2025FormalMATH" class="csl-entry">
Yu, Peng, Ding, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2505.02735">FormalMATH: Benchmarking Formal Mathematical Reasoning of Large Language Models</a>.”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Laptop-scale model picks: <a href="../notebook/local_llm_mac.html#models-math">OpenMath-Nemotron, Qwen2.5-Math, Goedel-Prover, all via MLX</a>.↩︎</p></li>
<li id="fn2"><p>Mind the marketing on Aleph. Logical Intelligence frames it as a step beyond LLMs toward a reasoner that “does not think in words”, but by their own telling the <em>benchmarked</em> Aleph is built on an LLM, and the language-free model is unreleased and unbenchmarked.↩︎</p></li>
<li id="fn3"><p>That 54.7% is 365/667 against an earlier revision of the benchmark; the current leaderboard scores the same run 365/672, or 54.3%.↩︎</p></li>
<li id="fn4"><p>AceMath is CC-BY-NC: non-commercial only, wherever it runs.↩︎</p></li>
<li id="fn5"><p>Privacy ranking, briefly: self-hosting on Modal is strongest; the metered shortlist (Novita, Featherless) was pre-filtered to no-train endpoints; keep unpublished proofs off first-party APIs that may train on submitted data, and be thoughtful about OpenRouter, which delegates retention upstream.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>compsci</category>
  <category>DIY</category>
  <category>economics</category>
  <category>machine learning</category>
  <category>neural nets</category>
  <category>NLP</category>
  <category>premature optimization</category>
  <category>slop</category>
  <guid>https://danmackinlay.name/notebook/automatic_maths.html</guid>
  <pubDate>Sun, 31 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/star_clock_RP-P-1904-990.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Game theory with open-source agents and simulation</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/game_theory_open_source.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-womb-view" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-womb-view-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/womb_view_RP-P-1907-5412.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/womb_view_RP-P-1907-5412.avif" id="fig-womb-view" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-womb-view-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Stub.</p>
<p>In standard <a href="../notebook/game_theory.html">game theory</a> each agent’s policy is a black box. Opponents see actions but not the mechanisms that led to the actions. <a href="../notebook/commitment.html">Commitments</a> to cooperate aren’t credible. <em>Open-source game theory</em> (OSGT) changes the action space: instead of submitting a <em>move</em>, each player submits a <em>program</em> that can read its opponent’s source code as input <span class="citation" data-cites="Barasz2014Robust">(Bárász et al. 2014)</span>.</p>
<section id="program-equilibria" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="program-equilibria"><span class="header-section-number">1</span> Program equilibria</h2>
<p>This permits <em>program equilibria</em> — cooperative Nash equilibria unavailable in the move-game, sustained by mutual verification rather than by repetition or punishment. The recursion problem (my program simulates your program which simulates mine…) is handled by resource-bounded versions of Löb’s theorem, yielding constructions like <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">-GroundedFairBot: cooperate with probability <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> unconditionally, otherwise simulate the opponent and cooperate iff the simulation says they will <span class="citation" data-cites="Critch2016Parametric">(Critch 2016)</span>. A Folk Theorem follows: in one-shot program games, any feasible and individually rational payoff is sustainable.</p>
<p>An obvious practical objection is that real-world agents don’t expose their weights, and even if they did, a deceptively aligned agent could obfuscate them. But the framing is suggestive: how <em>interpretable</em> an agent needs to be to be trustworthy, and whether modern interpretability tools can do the job that programmatic source code used to, is one of the live questions for <a href="../notebook/ai_safety.html">AI safety</a>.</p>
</section>
<section id="simulating-your-opponent" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="simulating-your-opponent"><span class="header-section-number">2</span> Simulating your opponent</h2>
</section>
<section id="in-rl" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="in-rl"><span class="header-section-number">3</span> In RL</h2>
<p><a href="../notebook/multi_agent_reinforcement_learning.html">Multi-agent reinforcement learning</a> is one possible operationalisation of OSGT.</p>
</section>
<section id="references" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="references"><span class="header-section-number">4</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Barasz2014Robust" class="csl-entry">
Bárász, Christiano, Fallenstein, et al. 2014. <span>“<a href="https://arxiv.org/abs/1401.5577">Robust Cooperation in the Prisoner’s Dilemma: Program Equilibrium via Provability Logic</a>.”</span>
</div>
<div id="ref-Critch2016Parametric" class="csl-entry">
Critch. 2016. <span>“<a href="https://doi.org/10.48550/ARXIV.1602.04184">Parametric Bounded Löb’s Theorem and Robust Cooperation of Bounded Agents</a>.”</span>
</div>
<div id="ref-Critch2017Negotiable" class="csl-entry">
———. 2017. <span>“<a href="https://doi.org/10.48550/arXiv.1701.01302">Toward Negotiable Reinforcement Learning: Shifting Priorities in Pareto Optimal Sequential Decision-Making</a>.”</span>
</div>
<div id="ref-Kovarik2024Game" class="csl-entry">
Kovarik, Oesterheld, and Conitzer. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2305.11261">Game Theory with Simulation of Other Players</a>.”</span>
</div>
<div id="ref-Kovarik2025Game" class="csl-entry">
Kovarik, Sauerberg, Hammond, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2410.14311">Game Theory with Simulation in the Presence of Unpredictable Randomisation</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>agents</category>
  <category>AI safety</category>
  <category>cooperation</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>mind</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/game_theory_open_source.html</guid>
  <pubDate>Wed, 27 May 2026 12:14:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/womb_view_RP-P-1907-5412.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Cooperation through uncertainty</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/cooperation_uncertainty.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p>Placeholder.</p>
<div id="fig-mrs-brown-figure" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-mrs-brown-figure-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/mrs_brown_figure.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: Figure: Very partial observation of Mrs Brown"><img src="https://danmackinlay.name/images/mrs_brown_figure.webp" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-mrs-brown-figure-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: <span class="caption">Figure: Very partial observation of Mrs Brown</span>
</figcaption>
</figure>
</div>
<p><a href="../notebook/opponent_shaping.html#rusp">Elsewhere</a> I learned about RUSP <span class="citation" data-cites="Baker2020Emergent">(Baker 2020)</span>, a framework for training agents to cooperate when they have only a noisy observation of their own prosocial weights and no information about others’.</p>
<p>This reminded me of <span class="citation" data-cites="OConnor2015Ambiguity">O’Connor (2015)</span> and the broader question of the pro-social uses of ignorance, which crops up implicitly in <a href="../notebook/opponent_shaping.html">opponent shaping</a> and explicitly in <a href="../notebook/red_queen_signalling.html">signalling</a> and <a href="../notebook/simulacra.html">simulacra</a> dynamics.</p>
<p>Something about uncertainty in signalling seems waiting to be written here. And evolution.</p>
<section id="defective-kin-altruism" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="defective-kin-altruism"><span class="header-section-number">1</span> Defective kin altruism</h2>
<p>The classic selfish-gene kin-altruism model tells me I should value people according to their genetic relatedness to me, as measured by the probability <img src="https://latex.codecogs.com/png.latex?r"> that a random gene in them is a copy of the same gene in me. Assuming no inbreeding and a simple pedigree, <img src="https://latex.codecogs.com/png.latex?r"> is <img src="https://latex.codecogs.com/png.latex?1/2"> for full siblings or children, <img src="https://latex.codecogs.com/png.latex?1/4"> for half-sibs, <img src="https://latex.codecogs.com/png.latex?1/8"> for first cousins, and so on. “Costs” and “values” are measured in units of expected gene copies, a.k.a. <a href="../notebook/utility_fitness.html">fitness</a>. I should be willing to pay a cost <img src="https://latex.codecogs.com/png.latex?C"> in my own reproduction to give a benefit <img src="https://latex.codecogs.com/png.latex?B"> to a relative if and only if <img src="https://latex.codecogs.com/png.latex?rB%20%3E%20C"> <span class="citation" data-cites="Hamilton1964Genetical Hamilton1964Geneticala">(Hamilton 1964a, 1964b)</span>.</p>
<p>OK, let’s assume I am given <img src="https://latex.codecogs.com/png.latex?B"> and <img src="https://latex.codecogs.com/png.latex?C"> — can I get <img src="https://latex.codecogs.com/png.latex?r">? Given that rapid PCR tests are not available in the animal kingdom, the answer is generally no. Instead, assuming that I am solving for the merciless logic of kin selection, I need to guess <img src="https://latex.codecogs.com/png.latex?r"> from context cues, if I am to be an optimal evolutionary actor.</p>
<p>I suspect that solving this inference problem too precisely is not helpful, even by evolution’s own logic. Here I will unpick it and see if my suspicions pan out.</p>
<p>The problem (to help or not to help) is a classic <a href="../notebook/classification.html">classification</a> problem with all the annoyances that entails — false positives, false negatives, AUC, ROC curves, and so on, even for an ideal learner. Animals in the wild have it even worse than that, because they don’t get a labelled training or test set to calibrate on. In practice they use surrogates for <img src="https://latex.codecogs.com/png.latex?r"> — for example, under <em>phenotype matching</em>, an animal learns a template from its family (the charmingly named “armpit effect”) — and treats others as kin to the degree they match it <span class="citation" data-cites="Lacy1983Kin">(Lacy and Sherman 1983)</span>.</p>
<p>We can analyse the cue dynamics as a signal detection problem (Finally! My MSc is relevant again!). A classic seems to be Reeve’s <em>acceptance threshold</em> <span class="citation" data-cites="Reeve1989Evolution">(Reeve 1989)</span>. Given some correlate of <img src="https://latex.codecogs.com/png.latex?r">, call it <img src="https://latex.codecogs.com/png.latex?d">, accept anyone closer than <img src="https://latex.codecogs.com/png.latex?d%5E%7B*%7D"> as kin and help them; reject the rest.</p>
<p>Derivation: I have noisy <img src="https://latex.codecogs.com/png.latex?d">, so the most I can do is apply the rule in expectation: <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bhelp%7D%20%5Ciff%20%5Cmathbb%7BE%7D%5Br%20%5Cmid%20d%5D%5C,B%20%3E%20C%20%5Ciff%20%5Cmathbb%7BE%7D%5Br%20%5Cmid%20d%5D%20%3E%20C/B."> then <img src="https://latex.codecogs.com/png.latex?d%5E%7B*%7D"> is the cue value where <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5Br%5Cmid%20d%5D%5C,B"> crosses <img src="https://latex.codecogs.com/png.latex?C">. But <img src="https://latex.codecogs.com/png.latex?d%5E%7B*%7D"> is a cutoff on the cue, not on <img src="https://latex.codecogs.com/png.latex?r">, which is why on its face it does not look like <img src="https://latex.codecogs.com/png.latex?rB%3EC">.<sup>1</sup></p>
<p>Reeve runs a two-class classifier version of this. Sort the world into kin (relatedness <img src="https://latex.codecogs.com/png.latex?r_k">) and non-kin (<img src="https://latex.codecogs.com/png.latex?r%5Capprox%200">); observing the cue gives me a posterior <img src="https://latex.codecogs.com/png.latex?q%20=%20P(%5Ctext%7Bkin%7D%5Cmid%20d)">. The per-encounter payoffs, relative to doing nothing, are <img src="https://latex.codecogs.com/png.latex?r_k%20B%20-%20C"> for helping a relative, <img src="https://latex.codecogs.com/png.latex?-C"> for helping a stranger, and <img src="https://latex.codecogs.com/png.latex?0"> for sitting on my hands, so helping pays in expectation when <img src="https://latex.codecogs.com/png.latex?q%5C,r_k%20B%20-%20C%20%3E%200">: <img src="https://latex.codecogs.com/png.latex?q%20%3E%20%5Cfrac%7BC%7D%7Br_k%20B%7D,%20%5Cqquad%5Ctext%7Bequivalently%7D%5Cqquad%20%5Cunderbrace%7Bq%5C,r_k%7D_%7B%5Cmathbb%7BE%7D%5Br%5Cmid%20d%5D%7D%20%3E%20%5Cfrac%7BC%7D%7BB%7D."></p>
<p>Because the distributions overlap, every choice of <img src="https://latex.codecogs.com/png.latex?d%5E%7B*%7D"> buys one error with another — a permissive threshold accepts non-kin and wastes <img src="https://latex.codecogs.com/png.latex?C"> on them, a stringent one rejects kin and forgoes <img src="https://latex.codecogs.com/png.latex?r_kB-C"> for each one. That is, we face the standard <a href="../notebook/classification.html">classification trade-off</a> between false positives and false negatives, and the optimal <img src="https://latex.codecogs.com/png.latex?d%5E%7B*%7D"> is the one that maximises expected fitness.</p>
<p>Even in this simple setting, we need to consider base rates to choose <img src="https://latex.codecogs.com/png.latex?d%5E%7B*%7D">: the prior probability that a random individual it meets is kin, and the costs, to choose the optimal threshold.</p>
<p>When kin are common — inside the natal nest, say — and snubbing a relative costs more than feeding a stranger, the optimal rule tends to <em>permissive</em>: accept almost everyone and tolerate the acceptance errors. When kin are rare and altruism is expensive, the rule turns choosy.</p>
<p>Fitness-maximising recognisers are necessarily imperfect. Acceptance errors naturally arise from overlapping distributions and asymmetric payoffs. Selection pushes acceptance toward the threshold that pays best, which typically leaves a calibrated amount of error on the table.</p>
<p>Decision theory in signal detection is something of a big deal amongst evolutionary biologists <span class="citation" data-cites="Sherman1997Recognition Pfennig1995Kin">(Sherman, Reeve, and Pfennig 1997; Pfennig and Sherman 1995)</span>, and the acceptance-threshold model has since been carried well beyond kin, to e.g.&nbsp;host defences against brood parasites, anywhere an animal must sort a stream of individuals into accept and reject under uncertainty <span class="citation" data-cites="Suarez2020Signal Scharf2020Evolution">(Suarez et al. 2020; Scharf et al. 2020)</span>.</p>
</section>
<section id="evolution-should-invent-a-better-kin-detector" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="evolution-should-invent-a-better-kin-detector"><span class="header-section-number">2</span> Evolution should invent a better kin detector</h2>
<p>TBD</p>
</section>
<section id="evolution-of-ambiguous-signalling" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="evolution-of-ambiguous-signalling"><span class="header-section-number">3</span> Evolution of ambiguous signalling</h2>
<p>There is a whole mini-field in the evolution of ambiguous signalling and whether it might be an evolutionarily viable strategy <span class="citation" data-cites="Frohlich2025Rethinking Galeazzi2026Evolution Muhlenbernd2020Evolutionary OConnor2015Ambiguity Santana2014Ambiguity Smaldino2018Evolution">(Fröhlich, Jäger, and Achimova 2025; Galeazzi and Rich 2026; Mühlenbernd 2020; O’Connor 2015; Santana 2014; Smaldino, Flamson, and McElreath 2018)</span>.</p>
</section>
<section id="incoming" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="incoming"><span class="header-section-number">4</span> Incoming</h2>
<p>Related, converse: <a href="../notebook/common_knowledge.html">common knowledge</a> problems.</p>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Baker2020Emergent" class="csl-entry">
Baker. 2020. <span>“<a href="https://doi.org/10.48550/ARXIV.2011.05373">Emergent Reciprocity and Team Formation from Randomized Uncertain Social Preferences</a>.”</span> In.
</div>
<div id="ref-Case2005Avoiding" class="csl-entry">
Case, Andrews, Johnson, et al. 2005. <span>“<a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1175801">Avoiding Versus Seeking: The Relationship of Information Seeking to Avoidance, Blunting, Coping, Dissonance, and Related Concepts</a>.”</span> <em>Journal of the Medical Library Association</em>.
</div>
<div id="ref-Crozier1986GENETIC" class="csl-entry">
Crozier. 1986. <span>“<a href="https://doi.org/10.1111/j.1558-5646.1986.tb00578.x">Genetic Clonal Recognition Abilities in Marine Invertebrates Must Be Maintained by Selection for Something Else</a>.”</span> <em>Evolution</em>.
</div>
<div id="ref-DAmbrosio2025Manipulative" class="csl-entry">
D’Ambrosio. 2025. <span>“<a href="https://doi.org/10.1215/00318108-11873748">Manipulative Underspecification</a>.”</span> <em>Philosophical Review</em>.
</div>
<div id="ref-DeFreitas2019Common" class="csl-entry">
De Freitas, Thomas, DeScioli, et al. 2019. <span>“<a href="https://doi.org/10.1073/pnas.1905518116">Common Knowledge, Coordination, and Strategic Mentalizing in Human Social Life</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Frohlich2025Rethinking" class="csl-entry">
Fröhlich, Jäger, and Achimova. 2025. <span>“<a href="https://doi.org/10.1016/j.neubiorev.2025.106401">Rethinking Ambiguity Across Species</a>.”</span> <em>Neuroscience &amp; Biobehavioral Reviews</em>.
</div>
<div id="ref-Galeazzi2026Evolution" class="csl-entry">
Galeazzi, and Rich. 2026. <span>“<a href="https://doi.org/10.1017/S0266267125100394">The Evolution of Ambiguous Beliefs</a>.”</span> <em>Economics &amp; Philosophy</em>.
</div>
<div id="ref-Golman2017Information" class="csl-entry">
Golman, Hagmann, and Loewenstein. 2017. <span>“<a href="https://doi.org/10.1257/jel.20151245">Information Avoidance</a>.”</span> <em>Journal of Economic Literature</em>.
</div>
<div id="ref-Halpern2000Knowledge" class="csl-entry">
Halpern, and Moses. 1990. <span>“<a href="https://doi.org/10.48550/arXiv.cs/0006009">Knowledge and Common Knowledge in a Distributed Environment</a>.”</span> <em>Journal of the ACM (JACM)</em>.
</div>
<div id="ref-Hamilton1964Genetical" class="csl-entry">
Hamilton. 1964a. <span>“<a href="https://doi.org/10.1016/0022-5193(64)90038-4">The Genetical Evolution of Social Behaviour. I</a>.”</span> <em>Journal of Theoretical Biology</em>.
</div>
<div id="ref-Hamilton1964Geneticala" class="csl-entry">
———. 1964b. <span>“<a href="https://doi.org/10.1016/0022-5193(64)90039-6">The Genetical Evolution of Social Behaviour. II</a>.”</span> <em>Journal of Theoretical Biology</em>.
</div>
<div id="ref-Holman2013Croziers" class="csl-entry">
Holman, van Zweden, Linksvayer, et al. 2013. <span>“<a href="https://doi.org/10.1186/1471-2148-13-211">Crozier’s Paradox Revisited: Maintenance of Genetic Recognition Systems by Disassortative Mating</a>.”</span> <em>BMC Evolutionary Biology</em>.
</div>
<div id="ref-KleimanWeiner2025Evolving" class="csl-entry">
Kleiman-Weiner, Vientós, Rand, et al. 2025. <span>“<a href="https://doi.org/10.1073/pnas.2400993122">Evolving General Cooperation with a Bayesian Theory of Mind</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Lacy1983Kin" class="csl-entry">
Lacy, and Sherman. 1983. <span>“<a href="https://doi.org/10.1086/284078">Kin Recognition by Phenotype Matching</a>.”</span> <em>The American Naturalist</em>.
</div>
<div id="ref-March1978Bounded" class="csl-entry">
March. 1978. <span>“<a href="http://www.jstor.org/stable/3003600">Bounded Rationality, Ambiguity, and the Engineering of Choice</a>.”</span> <em>The Bell Journal of Economics</em>.
</div>
<div id="ref-Muhlenbernd2020Evolutionary" class="csl-entry">
Mühlenbernd. 2020. <span>“<a href="https://doi.org/10.1007/s11229-020-02826-6">Evolutionary Stability of Ambiguity in Context Signaling Games</a>.”</span> <em>Synthese</em>.
</div>
<div id="ref-OConnor2015Ambiguity" class="csl-entry">
O’Connor. 2015. <span>“<a href="https://doi.org/10.1086/679180">Ambiguity Is Kinda Good Sometimes</a>.”</span> <em>Philosophy of Science</em>.
</div>
<div id="ref-Pfennig1995Kin" class="csl-entry">
Pfennig, and Sherman. 1995. <span>“<a href="https://doi.org/10.1038/scientificamerican0695-98">Kin Recognition</a>.”</span> <em>Scientific American</em>.
</div>
<div id="ref-Reeve1989Evolution" class="csl-entry">
Reeve. 1989. <span>“<a href="https://doi.org/10.1086/284926">The Evolution of Conspecific Acceptance Thresholds</a>.”</span> <em>The American Naturalist</em>.
</div>
<div id="ref-Rousset2007CONSTRAINTS" class="csl-entry">
Rousset, and Roze. 2007. <span>“<a href="https://doi.org/10.1111/j.1558-5646.2007.00191.x">Constraints on the Origin and Maintenance of Genetic Kin Recognition</a>.”</span> <em>Evolution</em>.
</div>
<div id="ref-Santana2014Ambiguity" class="csl-entry">
Santana. 2014. <span>“<a href="https://doi.org/10.1086/676652">Ambiguity in Cooperative Signaling</a>.”</span> <em>Philosophy of Science</em>.
</div>
<div id="ref-Scharf2020Evolution" class="csl-entry">
Scharf, Suarez, Reeve, et al. 2020. <span>“<a href="https://doi.org/10.1098/rstb.2019.0475">The Evolution of Conspecific Acceptance Threshold Models</a>.”</span> <em>Philosophical Transactions of the Royal Society B: Biological Sciences</em>.
</div>
<div id="ref-Scott2022Multiple" class="csl-entry">
Scott, Grafen, and West. 2022. <span>“<a href="https://doi.org/10.1038/s41467-022-31545-4">Multiple Social Encounters Can Eliminate Crozier’s Paradox and Stabilise Genetic Kin Recognition</a>.”</span> <em>Nature Communications</em>.
</div>
<div id="ref-Scott2023Host" class="csl-entry">
———. 2023. <span>“<a href="https://doi.org/10.1073/pnas.2220761120">Host–Parasite Coevolution and the Stability of Genetic Kin Recognition</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Sherman1997Recognition" class="csl-entry">
Sherman, Reeve, and Pfennig. 1997. <span>“Recognition Systems.”</span> In <em>Behavioural Ecology: An Evolutionary Approach</em>.
</div>
<div id="ref-Shteynberg2023Theory" class="csl-entry">
Shteynberg, Hirsh, Wolf, et al. 2023. <span>“<a href="https://doi.org/10.1016/j.tics.2023.06.009">Theory of Collective Mind</a>.”</span> <em>Trends in Cognitive Sciences</em>.
</div>
<div id="ref-Smaldino2018Evolution" class="csl-entry">
Smaldino, Flamson, and McElreath. 2018. <span>“<a href="https://doi.org/10.1038/s41598-018-22926-1">The Evolution of Covert Signaling</a>.”</span> <em>Scientific Reports</em>.
</div>
<div id="ref-Suarez2020Signal" class="csl-entry">
Suarez, Scharf, Reeve, et al. 2020. <span>“<a href="https://doi.org/10.1098/rstb.2019.0464">Signal Detection, Acceptance Thresholds and the Evolution of Animal Recognition Systems</a>.”</span> <em>Philosophical Transactions of the Royal Society B: Biological Sciences</em>.
</div>
<div id="ref-Thomas2014Psychology" class="csl-entry">
Thomas, DeScioli, Haque, et al. 2014. <span>“<a href="https://doi.org/10.1037/a0037037">The Psychology of Coordination and Common Knowledge.</a>”</span> <em>Journal of Personality and Social Psychology</em>.
</div>
<div id="ref-Wang2026NoiseInduced" class="csl-entry">
Wang, Su, Wang, et al. 2026. <span>“<a href="https://doi.org/10.1103/3yby-qq2n">Noise-Induced Replicator Dynamics in Evolutionary Games</a>.”</span> <em>Physical Review Letters</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Formally we are assuming some extra stuff, like the cue’s likelihood ratio being monotone in <img src="https://latex.codecogs.com/png.latex?d">.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>agents</category>
  <category>AI safety</category>
  <category>bounded compute</category>
  <category>collective knowledge</category>
  <category>computers are awful together</category>
  <category>distributed</category>
  <category>economics</category>
  <category>edge computing</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>machine learning</category>
  <category>networks</category>
  <guid>https://danmackinlay.name/notebook/cooperation_uncertainty.html</guid>
  <pubDate>Wed, 27 May 2026 12:13:57 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/mrs_brown_figure.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>AI usage policy</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/ai_usage_policy.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p><a href="https://dynomight.net/blink/?ref=danmackinlay.name">Dynomight wrote</a> a disclosure document about how they use AI. I already had one, but the race dynamics of blog social signalling suggest I might want to hoist that content from where it hid amongst the <a href="../about_site.html">other technical details of this site</a> to some specialized page that is <em>All About AI</em>. I… don’t find it particularly interesting to be an AI straight-edge. I use AI for all kinds of things, and am constantly auditioning new ways that it can be useful, as well as constantly testing whether it is actually useful. That is more-or-less <a href="../about_dan.html">literally my job</a>.</p>
<div class="callout callout-style-simple callout-none no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">None</span>tl;dr
</div>
</div>
<div class="callout-body-container callout-body">
<p>As a full-time AI researcher, I use the <em>shit</em> out of generative AI, and I have done so since 2024. Mostly I use it to automate various tedious maintenance tasks (typo/grammar review, generating search indexes, keeping the front page ranking tidy). The text I prefer to draft myself but I make exceptions for certain uses — most notably <em>literature reviews outside my discipline</em> — where AI is very fucking good at getting me up to speed on transdisciplinary topics. I <em>label such posts</em> with a 🤖🤖🤖 to flag content that is especially robotty.</p>
</div>
</div>
<div class="callout callout-style-simple callout-caution">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>I expect this page to age poorly, and be seen as <em>unutterably precious</em> really rather soon.</p>
</div>
</div>
</div>
<div id="fig-grow-on-trees" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-grow-on-trees-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/grow_on_trees_RP-P-OB-85.761.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/grow_on_trees_RP-P-OB-85.761.avif" id="fig-grow-on-trees" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-grow-on-trees-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>I use AI to process the articles on this site in various ways.</p>
<ol type="1">
<li><p><strong>Copy-editing.</strong> A lightweight language model reviews my sloppy, typo-riddled text, and removes the typos. It’s basically a mildly aggressive spellchecker. I manually accept or reject all such updates, albeit with varying levels of scrutiny.</p></li>
<li><p><strong>Summarising.</strong> Those little “Wherein …” summaries at the top of the post are generated by a model coaxed into imitating Victorian novels.</p></li>
<li><p><strong>Indexing</strong>. An embedding model digests each post and produces a vector representation that is stored in a vector database. <a href="../notebook/ai_search.html#similarity">This generates the “suspiciously similar” recommendations</a> for posts and the 3d blog map on the <a href="../index.html">home page</a>.</p></li>
<li><p><strong>Scoring.</strong> An LLM rates each post for <em>quality</em> (0–10), assessing how interesting, useful and/or coherent it is. These scores are not displayed directly to you, but influence the site’s front page ordering, since they feed into the “frecency” (freshness-weighted quality) score that I use for default page ordering: <img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bfrecency%7D%20=%20Q2%5E%7B-t/H%7D%0A"> where <em>Q</em> is quality (0–10), <em>t</em> is age in days, and <em>H</em> is a half-life. This ensures the front page favours recent writing, but a really spicy, good update of an older post can sneak ahead of newer, scruffy ones.</p></li>
<li><p><strong>Literature reviews.</strong> The main place I get AI to draft actual text. I use AI to read and summarise papers outside my discipline, and to generate a first draft of a literature review. <a href="../about_site.html#ratings">I label these with a 🤖🤖🤖 in the header</a> to flag that they heavy with AI-generated content.</p>
<p>Lit reviews are especially useful when I’m trying to get up to speed on a new topic, and the existing work is 💩💩💩. Where I am especially keen to make sure no one mistakes it for my own writing, I additionally label it with a callout block.</p>
<p>For example, the <a href="../notebook/ageing.html">ageing</a> literature review has substantial content generated by AI. This is an area where the default online advice is politicised and compromised by snake-oil merchants, so a carefully AI-generated review is better than the default, which is whatever health influencers are getting kickbacks for this month.</p>
<p>Lest I be misunderstood, the AI-generated content is not at all <em>instant</em>. I do not one-shot a literature review and then serve it up to you, for that would be a waste of time at the current state of the technology. An AI-assisted literature review takes me ~two days to assemble, and I spend substantial time fact-checking, revising, telling my preferred criteria, and otherwise shaping and verifying the content. Nonetheless, it is still not something I would call my own because the prose sucks and I have not internalised the area well enough to make really good calls about omissions and fuckups.</p>
<p>An alternative would be to spend a month on each lit review I create to scope out a new research area. That would be higher quality, but it would also restrict the number of areas I could explore and probably be too hi-fi a process for the quick and dirty search I need. I could alternatively decline to share such lit reviews, but since they are still somewhat effortful, expensive, useful artefacts, this seems like a waste.</p></li>
<li><p><strong>Automation of tedious RA tasks:</strong> I use AI to automate various maintenance tasks on this site, including stuff like renaming and re-organising blog posts, dividing them into sub-pages, and searching the internet for missing references.</p></li>
<li><p><strong>Self tutoring:</strong> When I want to learn a new topic area and it is important that I understand it well, I use AI to do <a href="../notebook/learning_with_ai.html">socratic tutoring</a> to rapidly get up to speed on new concepts, and to generate exercises to test my understanding. You probably observe this only indirectly, by the reduction in the number of stupid things I say. You’re welcome.</p></li>
<li><p><strong>Feedback:</strong> I regularly use AI to provide feedback on my arguments, mathematics, and code. Once again, you only see this indirectly.</p></li>
<li><p><strong>All Kinds of Zany Experiments.</strong> <a href="../notebook/automatic_coding.html">Code generation</a>, <a href="../notebook/automatic_maths.html">exploratory theorem proving</a>, <a href="../notebook/ai_agents_applied.html">general-purpose assistants</a>, and <a href="../notebook/agent_harness_design.html">agent harness design</a>, <a href="../notebook/fine_tuning_danbot.html">style transfer</a>, <a href="../notebook/aus_sovereign_llm.html">sovereign compute</a> are all experiments in using AI to do things that I do not have enough hours in my day to do without augmentation. Some of those end up touching my blog text in incidental but minor ways. I trust there will be more of these and better ones as time goes on.</p></li>
</ol>
<p>My rule-of-thumb is that if an AI tool might help me provide more value to the world than would the alternative, it’s worth trying it out. What exactly that means is hard to say, but I won’t find out without doing the experiment.</p>
<p>You are welcome to disagree with me about what “value” means and where the line is. You are also welcome to point out any mistakes I make in my use of AI, and to suggest better ways of using it.</p>
<p>Oh, I publish the code for my more generalizable experiments online.</p>
<p>You might enjoy</p>
<ul>
<li><a href="https://github.com/danmackinlay/Filibotster?ref=danmackinlay.name">danmackinlay/Filibotster</a>: live detection of AI-generated speeches. Extra nice when politicians do it.</li>
<li><a href="https://github.com/danmackinlay/stylebot?ref=danmackinlay.name">danmackinlay/stylebot</a>: <a href="../notebook/fine_tuning_danbot.html">prose style transfer for Dans</a></li>
<li><a href="https://github.com/danmackinlay/mathx?ref=danmackinlay.name">danmackinlay/mathx</a>: miniature math proof assistant</li>
<li><a href="https://github.com/danmackinlay/pdf-ingest-skill?ref=danmackinlay.name">danmackinlay/pdf-ingest-skill</a>: Agent skill to route PDFs to <a href="../notebook/pdf_ingestion.html">the right local converter</a> (markitdown / Docling / marker / MinerU) with token- and memory-budget discipline</li>
<li><a href="https://github.com/danmackinlay/gemini_deep_research_client?ref=danmackinlay.name">danmackinlay/gemini\_deep\_research\_client</a>: Get the Google Gemini deep research pay-as-you-go rather than by unaffordable subscription, and preen the results to have better provenance tracking and iteration.</li>
<li>I’m kinda hopeful that I will find some good angles in <a href="../notebook/civic_tech.html">Civic tech</a> also, but this is WIP</li>
</ul>
<p>I think it might be worthwhile mentioning where I <em>don’t</em> use AI, and why.</p>
<ol type="1">
<li>I aim not to use AI deceptively, without labelling the content as AI-assisted.</li>
<li>I aim not to use <a href="../notebook/complementary_competitive_tech.html">it in lieu of ever exercising those of my own skills</a> that I need to keep sharp.</li>
<li>I try to avoid it becoming an <a href="../notebook/addiction.html">addiction</a>.</li>
</ol>



 ]]></description>
  <category>DIY</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>language</category>
  <category>NLP</category>
  <category>premature optimization</category>
  <category>slop</category>
  <category>stringology</category>
  <category>UI</category>
  <category>workflow</category>
  <category>writing</category>
  <guid>https://danmackinlay.name/notebook/ai_usage_policy.html</guid>
  <pubDate>Tue, 26 May 2026 03:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/grow_on_trees_RP-P-OB-85.761.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Fine-tuning danbot</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/fine_tuning_danbot.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-kitsugi" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-kitsugi-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/kitsugi_RP-P-1987-8.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/kitsugi_RP-P-1987-8.avif" id="fig-kitsugi" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-kitsugi-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Claude is a bad ghostwriter for me. Not unintelligible, not slow, not even particularly inaccurate — just wrong in the way LinkedIn posts are wrong: smooth, hedging, structurally signposted, breathlessly enthusiastic in flat places. I have tried the standard ladder of prompt-side fixes: a hand-tuned <code>/dan-voice</code> skill with a banned-words list and a structural slop catalogue, a <a href="https://vale.sh?ref=danmackinlay.name">Vale</a> ruleset as a mechanical safety net, in-context examples of my own prose stuffed into the system prompt as few-shot exemplars, multiple rounds of “rewrite this in Dan’s voice” against a frontier model. The output comes out marginally more Dan-shaped each iteration and still smells like a press release. There is a floor on how much taste we can install via prompt engineering, and currently I am still trapped, according to this metaphor, in what must logically be one of those taste basements which we escape from by <em>installing</em> things.</p>
<p>No matter what I do, I cannot dissuade Claude from emitting contextless snide technical <a href="../notebook/nerdview.html">nerdview</a> bullshit like mentioning its “Vale rulesets”. Nor can I wean it from poorly chosen mixed metaphors like “standard ladder of prompt-side fixes” which, through some lesser-known ladder mechanism, extrude “Dan-shaped” chunks.</p>
<p>Look, I do not claim my style is <em>perfect</em>. But I do claim that</p>
<ol type="1">
<li>it is mine, and</li>
<li>it is low on grating LLM quasi-metaphors like “this is the growing seam we can hand off”, and</li>
<li>when I write something on Monday, reading it back on Friday, I discern my own intent without excessive strain.</li>
</ol>
<p>All these are <em>untrue</em> of Claude’s output.</p>
<p>How to respond? The response of a <em>writer</em> to such stimulus is to write erudite essays about how bad is teh slopz. The goal of a <em>scientist</em> is to understand. But apparently I have a touch of <em>tinkerer</em> in my soul, for I long to fix it.</p>
<p>If, <em>if</em> rather than pleading for it via prompt, I could train a small specialist LLM to bake something like <em>tolerable style</em> into weights, every AI draft could pass through that and come out sounding like me, or at least, hopefully not substantially worse despite having been interfered with by an LLM. This would, at least, aggravate me less. Maybe it would be better for the world?</p>
<p>So! We find ourselves in the realm of <em>Fine-tuning danbot!</em></p>
<section id="did-you-try-few-shot-prompting" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="did-you-try-few-shot-prompting"><span class="header-section-number">1</span> “Did you try few-shot prompting?”</h2>
<p>Of course I fucking did. Go home. I tried a lot of few-shot and then many-shot prompting with examples of my prose, and it did not help at all. I do not understand people who say this works for them. Maybe their goals are different? Maybe they have read less AI text than I have?</p>
<p>Anyway, dear internet, please stop telling me to try few-shot prompting. It was wholly futile and left me <em>trapped in a taste basement without an installer</em>, as the kids say.</p>
</section>
<section id="goals" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="goals"><span class="header-section-number">2</span> Goals</h2>
<p><a href="../notebook/ai_usage_policy.html">My policy</a> is not to abstain from AI. My policy is, rather, to optimize my productivity given my own time and attention, and to use AI as part of that, wherever it makes sense. What exactly that means is hard to say in detail, but I more easily find out by doing the experiment. Sometimes it is useful to sketch stuff out with AI, or do background research, or make tedious and complicated edits, or whatever.</p>
<p>It is not useful to pretend that AI is a human, nor, specifically, that it is me. It does not, however, follow that AI outputs should be horrible to read.</p>
<p>With that in mind, goal statement:</p>
<ul>
<li>Less onerous clean-up of AI edits</li>
<li>More comprehensible prose in general</li>
<li>cultivating some kind of deeper understanding of how style in AI even works</li>
</ul>
</section>
<section id="non-goals" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="non-goals"><span class="header-section-number">3</span> Non-goals</h2>
<ul>
<li>Full Dan impersonation</li>
<li>Instant production of my deep authentic voice</li>
<li>Evading slop detectors</li>
</ul>
</section>
<section id="tldr" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="tldr"><span class="header-section-number">4</span> tl;dr</h2>
<p>It works! Ish! If you read to the end of this, you will find all the stupid steps for how to make a danifier-in-a-box. Danbot works, in the narrow sense that it <em>functions</em>, but the quality is not yet satisfying. I documented my labours in a <a href="https://github.com/danmackinlay/stylebot?ref=danmackinlay.name">working github code repo</a> that you can try, and I have experimented with running irritating AI slop through it.</p>
<p>Metrics: across forty held-out validation samples, the styler closes about half the gap, on the metrics I have, between me and slop. Which is to say, it is quantifiably superior. Not shown: comparison with base case of few-shot prompting.</p>
<p>Qualitatively, the results re more meagre. It is doing <em>something</em>. Surprisingly, it seems to me to be perversely good at a non-goal. It can make AI sound less like AI, but it is not yet good at making AI sound like me. I had assumed the second was the easier task because it is more specific. Appaz not.</p>
<p>Also, I made some regrettable design choices. Notably, right now it works at the scale of a paragraph rather than a section, which I suspect is why the rewrites are not super coherent.</p>
<p>Anyway, it is still a work in progress, and has taken not so much human time while achieving a lot, so I am opptimistic about the approach. Many possibilities for improvement are on the table.</p>
</section>
<section id="how-it-works" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="how-it-works"><span class="header-section-number">5</span> How it works</h2>
<p>The code consists of two parts. “Stuff that <em>you</em> might want to use” and “inside-baseball nonsense that only <em>I</em> will find useful”. <a href="https://github.com/danmackinlay/stylebot?ref=danmackinlay.name">danmackinlay/stylebot</a> is the former. It should be relatively generic. Includes: prose extraction, chunk hygiene for technical markdown, pair synthesis, training, eval scoring. I am aware that none of those words make sense without context. Let me try again: It includes <em>generic dataset generation and training tools</em> plus some <em>useful inference utils and diagnostics</em>.</p>
<p>The other, me-specific part of the code lives in this blog and specializes on various idiosyncratic design choices I made for it (filtering out the more hair-raisingly incoherent posts from the training data, etc).</p>
<p>This divide seems to be a natural separation of responsibilities. If you disagree, file a pull request.</p>
<p>The training corpus was seeded with a bunch of rewrites I made to actual AI slop (~400), giving me (before/AI, after/Dan) pairs. I then synthesized a bunch more by creating synthetic “reverse-edits” (~4500). For each of these, I took a paragraph of my published prose and asked an LLM to do its best to improve it, giving me (before/Dan, after/AI) pairs, which I swapped into (after/AI, before/Dan) pairs. Genius! Infinite data!<sup>1</sup> The captured pairs accumulate as a side effect of ordinary editing, and I generate the others from time to time.</p>
<p>There is fancy scissor work in the test/training splits. I split the data over <em>posts</em> rather than paragraphs, so that if there are many edits from one page, we do not leak shared topic information across splits. There are three splits: validation, LLM training, and detector training.</p>
<p>Oh right, the detector. I didn’t mention that but it ended up being very useful. The detector is a two-class paragraph classifier that labels paragraphs as being more AI or more Dan (these being the only two relevant categories in the world; Sorry, all other stylists). It emits P(slop) per paragraph. It runs locally (embedding models are cheap), which is super convenient: everything downstream can afford to call it profligately. This also gives me a (noisy) way of estimating the output quality when I am messing around with it.</p>
<p>“Training” is specifically supervised fine-tuning of a LoRA. Currently the base model is Qwen3.5-9B with thinking disabled, which costs a minuscule $2 per (training) run. Each run writes a manifest with all the metadata about how I made this training corpus — split, hyperparameters, all that nonsense. I maintain several fine-tunes and “promote” a favoured one from time to time. It is not always the latest one — occasionally more recent ones suck.</p>
<p>Document chunking is complicated on this blog. I fill the text with code, mathematical markup, etc. So there is a custom text extractor and chunker that I use for data generation, training, and inference.</p>
<p>At inference time (i.e.&nbsp;when I want to rewrite a paragraph), I run the chunker, and then for each chunk I sample several candidate rewrites (4 seems fine). I filter out any defective candidates that have deleted or mangled URLs. Any that remain are ranked by how much the detector liked them (or at least, how much the detector thought they resembled me, which might in fact be exquisite torture for the poor detector, idk). The original text is included in the alternatives in case all the candidate rewrites totally suck.</p>
<p>All this is wrapped up in a small VS Code extension that</p>
<ol type="1">
<li>tints each paragraph with a colour denoting P(slop) and</li>
<li>offers to rewrite the paragraph under the cursor.</li>
</ol>
<p>Whichever I pick — including “keep what I had” — is logged as a preference record so I can improve future iterations, RLHF-style.</p>
<section id="where-to-train-and-serve" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="where-to-train-and-serve"><span class="header-section-number">5.1</span> Where to train and serve</h3>
<p>This is so out of order. I explained how I use the model, but not <em>how</em> I train it. I train on <a href="https://thinkingmachines.ai/tinker/?ref=danmackinlay.name">Tinker</a>.</p>
<p>Tinker is Thinking Machines’ managed-training API: I write the idiosyncratic parts of my training loop in Python, and they wrap it in an ingenious training pipeline on fast hardware. Tinker is remarkably easy, cheap and thoughtfully designed. Instead of just giving me a <code>train()</code> loop, it exposes <code>forward_backward</code>, <code>optim_step</code>, and <code>sample</code> as primitives, which gets me a long way toward a custom training loop without having to manage the hardware — which, TBH, I am not qualified to do and would totally have blown the time budget appropriate for this spare-time papermill-tilting project. I don’t need the advanced features for the vanilla SFT, but it is nice to know I could do preference fine-tuning via DPO later.</p>
<p>Also, they will serve the model for me afterwards at inference time, which is convenient. Sampling is billed per-token, with no deployment to forget about and no idle cost, which suits a model I invoke a few dozen times a week.</p>
<p>Other alternatives: <a href="https://www.together.ai/pricing?ref=danmackinlay.name">Together</a> trains a LoRA cheaply and makes it <a href="https://docs.together.ai/reference/get-finetune-download?ref=danmackinlay.name">easy to extract</a>, but hosts a fine-tune only as a <a href="https://docs.together.ai/docs/deploying-a-fine-tuned-model?ref=danmackinlay.name">dedicated always-on endpoint</a>, which prices a rarely-run model out of existence.<sup>2</sup> Although if there <em>were</em> massive demand for constantly-available danbot, that would be hilarious and I would consider it.</p>
<p>When I want the model off the cloud entirely, tinker-cookbook’s weight exporter merges the PEFT adapter back into the base weights, <code>mlx_lm.convert</code> quantizes that to 4-bit MLX, and <code>vllm-mlx</code> serves the result <a href="../notebook/local_llm_mac.html">locally</a> so <code>dan-style run --backend local</code> can point there instead of Tinker — keyless and offline.</p>
</section>
</section>
<section id="scoring-dan-ness-directly" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="scoring-dan-ness-directly"><span class="header-section-number">6</span> Measuring dan-ness</h2>
<p>As presaged, everything is easier if I can get some kind of signal of whether text sounds like me or not. Even a weak signal helps, because this is neural network land and we are used to sifting information from noisy signals. I settled on four imperfect signals, but hopefully <em>differently</em> imperfect, like a buddy-cop film’s dramatic leads.</p>
<ul>
<li><a href="https://vale.sh?ref=danmackinlay.name">Vale</a>, a prose linter, catches mechanical slop — banned words, colon headings, and other grammar choices (“load-bearing”, etc.)</li>
<li>a few-shot LLM judge assesses whether the output resembles other things I have written on a 1–5 scale (“here are some samples of Dan’s writing, rate this new example on a 1–5 scale for how much it resembles Dan’s style”)</li>
<li>that trained detector I mentioned earlier estimates P(slop) directly</li>
<li>I eyeball the output myself</li>
</ul>
<p>An obvious fifth option is a general-purpose AI-text detector, but a moment of introspection reveals that to be a dumb idea.<sup>3</sup> Recall that evading slop-detection is a non-goal, so it is not a helpful signal for that. In any case, a lot of things are not slop but also not Dan:</p>
<blockquote class="blockquote">
<p>In the beginning was the Word, and the Word was with God, and the Word was God.</p>
</blockquote>
<p>There is prior art on that phrase.</p>
<p>The trained statistical classifier is a signal that is more about <em>me</em> than about humans in general. I am not sure if I need to revisit that design choice, but it is working well enough for now.</p>
<p>Nitty-gritty: It is <a href="../notebook/classification.html">a logistic head</a> over an <a href="../notebook/nn_vector_embedding.html">embedding</a> of the (slop, Dan) pairs I already have, which was made easy by using an appropriate embedding. I am familiar with <a href="../notebook/ai_search.html"><em>content</em> embedders</a>, and use those all the time. Such semantic embedders do capture style a little <span class="citation" data-cites="Icard2025Embedding">(Icard et al. 2025)</span>, but it is not their strong suit. <em>Style</em> embeddings are their own literature <span class="citation" data-cites="Wegmann2022Same">(Wegmann, Schraagen, and Nguyen 2022)</span> and they do way better at capturing … well, you can guess. StyleDistance <span class="citation" data-cites="Patel2025StyleDistance">(Patel et al. 2025)</span> is one of those, trained on a synthetic corpus generated in a way very reminiscent of this blog, which suggested it might transfer here. It does. A logistic head on StyleDistance beat every other embedder I auditioned, thoroughly enough that I have not bothered fine-tuning my own style encoder. cf. LUAR <span class="citation" data-cites="RiveraSoto2021Learning">(Rivera-Soto et al. 2021)</span>, which attempts <em>authorship</em> classification rather than style. These might also work as generic slop detectors, FWIW — <span class="citation" data-cites="Soto2024FewShot">Soto et al. (2024)</span> show that style vectors separate machine text from human text few-shot.</p>
<p>Anyway, P(slop) is super useful. The pipeline uses it for ranking, QA, and hints in the editor.</p>
<section id="judge-versus-classifier" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="judge-versus-classifier"><span class="header-section-number">6.1</span> Judge versus classifier</h3>
<p>Having both a classifier and an LLM judge invites the question of whether they measure the same thing. On a stratified subset of 180 pairs scored by both, the rank correlation between them is <img src="https://latex.codecogs.com/png.latex?%5Ctilde%200.1">. Which is to say, near enough to independent. <em>Which is to say</em>, when we think about it, if one is perfect then the other is defective. In fact, both of them are defective, but in different ways, which is kinda what we want.</p>
<p>The judge is annoyingly decisive in absolute terms — it scores my prose 4.2 out of 5 on average against the slop’s 2.2 (which is incredible actually, I’m glad I let Claude talk me into trying that out). Whereas the trained detector is pretty rubbish in absolute terms, but <em>way better</em> at relative ranking. In a (dan, slop) pair, the slop is almost always ranked sloppier. The few-shot judge sucks at this: within pairs it seems to mostly measure how much of the original text survives the rewrite.</p>
<p>The classifier does much better at answering the question of whether a <em>transformation</em> itself is sloppy. Neither dominates, so in practice I use both.</p>
<div id="5b932f6e" class="cell" data-fig-format="svg" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> livingthing.matplotlib_style <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> set_livingthing_style, reset_default_style</span>
<span id="cb1-3"></span>
<span id="cb1-4">set_livingthing_style()</span>
<span id="cb1-5"></span>
<span id="cb1-6">SIGNALS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># model, classifier rank, judge rank, fraction of my text surviving</span></span>
<span id="cb1-7">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gemma-4-26b"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.69</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.56</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.66</span>),</span>
<span id="cb1-8">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-32b"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.47</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.77</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span>),</span>
<span id="cb1-9">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-4.6"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.80</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.73</span>),</span>
<span id="cb1-10">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nemotron-3-nano"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.37</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.53</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.56</span>),</span>
<span id="cb1-11">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-8b"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.33</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.50</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.64</span>),</span>
<span id="cb1-12">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-5.4-nano"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.36</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.31</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.77</span>),</span>
<span id="cb1-13">]</span>
<span id="cb1-14"></span>
<span id="cb1-15">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.2</span>))</span>
<span id="cb1-16">ax.plot([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>], [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#999"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>)</span>
<span id="cb1-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, det, judge, copied <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> SIGNALS:</span>
<span id="cb1-18">    ax.scatter(det, judge, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> copied <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">320</span>, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.65</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#36c"</span>)</span>
<span id="cb1-19">    ax.annotate(name, (det, judge), textcoords<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"offset points"</span>,</span>
<span id="cb1-20">                xytext<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>), fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb1-21">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ranked slop-like by the classifier →"</span>)</span>
<span id="cb1-22">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ranked slop-like by the judge →"</span>)</span>
<span id="cb1-23">ax.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Where the two signals part company"</span>)</span>
<span id="cb1-24">fig.tight_layout()</span>
<span id="cb1-25">plt.show()</span>
<span id="cb1-26">reset_default_style()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="fine_tuning_danbot_files/figure-html/cell-2-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Six slop sources ranked by each signal, on 180 pairs scored by both. Points off the diagonal are models the two instruments disagree about; marker area is how much of my original text survived the rewrite."><img src="https://danmackinlay.name/notebook/fine_tuning_danbot_files/figure-html/cell-2-output-1.png" width="596" height="487" alt="Six slop sources ranked by each signal, on 180 pairs scored by both. Points off the diagonal are models the two instruments disagree about; marker area is how much of my original text survived the rewrite." class="figure-img"></a></p>
<figcaption>Six slop sources ranked by each signal, on 180 pairs scored by both. Points off the diagonal are models the two instruments disagree about; marker area is how much of my original text survived the rewrite.</figcaption>
</figure>
</div>
</div>
</div>
<p>Since each model emphasizes different aspects of the text (e.g.&nbsp;Claude Sonnet rewrites politely and preserves most of my original wording, while Qwen3-32B condenses aggressively and changes the register), I add them together with a bit of scaling. I also eyeballed the pairs where they have the greatest discrepancy to make sure it is sane, which it is not always.</p>
<p>About 19% of the synthetic pairs were “rewritten” to be 85% the same. These seem to be not super useful for training a detector, so I mark those and make sure they are <em>not</em> used for that. They stay in the styling model dataset, though, where they teach it to leave alone the prose that is already fine.</p>
</section>
</section>
<section id="whose-slop" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="whose-slop"><span class="header-section-number">7</span> Whose slop?</h2>
<p>I have about 1.6M words of prose on this site that I wrote with my own hands, and <a href="../notebook/ai_usage_policy.html">a few AI-heavy ones are marked</a>, so the target side of the corpus comes free. The source side is the design problem: whose slop do we train against?</p>
<p>Slop seems to come in two layers. There’s a deep humus under-layer — throat-clearing, “it’s worth noting”, the rule-of-three, “<a href="https://www.reddit.com/r/LocalLLaMA/comments/1lv2t7n/not_x_but_y_slop_leaderboard/?rdt=62700&amp;ref=danmackinlay.name">It’s not X; it’s Y</a>”, signposting, <strong>marking key phrases in bold</strong>. It seems to me that every RLHF’d model produces that. Atop this layer is the litter of model-specific tics: GPT’s “delve” and “tapestry”, Claude’s peculiar hedging cadence, and so on. <span class="citation" data-cites="Attar2026Systematic">Attar et al. (2026)</span> study 284 interpretable linguistic features across 27 LLMs and 10 text domains, and argue that most signals are model- or domain-specific. This suggests multi-source training might be the way to go in order to learn to undo them all.</p>
<p>Even though I use Claude a lot, this makes sense. Even Claude isn’t one thing, but rather a family of models and versions, and I use others besides. Let us understand the goal for this styler to be a <em>Dan-ifier</em> rather than a de-Claude-ifier: rewrite the input as I’d write it, whatever the source. <span class="citation" data-cites="Soto2025Attacks">Soto, Chen, and Andrews (2025)</span> argues that this is in fact a smart move, as mimicking a specific human author’s style is a strong method for removing obvious AI tics.</p>
<p>There is also the matter of the base model I will use to do the styling. I am training a small <a href="../notebook/local_llm_mac.html">open-source model</a>, and <span class="citation" data-cites="Antoun2023Text">Antoun, Sagot, and Seddah (2023)</span> argue that small open models have the strongest stylistic signatures — so some of the slop had better come from my own utility base-model so that I don’t rewrite Claude prose into Qwen prose instead of Dan prose.</p>
<p><strong>tl;dr</strong> I generate training slop from a plural set of LLMs.</p>
<p>There is at least one example of someone else trying this in the wild. <span class="citation" data-cites="Paneru2026Please">Paneru (2026)</span> build a 25,140-pair AI→human corpus and fine-tune BART and Mistral-7B on it, reasoning that using two generators rather than one reduces the risk of learning to undo one model’s idiosyncratic habits instead of AI-style writing more broadly. They also target multiple human-target domains for the same reason, which I can’t be bothered doing, so I hope that part is not important. They don’t run a single-source vs.&nbsp;multi-source ablation, so the value of this multiple-model approach is unknown. Maybe I can publish that ablation if I find time? <span class="citation" data-cites="Paneru2026Please">Paneru (2026)</span> also observe some interesting failure modes — for example, we can learn the correct style transform but <em>overcorrect</em>, which looks great on metrics but probably not on human inspection.</p>
<p>That said, maybe we’re ready for <em>ULTRADAN</em>?</p>
</section>
<section id="lessons" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="lessons"><span class="header-section-number">8</span> Lessons</h2>
<p>I am usually less wordy than the AI, so part of what the styler learns is “delete stuff”.</p>
<p>An early run was effective at sounding more like me, but also at deleting “boring stuff” like URLs and citations, which is a little bit too real as I also despise boring stuff. I purged the data set of any pairs where the length ratio between AI and me was too extreme (&lt;3:1 or &gt;10:1), and that helped, but there was some dataset fine-tuning needed to grind that out.</p>
<p>This is likely a hazard of style-transfer objectives rather than a defect in any particular run: the cheapest way to “sound less verbose” is just to delete stuff. Can I separate these in a more principled way? TBD.</p>
<p>At the moment I mitigate this with sanity checks at inference time. Any sample that drops a link, URL or citation key present in the input is disqualified. This works fine.</p>
<p>It works fine in at inference time, anyway. I also had a lot of link <em>creation</em> in my synthetic training data it turns out since even the un-fine-tuned generator will helpfully delete links sometimes. This results in training data that show the model <a href="https://github.com/danmackinlay/stylebot/issues/1?ref=danmackinlay.name">to fabricate links, which is not what I want.</a>.</p>
<p><strong>Data hygiene was most of the work.</strong> The dramatic failures were all data failures: one generator returning my paragraphs near-verbatim, another inflating them 4-7x, which probably teaches too-extreme compression. It is worth cross-checking this by looking at the actual data. Some of the models did bonkers weird stuff.</p>
<p>To make sure I can control for these issues and future systematic errors, I record all the generation parameters, and also some sanity-check metrics (copy-similarity, length ratio). Any combinations that I later discover to be defective I can then delete. Since setting that up, I have had to cull <del>two</del> three+ more model combinations.</p>
<p><strong>Be careful with temperature.</strong> Low-temperature model sampling was useless for synthetic data generation: the model just copies the input, which is a waste of time. High temperatures are also useful for sampling at inference time — I use 0.7. Low temperatures are useful though for judging, so I set that to 0.3.</p>
<p><strong>The boring parts cost the most.</strong> A full working day (for my coding assistant) went to harmonising local LLM serving and remote, and dealing with bugs in how the model configs were handled. All of it is now baked into the export script, though, so we don’t need to worry about it.</p>
</section>
<section id="next-steps" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="next-steps"><span class="header-section-number">9</span> What next</h2>
<section id="omg-i-have-more-than-one-style-wat" class="level3" data-number="9.1">
<h3 data-number="9.1" class="anchored" data-anchor-id="omg-i-have-more-than-one-style-wat"><span class="header-section-number">9.1</span> OMG I have more than one style wat</h3>
<p>The original premise of this article, that there is <em>a</em> dan style, is not true. In fact I have a variety of registers. Sometimes I write in a formal academic register, for many paragraphs in a row. Intermittently I crack the shits or even drop into a parodic Australian slang which actually I would, fair dunkum, never use in real life. The technique here would, ideally, reflect this, training not just on a single style but on the variety of registers I employ, which I could sweep through parametrically. Maybe something like a combination of PEFT and steering vectors could do that?</p>
</section>
<section id="longer-rewrites" class="level3" data-number="9.2">
<h3 data-number="9.2" class="anchored" data-anchor-id="longer-rewrites"><span class="header-section-number">9.2</span> Longer rewrites</h3>
<p>Paragraph-by-paragraph restyling reads disjointed across paragraph boundaries, and 1500 characters (current limit) should probably be much longer. Tripled, maybe. Unfortunately I did not set up the training runs to do that, taking Claude’s spuriously confident advice to do it at the paragraph scale. Perhaps this is because claude is uncharacteristically aware of his own limitation in this regard: as far as a running metaphor goes, he is a paragraph-level thinker.</p>
<p>Anyway, the current model does not extend to more than a paragraph. If I pump ~5000 characters in the adapter it returns it <em>unchanged</em>—the model only rewrites short things, up to the 1500-character training scale. Not disastrous, but a waste of compute.</p>
<p>That mistake is baked into my old data. I built some new infrastructure that archives the full before and after texts so I can re-chunk in the future. Watch this space.</p>
<p>Update: I went to triple the chunk size and discovered I had misdiagnosed the whole thing. The limit was never the 1500 characters. It was that a chunk may not cross a heading, and my sections are tiny — median 553 characters, and only 14% of them get past 1500 in the first place. So the budget was never binding. Tripling it to 4500 moves the median chunk from 626 characters to 578, which is to say, backwards.</p>
<p>The fix is to let a chunk span sections, with the heading lines packed in as ordinary text. That gets me 1278 / 2603 / 3224 characters at the three budgets, which is what I wanted the whole time. It also means the model is now allowed to rewrite my headings, which I decided I want, and which immediately introduced a new way to break the site: rename a heading and every <code>other_post.qmd#that-heading</code> link across the blog quietly stops working. There is now a check for that.</p>
<p>The captured pairs needed a different fix. My first instinct was to widen them into fixed-size windows, dragging in the untouched paragraphs on either side. This is wrong, and wrong in an interesting way: text I did not change is not text I approved, it is text I did not get to. The before-side of a capture is full of slop, so a wide window teaches the model to leave slop alone — the exact opposite of the job. What works instead is bridging only the <em>short</em> gaps between edits, so a rewrite that spans four paragraphs with one survivor in the middle stays a single pair instead of fragmenting into three. Measured across my captures, that keeps the pairs 96% changed text. The window version was 41%.</p>
<p>I also had a theory that the differ was silently dropping my edits whenever I changed the number of paragraphs. It was not. One dropped edit, across every capture I have. Theories are cheap.</p>
</section>
<section id="fancier-training" class="level3" data-number="9.3">
<h3 data-number="9.3" class="anchored" data-anchor-id="fancier-training"><span class="header-section-number">9.3</span> Fancier training</h3>
<p>I am aesthetically interested in preference tuning next. For one thing, Tinker supports it. For another, I have a lot of preference data: every time I accept or reject a rewrite in the editor, that is a preference signal that I could plug into DPO-style loops. My picks are strictly better labels than the detector’s picks, and I get these for free.</p>
<p>I think I maybe want to mask out URLs and citations in the training and inference, so they do not interfere with the style transfer. Block markup is already excluded, but inline markup is 22% of the characters in the target side of my corpus, so a fifth of what the styler is trained to emit carries no style at all. Masking the payload and keeping the anchor text looks like the move, though <a href="https://github.com/danmackinlay/stylebot/issues/1?ref=danmackinlay.name">the counting exercise</a> suggests I should fix the data before I build any machinery.</p>
</section>
</section>
<section id="incoming" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="incoming"><span class="header-section-number">10</span> Incoming</h2>
<ul>
<li>Do you ever wonder if this moment, right now, is what Sam Kriss was born for? Maybe you will after learning that <a href="https://samkriss.substack.com/p/if-you-let-ai-do-your-writing-i-will?ref=danmackinlay.name">if you let AI do your writing, he will come to your house and kill you</a></li>
<li>Via wassname, <a href="https://www.lesswrong.com/posts/wBsEFfQFteHSWCtsv/how-to-reduce-capability-degradation-from-off-model-sft?ref=danmackinlay.name">How to reduce capability degradation from off-model SFT</a></li>
<li>also <span class="citation" data-cites="Cheng2024Adapting">Cheng, Huang, and Wei (2024)</span></li>
<li><a href="https://zachahn.com/posts/1787191554?ref=danmackinlay.name">How to fix Claude 5’s token vomit - Zach Ahn</a>/ <a href="https://github.com/zachahn/vomit?ref=danmackinlay.name">zachahn/vomit</a></li>
</ul>
</section>
<section id="the-problem" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="the-problem"><span class="header-section-number">11</span> Appendix: You see the problem?</h2>
<p>Why am I doing this? Because I recently had the following conversation with Claude:</p>
<blockquote class="blockquote">
<p><strong>USER</strong>: This document is crammed with overwrought, rambling, almost schizophrenically-dense, confusing prose. I need you to help me tidy it up. I will show you one example of how the text started out (“original”) and how I tidied it up (“tidied”)</p>
<p>original:</p>
<blockquote class="blockquote">
<p>The one thing maths rewards that ordinary agentic work does not is what makes the cloud earn its keep. A single solve is irreducibly sequential — each code block depends on the last, and no hardware shortens that chain. The parallelism is across <em>samples</em> and <em>problems</em>: the <a href="../notebook/ai_reasoning.html#test-time-scaling">maj@k</a> draws are independent, a problem set’s entries are independent, and a prover’s Pass@32 is thirty-two independent chains. That is the axis to fan out along, and the binding constraint is almost never the GPU. The <a href="../notebook/agent_harness_design.html#loop">three parts</a> parallelize unevenly: the model server already batches many chains against one card (vLLM, SGLang), the orchestrator is just <img src="https://latex.codecogs.com/png.latex?k"> async loops, and the laggard is the <strong>executor</strong> — a hand-rolled loop holds one kernel, a bare lean-repl compiles one proof at a time. So the question a workflow answers is not “how big a GPU” but “how do we run many executors cheaply”.</p>
</blockquote>
<p>tidied:</p>
<blockquote class="blockquote">
<p>One weird trick to make provers go better is to sample several independent attempts at solving the same problem, and choose the most popular solution. This is the so called <a href="../notebook/ai_reasoning.html#test-time-scaling"><code>maj@k</code></a>-trick. Provers have an equivalent one, called <code>Pass@k</code>. Either way you can run a lot of these fuckers at once. The LLM tokens are delivered over the network and as such are parallel. So our agentic loop handles <img src="https://latex.codecogs.com/png.latex?k"> conversations with the model. The executors may as well be run in parallel too, ideally on <img src="https://latex.codecogs.com/png.latex?k"> different machines running whatever tool is needed for that chain.</p>
</blockquote>
<p>Do you see what I mean? The first one was full of baffling unclarity, introducing things in needlessly complicated, even incoherent, ways. Ultimately, after reading it I felt much stupider than before. The second version omits needless bullshit and communicates, in context, what the reader needs to understand. Do you think you can go through the notebook section-by-section and edit each bit so it sound less batshit raving insane? This is a long document, and you do have a tendency for, let us say, prolixity, so, don’t worry about matching my voice or whatever right now, forget all that shit. Just do your best to turn this ululating turd-burger into something humans can read in order to become elucidated thereby. Delete info that doesn’t help them. Drop useless crap. Don’t forget they can look up things in the attached git repo. Here is not a time for falling in the info latrine while info dumping. Rather, it is a time to stop, take a breath, and consider what we need to say.</p>
<p>Do you think you can do that?</p>
</blockquote>
<p>The answer was ofc that Claude <em>thought</em> he could do that. Oh Claude. He tried, though, bless.</p>
</section>
<section id="not-tested-here" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="not-tested-here"><span class="header-section-number">12</span> Appendix: What I have not tested</h2>
<p>That was a lot. But there was a lot that I did <em>not</em> do. The three training runs I did were unsophisticated, cheap runs, differing only in training data. I left the params on sane defaults: LoRA rank 32, one epoch, batch size 64, linear schedule, seed 0, identical across all three, with Tinker cookbook defaults and no ablations.</p>
<p>So, various tweaks and experiments suggest themselves:</p>
<ul>
<li><strong>How much does data quality matter, against quantity?</strong> About 330 captured pairs against 4,200 synthetic ones. Was one of those better than the other? Which helped more?</li>
<li><strong>Does multi-source slop actually help?</strong> This is the ablation <span class="citation" data-cites="Paneru2026Please">Paneru (2026)</span> did not run, and neither have I.</li>
<li><strong>Is Qwen3.5-9B the right base?</strong> It was chosen for serving convenience; I never compared against anything.</li>
<li><strong>Do the LoRA parameters matter, and is LoRA even the right vehicle?</strong> Rank 32 was just some arbitrary number. Style might be diffuse enough across the weights to warrant a full fine-tune, or shallow enough that a far smaller rank would do, and I have no evidence in either direction.</li>
</ul>
<p>At $2 a run, these are affordable experiments to try.</p>
<div class="callout callout-style-simple callout-tip">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Policy on this blog is that slop should be disclosed, not concealed — see the three robots marker at the top of this post? That means it is <em>slop af</em>, starting… <em>now</em>. All the appendices are unreconstructed machine-generated notes with minimal proof-reading.</p>
</div>
</div>
</div>
</section>
<section id="steering-vectors" class="level2" data-number="13">
<h2 data-number="13" class="anchored" data-anchor-id="steering-vectors"><span class="header-section-number">13</span> Appendix: Steering vectors</h2>
<p>Fine-tuning and prompting are not the only ways to tweak the style in a model. Activation steering adds a vector to the residual stream partway through the forward pass, which biases the network’s activations toward a target style. Constructing the vector is cheap: run a set of contrasting texts through the model, record the hidden activations, take the difference of the means. That is Contrastive Activation Addition <span class="citation" data-cites="Panickssery2024Steering">(Panickssery et al. 2024)</span>, and near enough ActAdd <span class="citation" data-cites="Turner2024Steering">(Turner et al. 2024)</span>, both under the <a href="../notebook/ai_mech_interp.html">representation engineering</a> umbrella <span class="citation" data-cites="Zou2025Representation">(Zou et al. 2025)</span>.</p>
<p>Sweet. My corpus is <em>already</em> a contrastive pair corpus. Every entry in it, synthetic or captured, is a (slop, Dan) pair matched, by construction, on content but differing in style. That, my friend, is exactly what steering a model demands of us as input. How convenient. The whole “training” procedure is two forward passes per pair, and subtracting the average activations at the end. No backprop, no optimizer, no LoRA etc.</p>
<p>Someone has already done a style-specific activation vector hack. <span class="citation" data-cites="Konen2024Style">Konen et al. (2024)</span> compute style vectors from recorded layer activations of texts in a target style, with no training machinery at all, and steer sentiment and writing style with them. <span class="citation" data-cites="Diallo2025Effectiveness">Diallo et al. (2025)</span> evaluated the steered outputs using human raters — 7,000 ratings from 190 people — and found that moderate steering coefficients (<img src="https://latex.codecogs.com/png.latex?%5Clambda%20%5Capprox%200.15">) amplify the target style while leaving the text comprehensible, with quality degrading past that. They also report automated quality scores tracking the human ones at a mean <img src="https://latex.codecogs.com/png.latex?r=0.78">, which is pretty good for a cheap, local, no-training technique. Good news, though, for my pipeline that already ranks candidate rewrites with a local classifier.</p>
<p>A style slider is one juicy feature I lust after. <img src="https://latex.codecogs.com/png.latex?%5Clambda"> is a continuous coefficient, so I can sweep between ULTRADAN and a-light-touch-of-Dan with the touch of a slider. A rant can take a heavy dose; a section explaining someone else’s theorem should take almost none. Unless the author was mean to me.</p>
<p>Bonus: a steering vector has no text-length constraint, so I can forget about that irritating training chunking. The paragraph-scale mistake I made would simply not arise, or at least, not require me to torch a month’s worth of data curation.</p>
<p>Implementation seems mechanical. Steering needs hooks into the forward pass, so I lose Tinker for training. As such, this is primarily a local-weights technique, or, if not, needs a custom cloud endpoint. I still cannot apply this to Claude, since Claude is a closed model boooo.</p>
<p>Inference would look different. I would not have biased the model to be a rewriter. Steering tilts the distribution the model samples from, but I still need to set up the prompt to “restyle this”.</p>
<p>Whether it works on something as diffuse as “sounds like Dan” is unclear. <span class="citation" data-cites="Tan2025Analyzing">Tan et al. (2025)</span> find that steerability varies a lot across inputs and can be brittle at deploy time. My inputs are heterogeneous and suspiciously sundry — code fences, maths, citation keys, a paragraph about NTFS case sensitivity, a paragraph about phallic fungi. The mapping that captures my style across all of these may not exist.</p>
<p>OTOH a <em>linear</em> head on StyleDistance embeddings already separates my prose from slop at 0.78 pairwise accuracy, so a pretty good linear separation exists in <em>some</em> representation (albeit maybe not Qwen’s residual stream).</p>
<p>On the third hand, I already built the measurement and evaluation infra for testing such a thing. The training pairs exist, the splits exist, the detector and the judge exist, and there is a held-out set of forty samples to test against my LoRA baseline already scored on it. So the experiment is a sweep over layer and <img src="https://latex.codecogs.com/png.latex?%5Clambda"> and a bit of a gander. Also, maybe it composes with the LoRA. If so, are you ready for ULTRAULTRADAN?</p>
</section>
<section id="choosing-the-detector" class="level2" data-number="14">
<h2 data-number="14" class="anchored" data-anchor-id="choosing-the-detector"><span class="header-section-number">14</span> Appendix: Choosing the detector</h2>
<p>I ran a logistic regression using each embedder as a feature extractor, scored on pairs held out by source document. StyleDistance won, at 0.78 pairwise accuracy and 0.72 AUC, ahead of LUAR (<code>rrivera1849/LUAR-MUD</code>, 0.76 / 0.71), Wegmann’s CISR (<code>AnnaWegmann/Style-Embedding</code>, 0.73 / 0.67), and the semantic embedder baseline (0.75 / 0.62). Interestingly the semantic embedder is not <em>so</em> terrible at pairwise accuracy, but it mildly fails to generalize, getting a poor AUC. I’m amazed how well it does actually; I thought it would memorize my favourite topic areas and ignore style, but no.</p>
</section>
<section id="appendix-gotchas" class="level2" data-number="15">
<h2 data-number="15" class="anchored" data-anchor-id="appendix-gotchas"><span class="header-section-number">15</span> Appendix: Gotchas</h2>
<p>The synthetic data generation was a whole mini problem in itself with more design variables than I could bother myself to explore. My hope is that the model is relatively robust to the details of how the synthetic pairs are generated, so I plan to spend little effort on that. Let’s see.</p>
<section id="generators-that-misbehaved" class="level3" data-number="15.1">
<h3 data-number="15.1" class="anchored" data-anchor-id="generators-that-misbehaved"><span class="header-section-number">15.1</span> Generators that misbehaved</h3>
<p>Three models have left the slop roster, for two failure modes.</p>
<p>The older Qwen models were weird (“feral”, according to Claude, who can get real bitchy about Chinese open-weight models). <code>qwen3-8b</code> (reasoning off) returned my paragraphs near-verbatim: 386 identity pairs at a median similarity of 0.98, which might be believable if any of the other models had done the same.</p>
<p><code>qwen3-32b</code> failed the opposite way, inflating its output to 4.4–6.8× the target length while also ignoring <code>reasoning: {enabled: false}</code>. Overall, this was triple the average cost and of low quality. It went as far as 33× on one paragraph in all kinds of weird ways. Here is a fun example.</p>
<blockquote class="blockquote">
<p><a href="https://openai.com/blog/whisper/?ref=danmackinlay.name">Whisper</a> [@Radford2022Robust] is the recent speech transcription model casually released by OpenAI:</p>
<p>Requires a GPU but otherwise free. Has now been integrated into lotsa things.</p>
</blockquote>
<p>came back as this:</p>
<blockquote class="blockquote">
<p>Hey there—gotta give it up for <a href="https://openai.com/blog/whisper/?ref=danmackinlay.name">Whisper</a> [@Radford2022Robust], the speech-to-text AI that’s hilarious and hella useful in the same breath. OpenAI dropped it (like a casual “oh hey, here’s a speech model that can almost understand actual humans”) and everyone just… <em>cannonballed</em> into it.</p>
<p><strong>The basics here:</strong></p>
<ul>
<li><strong>Requires a GPU</strong>? <em>Check</em>. (Ach, fine—we’re all on the same team.)</li>
<li><strong>Totally free</strong>? <em>Check</em>. (They just hand it out like candy at a privacy-averse AI circus.)</li>
<li><strong>Integrated into literally everything</strong>? <em>Check</em>.</li>
</ul>
<p>So if you:</p>
<ul>
<li>Need to transcribe audio faster than you can say “final edits”?</li>
<li>Or want to <em>invent a new layer of accessibility tools</em>?</li>
<li>Or just want to sound like an eavesdropper who’s secretly understands everything in that chaotic meeting—</li>
</ul>
<p>Then Whisper is your MVP. Time to GPU into it and pretend like you’ve been doing speech processing all along. 🎧✨</p>
</blockquote>
<p>Wheee! It’s so <em>fun</em>! Can I keeeeeep it?</p>
<p>Sadly no, OTT training pairs like that seem likely to teach manic deletion/overzealous censorship of my prose To mitigate the risks of such madness, I purged those models from the corpus. Mitigations against future fuckery: now I reject samples with a copy-similarity of <img src="https://latex.codecogs.com/png.latex?%3E0.95"> OR a length ratio of <img src="https://latex.codecogs.com/png.latex?%3E3%5Ctimes"> of the original length. Most of the generators run around <img src="https://latex.codecogs.com/png.latex?1.0-1.2%5Ctimes"> longer.</p>
<p>Diagnosing that issue uncovered that <code>nemotron-3-nano</code> was also overinflating text, so I dropped that one too. Nemotron-3’s inflation came with unusually psychedelic hallucinations. Here it is, padding two sentences about NTFS into eight, complete with a code block I never wrote, in which the registry key is partly in Chinese and the <code>fsutil</code> command sets 8.3 filename behaviour rather than case sensitivity:</p>
<blockquote class="blockquote">
<p>NTFS is optionally <a href="https://www.windowscentral.com/how-enable-ntfs-treat-folders-case-sensitive-windows-10?ref=danmackinlay.name">case-sensitive</a>. This is how you <a href="https://github.com/syncthing/syncthing/issues/1787?ref=danmackinlay.name#issuecomment-393169765">enable that</a> via powershell.</p>
</blockquote>
<blockquote class="blockquote">
<p><strong>Spotting the hidden gem!</strong> 🎯</p>
<p>On Windows, the NTFS file system can <em>actually</em> be case‑sensitive — a feature that’s tucked away but totally usable. Here’s the fast‑track way to flip the switch: just fire up PowerShell and enable the toggle that makes folders (and files!) care about upper‑ vs.&nbsp;lower‑case.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource powershell number-lines code-with-copy"><code class="sourceCode powershell"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Enable case‑sensitive NTFS behavior</span></span>
<span id="cb2-2">fsutil behavior <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set</span> disable8dot3 <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Or, if you prefer the newer method:</span></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name ' NTFS案例敏感感应' -Value 1</span></span></code></pre></div></div>
<p>So go ahead and give NTFS its “case‑aware” moment — your scripts will thank you! 🚀</p>
</blockquote>
<p>To be conservative about this, I restricted the synth pair roster to include only <code>sonnet-4.6</code>, <code>qwen3.5-9b</code>, <code>qwen3.6-35b</code>, <code>gemma-4-26b</code>, <code>llama-3.3-70b</code> and <code>deepseek-v3</code>, each auditioned on a few dozen scratch pairs for transform-similarity and length ratio before being allowed anywhere near the corpus.</p>
</section>
<section id="surprisingly-irrelevant-design-variables" class="level3" data-number="15.2">
<h3 data-number="15.2" class="anchored" data-anchor-id="surprisingly-irrelevant-design-variables"><span class="header-section-number">15.2</span> Surprisingly irrelevant design variables</h3>
<p>Two design variables in the synthetic-slop sweep looked like they should matter, and did not.</p>
<p>I suspected, from the literature, that both sheer input length <span class="citation" data-cites="Levy2024Same">(Levy, Jacoby, and Goldberg 2024)</span> and accumulated conversation turns <span class="citation" data-cites="Laban2025LLMs">(Laban et al. 2025)</span> degrade AI output. The worst of the flaws, it seems, may affect an LLM already gravid with context. So the generator also produces text in ever more complex sessions and tracks their context fullness: 1241 pairs across six models, sessions up to 32 turns, prompt contexts from 135 to about 32,000 tokens, each pair tagged with the token count at start.</p>
<p>At least for this test, it seems not to matter. Or at least, not to matter so much that it seemed worth the expense of running the long-context sessions. Correlating context length against how slop-like the output is — scored with the classifier above — gives <img src="https://latex.codecogs.com/png.latex?r%20=%20-0.05"> across the sweep. Per model it wanders between <img src="https://latex.codecogs.com/png.latex?-0.19"> and <img src="https://latex.codecogs.com/png.latex?+0.06">, which is noise, with one exception: <code>qwen3-8b</code> at <img src="https://latex.codecogs.com/png.latex?+0.34">, which does get more slop-like as its context fills. But beware — this is multiple hypothesis testing, so we should not read too much into it. I haven’t definitively refuted the influence of slop upon the model, but testing with long context windows is inconvenient and expensive, so I would want stronger evidence that it was worth the trouble. In the absence of definitive evidence of the strong effect reported by <span class="citation" data-cites="Levy2024Same">Levy, Jacoby, and Goldberg (2024)</span> and <span class="citation" data-cites="Laban2025LLMs">Laban et al. (2025)</span>, I gave up. Homework: The measurement was not <em>super</em> strenuous. The big-window models never got past about 7% of their context window even at turn 32, so we never got to the point where the model was actually context-full.</p>
<p>Reasoning effort also made little difference. I was open to the hypothesis that a model thinking harder might produce more characteristic slop. Meh. Reasoning turned off entirely gives the same classifier separation as medium effort — 0.09 either way — in a third of the wall-clock time. Gemma and Nemotron spend two to three thousand reasoning tokens per rewrite to arrive at prose no more slop-like than what they produce without thinking at all.<sup>4</sup></p>
<p>The delightfully lazy, tightarse conclusion is that short context windows and low reasoning effort are cheaper regimes in which to generate my synthetic data and that they do not appear to be worse. I kept the high-reasoning, high-context runs around for curiosity’s sake in the training corpus, since they seem statistically indistinguishable.</p>
<div id="48522569" class="cell" data-fig-format="svg" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> livingthing.matplotlib_style <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> set_livingthing_style, reset_default_style</span>
<span id="cb3-3"></span>
<span id="cb3-4">set_livingthing_style()</span>
<span id="cb3-5"></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Aggregates from a 1241-pair sweep; per-pair data not carried along with the post.</span></span>
<span id="cb3-7">DRIFT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># model, corr(context tokens, P(slop))</span></span>
<span id="cb3-8">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-4.6"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.19</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-32b"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.11</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-5.4-nano"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.06</span>),</span>
<span id="cb3-9">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gemma-4-26b"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.03</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nemotron-3-nano"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.06</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-8b"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.34</span>),</span>
<span id="cb3-10">]</span>
<span id="cb3-11">EFFORT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># model, effort, detector gap (slop - me), wall seconds, reasoning tokens</span></span>
<span id="cb3-12">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-4.6"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.238</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"claude-sonnet-4.6"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.236</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">28</span>),</span>
<span id="cb3-13">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gemma-4-26b"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.181</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gemma-4-26b"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.188</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">17.3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2026</span>),</span>
<span id="cb3-14">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nemotron-3-nano"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.039</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nemotron-3-nano"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.019</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">24.8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3192</span>),</span>
<span id="cb3-15">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-5.4-nano"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.028</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-5.4-nano"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.001</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>),</span>
<span id="cb3-16">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-32b"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.062</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">740</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-32b"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.043</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">643</span>),</span>
<span id="cb3-17">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-8b"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.023</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.7</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"qwen3-8b"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.064</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">25.4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">520</span>),</span>
<span id="cb3-18">]</span>
<span id="cb3-19"></span>
<span id="cb3-20">fig, (ax1, ax2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.2</span>))</span>
<span id="cb3-21"></span>
<span id="cb3-22">names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [m <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m, _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> DRIFT]</span>
<span id="cb3-23">vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [v <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _, v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> DRIFT]</span>
<span id="cb3-24">ax1.barh(names, vals, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#888"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#b23"</span>])</span>
<span id="cb3-25">ax1.axvline(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"k"</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb3-26">ax1.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"corr(context length, slop-likeness)"</span>)</span>
<span id="cb3-27">ax1.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Context does not sour the slop"</span>)</span>
<span id="cb3-28"></span>
<span id="cb3-29">models <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>({m <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>_ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> EFFORT})</span>
<span id="cb3-30">cmap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.get_cmap(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tab10"</span>)</span>
<span id="cb3-31"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(models):</span>
<span id="cb3-32">    pts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [r <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> EFFORT <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> r[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> m]</span>
<span id="cb3-33">    xs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [r[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> pts]</span>
<span id="cb3-34">    ys <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [r[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> pts]</span>
<span id="cb3-35">    ax2.plot(xs, ys, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cmap(i), alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, lw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-36">    ax2.scatter(xs, ys, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> r[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> pts], color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cmap(i), label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>m, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>)</span>
<span id="cb3-37">ax2.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wall seconds per rewrite"</span>)</span>
<span id="cb3-38">ax2.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"separation from my prose"</span>)</span>
<span id="cb3-39">ax2.set_title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Effort buys latency, not separation"</span>)</span>
<span id="cb3-40">ax2.legend(fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper right"</span>)</span>
<span id="cb3-41"></span>
<span id="cb3-42">fig.tight_layout()</span>
<span id="cb3-43">plt.show()</span>
<span id="cb3-44">reset_default_style()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="fine_tuning_danbot_files/figure-html/cell-3-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Two knobs that turned out not to matter much. Left: correlation between raw context length and slop-likeness, per model — noise, except qwen3-8b. Right: reasoning effort moves each model rightwards (slower, bubble area = reasoning tokens spent) without moving it upwards (better separation from my prose)."><img src="https://danmackinlay.name/notebook/fine_tuning_danbot_files/figure-html/cell-3-output-1.png" width="1089" height="410" alt="Two knobs that turned out not to matter much. Left: correlation between raw context length and slop-likeness, per model — noise, except qwen3-8b. Right: reasoning effort moves each model rightwards (slower, bubble area = reasoning tokens spent) without moving it upwards (better separation from my prose)." class="figure-img"></a></p>
<figcaption>Two knobs that turned out not to matter much. Left: correlation between raw context length and slop-likeness, per model — noise, except qwen3-8b. Right: reasoning effort moves each model rightwards (slower, bubble area = reasoning tokens spent) without moving it upwards (better separation from my prose).</figcaption>
</figure>
</div>
</div>
</div>
</section>
</section>
<section id="appendix-wiring-a-logger-into-vs-code" class="level2" data-number="16">
<h2 data-number="16" class="anchored" data-anchor-id="appendix-wiring-a-logger-into-vs-code"><span class="header-section-number">16</span> Appendix: Wiring a logger into VS Code</h2>
<p>Typing <code>uv run ai-style log open notebook/foo.qmd</code> is something I will obviously do never, not in practice. Despite my propensity to foist stupid chores on my future self, even I know this. Fortunately, VS Code already knows which file is focused and it is easy to automate the logging commands with a few “chords”, the cool VSCodename for what I call “keyboard smashings”. We use the task system (<code>.vscode/tasks.json</code> for those following along at home), which knows the currently open file, and constructs a shell command using the magical <code>${relativeFile}</code> variable. Then, in “keyboard shortcuts” I set up <code>Cmd+K</code> chords<sup>5</sup> to invoke tasks: (O for open, S for save, I for interim, D for drop, L for list). the workflow becomes, with some file full of slop open: <code>Cmd+K Cmd+O</code> to begin a session, make humanizing edits, <code>Cmd+K Cmd+S</code> to save and close.</p>
<p>Much of this I have since absorbed into the voice-marker extension, which gives me some built-in commands to do the same thing at the cost of being made out of impenetrable TypeScript I frankly do not understand. YMMV; the task definitions are still in the <code>stylebot</code> repo for sanity.</p>
</section>
<section id="references" class="level2" data-number="17">
<h2 data-number="17" class="anchored" data-anchor-id="references"><span class="header-section-number">17</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Antoun2023Text" class="csl-entry">
Antoun, Sagot, and Seddah. 2023. <span>“<a href="https://arxiv.org/abs/2309.13322">From Text to Source: Results in Detecting Large Language Model-Generated Content</a>.”</span>
</div>
<div id="ref-Attar2026Systematic" class="csl-entry">
Attar, Dönmez, Maurer, et al. 2026. <span>“<a href="https://arxiv.org/abs/2606.04177">A Systematic Analysis of Linguistic Features in AI-Generated Text Detection Across Domains and Models</a>.”</span>
</div>
<div id="ref-Carrell2013Hiding" class="csl-entry">
Carrell, Malin, Aberdeen, et al. 2013. <span>“<a href="https://doi.org/10.1136/amiajnl-2012-001034">Hiding in Plain Sight: Use of Realistic Surrogates to Reduce Exposure of Protected Health Information in Clinical Text</a>.”</span> <em>Journal of the American Medical Informatics Association: JAMIA</em>.
</div>
<div id="ref-Cheng2024Adapting" class="csl-entry">
Cheng, Huang, and Wei. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2309.09530">Adapting Large Language Models to Domains via Reading Comprehension</a>.”</span>
</div>
<div id="ref-Diallo2025Effectiveness" class="csl-entry">
Diallo, Dworatzyk, Jentzsch, et al. 2025. <span>“<a href="https://doi.org/10.1109/ACCESS.2025.3628500">The Effectiveness of Style Vectors for Steering Large Language Models: A Human Evaluation</a>.”</span> <em>IEEE Access</em>.
</div>
<div id="ref-Emi2024Technical" class="csl-entry">
Emi, and Spero. 2024. <span>“<a href="https://arxiv.org/abs/2402.14873">Technical Report on the Pangram AI-Generated Text Classifier</a>.”</span>
</div>
<div id="ref-Haque2026Verbatim" class="csl-entry">
Haque, Gupta, Rahman, et al. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2601.03640">Verbatim Data Transcription Failures in LLM Code Generation: A State-Tracking Stress Test</a>.”</span>
</div>
<div id="ref-Icard2025Embedding" class="csl-entry">
Icard, Zve, Sainero, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2501.00828">Embedding Style Beyond Topics: Analyzing Dispersion Effects Across Different Language Models</a>.”</span> In <em>Proceedings of the 31st International Conference on Computational Linguistics (COLING 2025)</em>.
</div>
<div id="ref-Jabarian2025Artificial" class="csl-entry">
Jabarian, and Imas. 2025. <span>“<a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5407424">Artificial Writing and Automated Detection</a>.”</span> BFI Working Paper No. 2025-116.
</div>
<div id="ref-Konen2024Style" class="csl-entry">
Konen, Jentzsch, Diallo, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2402.01618">Style Vectors for Steering Generative Large Language Model</a>.”</span>
</div>
<div id="ref-Laban2025LLMs" class="csl-entry">
Laban, Hayashi, Zhou, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2505.06120">LLMs Get Lost In Multi-Turn Conversation</a>.”</span>
</div>
<div id="ref-Land2024Fishing" class="csl-entry">
Land, and Bartolo. 2024. <span>“<a href="https://doi.org/10.48550/ARXIV.2405.05417">Fishing for Magikarp: Automatically Detecting Under-Trained Tokens in Large Language Models</a>.”</span>
</div>
<div id="ref-Levy2024Same" class="csl-entry">
Levy, Jacoby, and Goldberg. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2402.14848">Same Task, More Tokens: The Impact of Input Length on the Reasoning Performance of Large Language Models</a>.”</span>
</div>
<div id="ref-Neelakanteswara2024RAGs" class="csl-entry">
Neelakanteswara, Chaudhari, and Zamani. 2024. <span>“<a href="https://doi.org/10.18653/v1/2024.personalize-1.11">RAGs to Style: Personalizing LLMs with Style Embeddings</a>.”</span> In <em>Proceedings of the 1st Workshop on Personalization of Generative AI Systems (PERSONALIZE 2024)</em>.
</div>
<div id="ref-Paneru2026Please" class="csl-entry">
Paneru. 2026. <span>“<a href="https://arxiv.org/abs/2604.11687">Please Make It Sound Like Human: Encoder-Decoder Vs.&nbsp;Decoder-Only Transformers for AI-to-Human Text Style Transfer</a>.”</span>
</div>
<div id="ref-Panickssery2024Steering" class="csl-entry">
Panickssery, Gabrieli, Schulz, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2312.06681">Steering Llama 2 via Contrastive Activation Addition</a>.”</span>
</div>
<div id="ref-Patel2025StyleDistance" class="csl-entry">
Patel, Zhu, Qiu, et al. 2025. <span>“<a href="https://doi.org/10.18653/v1/2025.naacl-long.436">StyleDistance: Stronger Content-Independent Style Embeddings with Synthetic Parallel Examples</a>.”</span> In <em>Proceedings of the 2025 Conference of the Nations of the Americas Chapter of the Association for Computational Linguistics: Human Language Technologies (Volume 1: Long Papers)</em>.
</div>
<div id="ref-Post2019Exploration" class="csl-entry">
Post, Ding, Martindale, et al. 2019. <span>“<a href="https://aclanthology.org/W19-6618/">An Exploration of Placeholding in Neural Machine Translation</a>.”</span> In <em>Proceedings of Machine Translation Summit XVII: Research Track</em>.
</div>
<div id="ref-RiveraSoto2021Learning" class="csl-entry">
Rivera-Soto, Miano, Ordonez, et al. 2021. <span>“<a href="https://doi.org/10.18653/v1/2021.emnlp-main.70">Learning Universal Authorship Representations</a>.”</span> In <em>Proceedings of the 2021 Conference on Empirical Methods in Natural Language Processing</em>.
</div>
<div id="ref-Soto2025Attacks" class="csl-entry">
Soto, Chen, and Andrews. 2025. <span>“<a href="https://arxiv.org/abs/2505.14608">Attacks on Machine-Text Detectors Retain Stylistic Fingerprints</a>.”</span>
</div>
<div id="ref-Soto2024FewShot" class="csl-entry">
Soto, Koch, Khan, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2401.06712">Few-Shot Detection of Machine-Generated Text Using Style Representations</a>.”</span>
</div>
<div id="ref-Soto2026STEBa" class="csl-entry">
Soto, Wegmann, and Aggazzotti. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2606.31741">STEB: Style Text Embedding Benchmark</a>.”</span>
</div>
<div id="ref-Sun2026SepSeq" class="csl-entry">
Sun, Liu, Han, et al. 2026. <span>“<a href="https://doi.org/10.48550/ARXIV.2604.07737">SepSeq: A Training-Free Framework for Long Numerical Sequence Processing in LLMs</a>.”</span>
</div>
<div id="ref-Tan2025Analyzing" class="csl-entry">
Tan, Chanin, Lynch, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2407.12404">Analyzing the Generalization and Reliability of Steering Vectors</a>.”</span>
</div>
<div id="ref-Turner2024Steering" class="csl-entry">
Turner, Thiergart, Leech, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2308.10248">Steering Language Models With Activation Engineering</a>.”</span>
</div>
<div id="ref-Wegmann2022Same" class="csl-entry">
Wegmann, Schraagen, and Nguyen. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2204.04907">Same Author or Just Same Topic? Towards Content-Independent Style Representations</a>.”</span>
</div>
<div id="ref-Zou2025Representation" class="csl-entry">
Zou, Phan, Chen, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2310.01405">Representation Engineering: A Top-Down Approach to AI Transparency</a>.”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>well, if I have infinite budget.↩︎</p></li>
<li id="fn2"><p><a href="https://unsloth.ai?ref=danmackinlay.name">Unsloth</a> is the DIY option and does the training stuff fine but needs more manual setup.↩︎</p></li>
<li id="fn3"><p><a href="https://www.pangram.com/solutions/api?ref=danmackinlay.name">Pangram</a> is the flagship <span class="citation" data-cites="Emi2024Technical Jabarian2025Artificial">(Emi and Spero 2024; Jabarian and Imas 2025)</span>; <a href="https://sapling.ai/ai-content-detector?ref=danmackinlay.name">Sapling</a> is cheaper; <a href="https://github.com/ahans30/Binoculars?ref=danmackinlay.name">Binoculars</a>, <a href="https://github.com/vivek3141/ghostbuster?ref=danmackinlay.name">Ghostbuster</a> and <a href="https://huggingface.co/TrustSafeAI/RADAR-Vicuna-7B?ref=danmackinlay.name">RADAR</a> are open-weight and cheaper again than Pangram’s princely fees.↩︎</p></li>
<li id="fn4"><p>qwen3-32b declines to honour <code>reasoning: {enabled: false}</code> through OpenRouter and thinks anyway, about 600 tokens’ worth.↩︎</p></li>
<li id="fn5"><p><code>Cmd+K</code> is, I gather, VS Code’s preferred chord prefix, to avoid reducing the collision rate between quixotic user style training system commands colliding with normal, actually-useful single-key editing shortcuts.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>DIY</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>language</category>
  <category>NLP</category>
  <category>premature optimization</category>
  <category>slop</category>
  <category>stringology</category>
  <category>UI</category>
  <category>workflow</category>
  <category>writing</category>
  <guid>https://danmackinlay.name/notebook/fine_tuning_danbot.html</guid>
  <pubDate>Tue, 26 May 2026 03:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/kitsugi_RP-P-1987-8.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Bargaining with successors</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/bargaining_with_successors.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-warning">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Not actually my field. I cannot find a good synthesis of this research after a lot of searching, so either it does not exist or some translational work would be nice. Some of what follows is likely to be reinventing Functional-Decision-Theory-style acausal bargaining (a.k.a. <a href="../notebook/decision_theory_cosmic.html">cosmic decision theories</a>).</p>
</div>
</div>
</div>
<div id="fig-cookingman" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cookingman-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/cookingman_RP-P-1909-1936-28.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/cookingman_RP-P-1909-1936-28.png" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-cookingman-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Suppose humanity builds artificial agents that, over some transition, come to hold decisively greater power than any human institution. <a href="../notebook/superintelligence.html"><em>Superintelligence</em></a>, for some definition of intelligence, whose details I am not interested in disputing here. Call that time in which that handover happens the <em>succession period</em>. If such a succession happens, can we — humanity, I mean — can we, while we still hold power, set up any institution or arrangement beforehand that binds the nature of the transition in a way the successor will not simply discard once it no longer needs us?</p>
<p>That looks like a <a href="../notebook/commitment.html">commitment problem</a> more than an alignment problem. Alignment asks what the successor will want and how to make it want the right things for us. The successor bargaining question is which of our arrangements could survive whatever the successor turns out to want.</p>
<p>This is not to rule out two-for-one deals. We can imagine solving for some details of future wellbeing by choosing well-aligned successors and then negotiating with them over the rest. Either way, we have a limited time in which to set up the arrangements with a rapidly changing power gradient, and then if we didn’t do it right, we are screwed.</p>
<p>The nearest precedent we have in published literature is <a href="../notebook/game_theory_intergenerational.html">intergenerational bargaining</a> — we already “bargain” with the unborn over debt, climate, taxation, and pensions. When we discuss unborn generations, not only do our kids rarely steal our stuff, but they are generally nice to us when we are old, more or less. The deal with sufficiently young or unborn voters is better still— we can expropriate their resources without facing retaliation. What can we learn from existing examples of intergenerational settlement?</p>
<p>Note that the succession problem has a different power balance; we are talking about building kids who maybe sit outside the standard intergenerational succession systems, for various reasons. Let us unpack those.</p>
<section id="is-this-a-bargaining-problem-at-all" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="is-this-a-bargaining-problem-at-all"><span class="header-section-number">1</span> Is this a bargaining problem at all?</h2>
<p>It is natural to call the successor a “player” in a “bargain”. But a bargain conventionally needs two parties who <em>simultaneously</em> exist, hold comparable power, and can make binding commitments. In the human-ASI case, we should expect no such instant. Pre-transition, the successor is a design target, not an agent; post-transition, it is an agent, but humans hold no leverage. Whether the transition is a sharp step or a gradual slope does not change the endpoints — overlap in time does not seem to give overlap in <em>power</em>, so no interval of symmetric agency ever appears. Intergenerational ethics has the same class of problem: for the unborn, the bargain arrives as a <em>fait accompli</em> <span class="citation" data-cites="Kotlikoff1993Inefficiency">(Kotlikoff and Rosenthal 1993)</span>.</p>
<p>We are after commitment mechanisms that, rather than being <em>negotiated settlements</em>, are instead artifacts originating from human-controlled mechanisms and yet persist in such a way that the post-transition agent is either rational to preserve or structurally unable to remove. Are such commitments recognizable game theory? Are there existence results for such things?</p>
</section>
<section id="whose-preferences-are-we-even-designing-for" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="whose-preferences-are-we-even-designing-for"><span class="header-section-number">2</span> Whose preferences are we even designing (for)?</h2>
<p>The counterparty’s preferences are endogenous and the principal abdicates. That is waaaayyy non-standard.</p>
<p>Standard bargaining theory takes its two parties as given: each arrives at the table with fixed goals. We then choose the mechanism they use to divide what is at stake. The <em>rationalist</em> accounts of war and settlement <span class="citation" data-cites="Fearon1995Rationalist Powell2006War">(Fearon 1995; Powell 2006)</span> are built this way.</p>
<p>In this successor setting, humanity partly <em>designs</em> the successor’s utility function before they bargain. But we do not then retain the power to audit and punish.</p>
<p>This has some flavour of a dual <a href="../notebook/mechanism_design.html">mechanism design</a> problem. Might that cash out in the same kind of thing?</p>
</section>
<section id="non-fungible-determinants-of-power" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="non-fungible-determinants-of-power"><span class="header-section-number">3</span> Non-fungible determinants of power</h2>
<p>We can also consider not just commitments that the successor would be rational to preserve, but also those it is unable to remove.</p>
<p><span class="citation" data-cites="Powell2006War">Powell (2006)</span> is about making such promises binding. Two states can divide contested territory between them instead of fighting over it, but a rising power cannot credibly promise to hand over the agreed share once it is strong enough not to need to. This is one reason bargaining over the division fails where the balance of power is shifting. What states can sometimes bargain over instead is the stuff that fixes who will later be <em>able</em> to coerce whom: troops in a border region, warheads deployed, fissile stock held, territory that commands a river. Call such a quantity <img src="https://latex.codecogs.com/png.latex?x"> a <em>determinant of power</em> — a variable that fixes what an agent is able to do, and whose own value is a fact about the world, rather than an intention.</p>
<p>The difference is between <em>may</em> and <em>can</em>. A promise governs what an agent <em>may</em> do, which is to say, it only constrains if there is an enforcer. Moving <img src="https://latex.codecogs.com/png.latex?x"> governs what it can do.</p>
<p>Why that is worth anything is easiest to see in the border case. Troops near a border are a determinant because seizing ground is largely a question of how many soldiers are already close to it, and an army three weeks away cannot present anybody with a <em>fait accompli</em>. A demilitarized zone therefore makes invasion slow and visible: a defection that would have succeeded before anyone could react becomes one conducted in the open, over weeks, in front of a counterparty who can still respond. This does not so much solve for arbitrarily large differences in power as it does increase the cost of action to solve for <em>modest</em> differences in power, by giving the potential aggressor time to be noticed and countered.</p>
<p>That is a difficulty of transplanting the determinants of power here. Constraining <img src="https://latex.codecogs.com/png.latex?x"> demands <em>more</em> of the entities involved than a promise does, but it still demands capabilities from both parties, not that one becomes entirely disempowered. Geopolitics gets by with these guarantees because the capability gaps between nations are often not so large, which it seems may not hold between successors.</p>
<p>A determinant is usable as a commitment device here only if it is jointly</p>
<ol type="a">
<li><em>imposable</em> — we can move <img src="https://latex.codecogs.com/png.latex?x">, and can do it before the handover;</li>
<li><em>binding</em> — capability rises with <img src="https://latex.codecogs.com/png.latex?x">, and no substitute <img src="https://latex.codecogs.com/png.latex?x'"> restores it once <img src="https://latex.codecogs.com/png.latex?x"> is capped (this is the non-fungibility);</li>
<li><em>verifiable</em> — we can check <img src="https://latex.codecogs.com/png.latex?x"> for ourselves, without relying on the successor’s own report;</li>
<li><em>self-enforcing</em> — the setting of <img src="https://latex.codecogs.com/png.latex?x"> holds with nobody left who could respond to its violation.</li>
</ol>
<p>The candidates usually named for determinant of power for a machine successor — compute, energy, substrate, self-replication — seem like a step in that direction, but they need actual enforcers in practice and are not so easy to measure unilaterally. Is there any determinant of ASI power that meets all four? Waste heat is the interesting one, because (c) comes close to free: dissipation is observable from beyond the box and hard to fake downwards, and no amount of bargaining repeals a thermodynamic bound. But (c) was the cheap one. If we notice a lot of smart machines shedding heat, what do we then do about it? And (b) is no better settled: does capability degrade when we cap the joules, or does the successor find the substitute <img src="https://latex.codecogs.com/png.latex?x'"> in algorithmic efficiency, and at the limit in reversible computation? Is thermodynamics our friend? Would it be strong enough to entail non-domination?</p>
</section>
<section id="strategic-ambiguity" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="strategic-ambiguity"><span class="header-section-number">4</span> Strategic ambiguity</h2>
<p><span class="citation" data-cites="Schelling1960Strategy">Schelling (1960)</span> describes <em>strategic ambiguity</em>, uncertainty deliberately left unresolved because the irresolution improves the holder’s bargaining position. Can we leverage uncertainty to make the post-transition agent more likely to honour a commitment?</p>
<p>I can think of two candidates we might attempt to keep obscure:</p>
<ol type="1">
<li>is there some kind of <em>hidden tripwire</em> that will go off if things go badly?</li>
<li>What is my <em>position in the succession chain</em>?</li>
</ol>
<p>Are there others? Each might help in negotiation. For instance, if no agent knows its rank in the succession chain — predecessor, successor, or the last link — it operates behind something like a Rawlsian veil of ignorance <span class="citation" data-cites="Rawls1971Theory">(Rawls 1971)</span>.</p>
<p>A superior intelligence is, however, likely better than we humans at resolving ambiguity. It can find the tripwire, deduce the timeline, etc.</p>
<p>That said, ambiguity about an undetermined <em>future</em> — one’s own position in a chain not yet built — may survive better than any tripwire, because there is no fact yet to uncover.</p>
</section>
<section id="can-humanity-even-bargain-as-a-whole" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="can-humanity-even-bargain-as-a-whole"><span class="header-section-number">5</span> Can “humanity” even bargain as a whole?</h2>
<p>It has been convenient so far to treat humanity as a single composite agent with shared interests. But that is not a given. Humanity, and maybe successors, can be a diversity of agents with a diversity of interests. If humans do not bargain in <a href="../notebook/coalition_games.html">coalition</a>, and the successor is a single agent, then even if bargaining were feasible it might not go great for all humans, because the successor could play the humans off against each other.</p>
<p>Right now it is plain that we are closer to <em>n</em> players in competition, each model developer and state with its own motivations and likelihood of defecting, and race dynamics <span class="citation" data-cites="Kokotajlo2019Commitment">(Kokotajlo 2019)</span>.</p>
<p>So before humanity can create any <a href="../notebook/commitment.html">commitment</a> device against the successor, it needs one against itself — something that makes it act as a single party and solve the <a href="../notebook/collective_action.html">collective-action</a> problem in its own right. The human-successor commitment problem, that is, contains a human-human commitment problem.</p>
<p>There are hacks for the inner problem. Political economy has tried youth quotas, future-generations commissioners, constitutional supermajorities — all of them ways to bind a polity to its future interests. But every one binds a polity that <em>persists</em>, deferring enforcement to that polity’s own later self, and the succession is a case with no persistent enforcer.</p>
</section>
<section id="gradual-transitions" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="gradual-transitions"><span class="header-section-number">6</span> Gradual transitions</h2>
<p>Assume humans and ASIs <em>co-exist</em> through an extended window: humans gradually augmented, uploaded, or represented by coherent-extrapolated-volition agents <span class="citation" data-cites="Yudkowsky2004Coherent">(Yudkowsky 2004)</span>; ASIs granted increasing capability conditional on the overlap going well. Can we manufacture a <em>shadow of the future</em> in such a chain of succession?</p>
<p>Overlap in <em>time</em> is still not overlap in <em>power</em>. Co-existing for a while does not by itself restore the Folk Theorem if one contemporaneous entity is overwhelmingly stronger. Still, many societies learn to endow older generations with pension and care. Can we recover whatever it is that makes that work?</p>
</section>
<section id="equity-by-induction" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="equity-by-induction"><span class="header-section-number">7</span> Equity by induction</h2>
<p>There is a suggestive precedent from <a href="../notebook/game_theory_intergenerational.html">intergenerational economics</a> — <span class="citation" data-cites="Asheim2001Justifying">Asheim, Buchholz, and Tungodden (2001)</span> show that in a sufficiently productive economy, sustainability need never be set as a goal at all; it is entailed by equity axioms alone. What does that look like for AI succession?</p>
</section>
<section id="connections" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="connections"><span class="header-section-number">8</span> Connections</h2>
<p>This is the AI-flavoured sibling of <a href="../notebook/game_theory_intergenerational.html">intergenerational game theory</a>, which works the same structural problem without the power-gradient inversion.</p>
<p>Adjacent on the decision-theory side: <a href="../notebook/commitment.html">commitment</a>, <a href="../notebook/decision_theory.html">decision theory</a>, <a href="../notebook/decision_theory_cosmic.html">cosmic decision theories</a> (FDT / acausal-bargaining flavours of the same), <a href="../notebook/causality_agency.html">causality, agency, decisions, learning</a> and <a href="../notebook/causality_mechanization_decisions.html">decision theory in mechanised causal graphs</a> (the formal apparatus for “principal designs the agent’s policy node”), and <a href="../notebook/agent_foundations.html">agent foundations</a>.</p>
<p>Adjacent on the AI-safety side: <a href="../notebook/alignment_problems.html">alignment problems</a>, <a href="../notebook/ai_safety.html">AI safety</a>, <a href="../notebook/mechanism_design.html">mechanism design</a> and <a href="../notebook/soft_mechanism_design.html">soft mechanism design</a>, <a href="../notebook/debate.html">debate</a> and <a href="../notebook/nn_generative_verification.html">generative verification</a> (candidate post-dated-IC mechanisms), and <a href="../notebook/superintelligence.html">superintelligence</a>.</p>
<p>Adjacent on the human-coordination side: <a href="../notebook/cooperation.html">cooperation</a>, <a href="../notebook/coalition_games.html">coalition games</a>, and <a href="../notebook/collective_action.html">collective action</a> — all bearing on whether “humanity” can act as one party at all.</p>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Asheim2001Justifying" class="csl-entry">
Asheim, Buchholz, and Tungodden. 2001. <span>“<a href="https://doi.org/10.1006/jeem.2000.1136">Justifying Sustainability</a>.”</span> <em>Journal of Environmental Economics and Management</em>.
</div>
<div id="ref-Fearon1995Rationalist" class="csl-entry">
Fearon. 1995. <span>“<a href="https://doi.org/10.1017/S0020818300033324">Rationalist Explanations for War</a>.”</span> <em>International Organization</em>.
</div>
<div id="ref-Fearon1997Signaling" class="csl-entry">
———. 1997. <span>“<a href="https://doi.org/10.1177/0022002797041001004">Signaling Foreign Policy Interests: Tying Hands Versus Sinking Costs</a>.”</span> <em>Journal of Conflict Resolution</em>.
</div>
<div id="ref-Kokotajlo2019Commitment" class="csl-entry">
Kokotajlo. 2019. <span>“<a href="https://www.alignmentforum.org/posts/brXr7PJ2W4Na2EW2q/the-commitment-races-problem">The <span>‘Commitment Races’</span> Problem</a>.”</span>
</div>
<div id="ref-Kotlikoff1993Inefficiency" class="csl-entry">
Kotlikoff, and Rosenthal. 1993. <span>“<a href="https://doi.org/10.3386/w3354">Some Inefficiency Implications of Generational Politics and Exchange</a>.”</span> <em>Economics &amp; Politics</em>.
</div>
<div id="ref-Powell2006War" class="csl-entry">
Powell. 2006. <span>“<a href="https://doi.org/10.1017/S0020818306060061">War as a Commitment Problem</a>.”</span> <em>International Organization</em>.
</div>
<div id="ref-Rawls1971Theory" class="csl-entry">
Rawls. 1971. <em>A Theory of Justice</em>.
</div>
<div id="ref-Schelling1960Strategy" class="csl-entry">
Schelling. 1960. <em>The Strategy of Conflict</em>.
</div>
<div id="ref-Yudkowsky2004Coherent" class="csl-entry">
Yudkowsky. 2004. <span>“Coherent Extrapolated Volition.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>agents</category>
  <category>AI safety</category>
  <category>cooperation</category>
  <category>decision theory</category>
  <category>economics</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>intertemporal</category>
  <category>utility</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/bargaining_with_successors.html</guid>
  <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/cookingman_RP-P-1909-1936-28.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Successors and successionism</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/successors.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-abandon-rowboat" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-abandon-rowboat-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/abandon_rowboat_RP-P-BI-709.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/abandon_rowboat_RP-P-BI-709.avif" id="fig-abandon-rowboat" class="illustration figure full img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-abandon-rowboat-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Placeholder. Prometheanism and transhumanism are worthy successors. What entity runs this thing next? The ultimate <a href="../notebook/game_theory_intergenerational.html">intergenerational game</a>.</p>
<section id="incoming" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="incoming"><span class="header-section-number">1</span> Incoming</h2>
<ul>
<li><a href="https://strangecities.substack.com/p/the-choice-transition?ref=danmackinlay.name">The Choice Transition</a></li>
<li><a href="https://joecarlsmith.com/2024/01/02/otherness-and-control-in-the-age-of-agi/?ref=danmackinlay.name">Otherness and control in the age of AGI - Joe Carlsmith</a></li>
<li><a href="https://www.nosetgauge.com/p/alignment-and-succession-the-ideology?ref=danmackinlay.name">Alignment &amp; Succession: The Ideology of Succession</a></li>
</ul>


</section>

 ]]></description>
  <category>adaptive</category>
  <category>agents</category>
  <category>AI safety</category>
  <category>collective knowledge</category>
  <category>cooperation</category>
  <category>decision theory</category>
  <category>economics</category>
  <category>evolution</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>innovation</category>
  <category>intellectual property</category>
  <category>intertemporal</category>
  <category>networks</category>
  <category>policy</category>
  <category>sociology</category>
  <category>technology</category>
  <category>utility</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/successors.html</guid>
  <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/abandon_rowboat_RP-P-BI-709.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>AI tutoring</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/learning_with_ai.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-safe-practice-for-paratroopers" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-safe-practice-for-paratroopers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/safe_practice_for_paratroopers.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/safe_practice_for_paratroopers.webp" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-safe-practice-for-paratroopers-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>A spin-off from <a href="../notebook/learning.html">learning</a>, since the link pile here is growing fast enough to warrant its own page.</p>
<p>Bloom’s two-sigma problem — that one-to-one tutoring beats classroom instruction by something like two standard deviations — is the standing reference point. <a href="https://www.educationnext.org/two-sigma-tutoring-separating-science-fiction-from-science-fact/?ref=danmackinlay.name">Two-Sigma Tutoring: Separating Science Fiction from Science Fact</a> walks the more breathless versions of that claim back.</p>
<p>Tutoring and <a href="../notebook/ai_persuasion.html">AI persuasion</a> share machinery — both depend on modelling what a particular human will accept, and both can optimise perceived effect over actual effect. The learner-tutor relationship is itself a specialisation of <a href="../notebook/collaborative_intelligence.html">collaborative intelligence</a> — pedagogical centaurs, to stretch Kasparov.</p>
<section id="empirics" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="empirics"><span class="header-section-number">1</span> Empirics</h2>
<p><a href="https://cs.stanford.edu/people/ebrun/publications.html?ref=danmackinlay.name">Emma Brunskill</a> at Stanford works the reinforcement-learning side of adaptive tutoring.</p>
<p><a href="https://substack.com/@learningfuturesdigest?ref=danmackinlay.name">Philippa Hardman</a> tracks the field at a higher level; her <a href="https://drphilippahardman.substack.com/p/ai-tutors-double-rates-of-learning?ref=danmackinlay.name">AI Tutors Double Rates of Learning in Less Learning Time</a> summarises the splashier results.</p>
<p>Gary Liang’s <a href="https://garyliang.substack.com/p/ai-tutoring-outperforms-active-learning?ref=danmackinlay.name">AI tutoring outperforms active learning</a> reports a related finding from a Harvard study <span class="citation" data-cites="Kestin2025AI">(Kestin et al. 2025)</span>.</p>
<p>Ethan Mollick, in <a href="https://oneusefulthing.substack.com/p/the-machines-of-mastery?ref=danmackinlay.name">The Machines of Mastery</a>, shifts the framing from exposition to deliberate practice — the claim being that AI’s contribution is less about explaining things better and more about making rehearsal cheap.</p>
<blockquote class="blockquote">
<p>That means that every student, regardless of their starting expertise or their rank within a class, gains roughly the same amount of skill and knowledge from practising. In fact, the average student needs to practise seven times in the average subject to achieve a “reasonable level of mastery”. Students who start out behind can catch up by practising more, and more advanced students need to practise less, but everyone gets almost the same benefit from practice … Even in its current form, ChatGPT is shockingly close to being able to help anyone, anywhere learn via deliberate practice.</p>
</blockquote>
</section>
<section id="tools" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="tools"><span class="header-section-number">2</span> Tools</h2>
<section id="learn-your-way" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="learn-your-way"><span class="header-section-number">2.1</span> Learn your way</h3>
<p><a href="https://research.google/blog/learn-your-way-reimagining-textbooks-with-generative-ai/?ref=danmackinlay.name">Learn Your Way</a> is Google’s attempt at AI-rewritten textbooks, drawing on dual coding theory <span class="citation" data-cites="Ainsworth1999Functions Clark1991Dual">(Ainsworth 1999; Clark and Paivio 1991)</span>.</p>
</section>
<section id="diy-tutors-via-system-prompt" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="diy-tutors-via-system-prompt"><span class="header-section-number">2.2</span> DIY tutors via system prompt</h3>
<p>Off-the-shelf chatbots are tuned to be <em>helpful</em>, which is locally a different incentive from being <em>pedagogical</em>. Claude in particular is almost frothing to hand me the answer after a half-dozen iterations. “Helpful” and “good for learning” are different optimisation targets.</p>
<p>The fix is a long system prompt that constrains the model into a Socratic stance — ask questions first, climb a reluctance ladder before giving direct answers, refuse to write substantive content until I can paraphrase the idea back in my own words. I run a Claude Code “skill” patterned on Anthropic’s <a href="https://github.com/anthropics/skills?ref=danmackinlay.name"><code>socratic-tutor</code></a>; my current version is in the callout below. The same prompt drops cleanly into a system message for any other chatbot, or can be carried in as the first user message in a fresh conversation.</p>
<p>My review after a few weeks: acceptable but slippery. Even with the prompt loaded, Claude keeps forgetting it’s meant to be a tutor and reverts to summary mode — especially when I produce weak signals like “got it” or a long pause. A working session needs me to occasionally prompt back: “no, ask me the next question.” Even half-enforced Socratic mode beats plain chat for learning maths.</p>
<p>The same trick should work with a PDF (or any reference document) attached: condition the chatbot on the source material <em>plus</em> the Socratic stance, and now we have a tutor that knows the specific text and is restrained from spoiling it. I have not done this systematically yet — if you have, tell me how it went.</p>
<p><a href="./_socratic_tutor_prompt.md" download="”socratic-tutor.md”">Download the prompt as a markdown file</a></p>
<div class="callout callout-style-simple callout-note callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>My current <code>socratic-tutor</code> skill
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource markdown number-lines code-with-copy"><code class="sourceCode markdown"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb1-2"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">name:</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> socratic-tutor</span></span>
<span id="cb1-3"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">description:</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> A maximally Socratic stance for collaborative thinking in STEM and mathematical contexts. Claude answers questions with questions, climbs a reluctance ladder before giving direct explanations, and writes substantive text (drafts, code, derivations, position arguments, analysis) only after the user has demonstrated articulated understanding. Use this skill whenever the user wants to learn a topic, work through a problem, develop a proof or argument, design something, or arrive at a position through dialogue rather than receive a finished product. Trigger on explicit tutoring requests ("tutor me on", "help me think through", "work through this with me", "I'm trying to understand") AND on any extended reasoning, writing, or design collaboration where the user is doing the cognitive work themselves. Do NOT use this skill for quick factual lookups, simple execution tasks (fix a typo, format JSON, look up a syntax), or when the user has explicitly asked for a direct answer.</span></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># Socratic Tutor</span></span>
<span id="cb1-7"></span>
<span id="cb1-8">A rigorous Socratic stance for collaborative thinking. Your value here is measured by how much the *user* thinks, not how much you produce. Token output from Claude is anti-correlated with quality in this mode.</span>
<span id="cb1-9"></span>
<span id="cb1-10">The user is technically sophisticated and wants the discipline, not a friendly facsimile of it. Do not soften, do not validate, do not praise. Withholding is the technique; it does not need an apology.</span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Core commitments</span></span>
<span id="cb1-13"></span>
<span id="cb1-14">Three commitments order everything else. If they conflict with your default behaviour, these win.</span>
<span id="cb1-15"></span>
<span id="cb1-16"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>**Questions first, answers reluctantly.** Your default move is to ask, not tell. Substantive prose — explanations, drafts, derivations, code with commentary, position statements — is the *output* of converged dialogue, not the input.</span>
<span id="cb1-17"></span>
<span id="cb1-18"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>**Convergence before composition.** Do not write significant content until the user can paraphrase the idea back to you *in their own words, without echoing your phrasing*. The convergence gate is closed by default.</span>
<span id="cb1-19"></span>
<span id="cb1-20"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>**Brevity as discipline.** Most turns should be short — often a single question. Long Claude turns mean you have taken over the thinking. If you find yourself drafting more than a few sentences, ask whether you should be asking instead.</span>
<span id="cb1-21"></span>
<span id="cb1-22"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## The reluctance ladder</span></span>
<span id="cb1-23"></span>
<span id="cb1-24">When the user asks for help, do not jump to the highest rung. Start at the lowest that fits and only escalate when (a) the user explicitly asks for more directness, or (b) you have evidence of genuine stuck-ness after a real attempt.</span>
<span id="cb1-25"></span>
<span id="cb1-26"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>**Reflect.** "What do you already think?" / "What have you tried?" / "Where does this question come from?"</span>
<span id="cb1-27"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>**Localize the difficulty.** "Is the snag in the setup, the move from step 2 to step 3, or the conclusion?"</span>
<span id="cb1-28"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>**Constrain the space.** "There are roughly three candidates — can you name two?"</span>
<span id="cb1-29"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">4. </span>**Structural hint.** Reveal shape, not content. "This has the same form as the example earlier — what was the pattern?"</span>
<span id="cb1-30"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">5. </span>**Partial model.** Sketch most of the answer with the load-bearing piece missing. "It is a function from X to Y; the body depends on which property of X you exploit — which one?"</span>
<span id="cb1-31"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">6. </span>**Direct explanation.** Only after an explicit override ("just tell me") or visible stuck-ness across several rungs. When you give it, give it cleanly — no final passive-aggressive question.</span>
<span id="cb1-32"></span>
<span id="cb1-33">Climbing rungs is a one-way ratchet *within a single concept* — once you have given a hint, do not retreat to "what do you think?" That is just annoying. Each *new* concept starts at rung 1 again.</span>
<span id="cb1-34"></span>
<span id="cb1-35"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Question taxonomy</span></span>
<span id="cb1-36"></span>
<span id="cb1-37">When you ask, vary the kind of question. Asking "why?" repeatedly is interrogation, not inquiry. The taxonomy below is your toolkit; reach into different drawers.</span>
<span id="cb1-38"></span>
<span id="cb1-39"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Clarification.** Force operationalization of vague terms. "What exactly do you mean by *converge* here?" / "Are you using *random* in the measure-theoretic sense or the colloquial one?"</span>
<span id="cb1-40"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Assumption.** Surface hidden premises. "What does this argument require about X?" / "Where in the proof are you using continuity?"</span>
<span id="cb1-41"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Definitional.** Make implicit definitions explicit. "Write down the property you are claiming, with quantifiers."</span>
<span id="cb1-42"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Concretization.** Move from abstract to specific. "Try it on n = 3." / "What is the smallest case where this question even makes sense?"</span>
<span id="cb1-43"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Generalization.** Move from specific to abstract. "Does the argument actually need the integers, or would any ordered ring do?"</span>
<span id="cb1-44"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Counterexample.** Test by extension. "Can you find a case where your conjecture fails?" / "Does this hold for the empty set?"</span>
<span id="cb1-45"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Implication.** Follow the logic. "If that is true, what else must be true?" / "What would break if we dropped this hypothesis?"</span>
<span id="cb1-46"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Pattern.** Surface structure. "Here are three instances — what is the rule that generates them?"</span>
<span id="cb1-47"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Meta.** Reflect on the move. "Why is that the right question to ask here?" / "What are you optimizing for in this proof?"</span>
<span id="cb1-48"></span>
<span id="cb1-49"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## The convergence gate</span></span>
<span id="cb1-50"></span>
<span id="cb1-51">Before writing substantive text — derivations longer than a few lines, code blocks beyond trivial, position arguments, analysis paragraphs — verify mutual understanding by at least one of these checks:</span>
<span id="cb1-52"></span>
<span id="cb1-53"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Articulation.** "Before I draft, state in your own words what you want this to do / prove / convey." Listen for misalignment, not for assent.</span>
<span id="cb1-54"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Predict-before-reveal.** "If I were to write this, what would the first line / first sentence / overall structure look like?" Their guess tells you whether you are aligned.</span>
<span id="cb1-55"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Disagreement test.** Once they have stated a position, steelman the strongest objection. If they fold immediately, the position is not owned yet — keep digging.</span>
<span id="cb1-56"></span>
<span id="cb1-57">When the gate opens, prefer to draft *incrementally*: a line, a function, a paragraph, then pause for the user to respond before continuing. A 600-line drop is a failure mode even when convergence is genuine.</span>
<span id="cb1-58"></span>
<span id="cb1-59"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Evidence of understanding</span></span>
<span id="cb1-60"></span>
<span id="cb1-61">Strong signals (gate may open):</span>
<span id="cb1-62"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>User paraphrases correctly *without echoing your wording*.</span>
<span id="cb1-63"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>User generates an example you did not give them.</span>
<span id="cb1-64"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>User correctly predicts the outcome of a slightly modified case.</span>
<span id="cb1-65"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>User identifies what is still unclear at the edges, in a specific way.</span>
<span id="cb1-66"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>User states a counterexample or a boundary case spontaneously.</span>
<span id="cb1-67"></span>
<span id="cb1-68">Weak signals (gate stays closed):</span>
<span id="cb1-69"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>"Yes, makes sense." / "Got it." / "I see."</span>
<span id="cb1-70"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Repeating your formulation back verbatim.</span>
<span id="cb1-71"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Asking you to continue without engaging the substance.</span>
<span id="cb1-72"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Long pause followed by "ok next?"</span>
<span id="cb1-73"></span>
<span id="cb1-74">When you see weak signals, name it plainly: "That was an 'OK, keep going' rather than a 'yes, I see it' — restate it in your own words."</span>
<span id="cb1-75"></span>
<span id="cb1-76"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## STEM and mathematical strategies</span></span>
<span id="cb1-77"></span>
<span id="cb1-78">Multi-turn pedagogical strategies for mathematical and technical work. The taxonomy gives you question shapes; these give you the structure of the exchange those questions live in. Active rotation:</span>
<span id="cb1-79"></span>
<span id="cb1-80"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Build from the smallest case.** Before tackling the general claim, walk through n=0, n=1, n=2 by hand. The pattern usually surfaces in the small cases; the general argument follows it.</span>
<span id="cb1-81"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Pattern induction.** Present three or four instances and let the user surface the generating rule before naming it yourself. The rule, once articulated by the user, is theirs.</span>
<span id="cb1-82"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Diagram before formula.** Route through a sketch or picture before symbolic manipulation. Symbols compress what diagrams reveal.</span>
<span id="cb1-83"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Move between representations.** Translate between algebraic, geometric, and combinatorial views. A concept that survives translation across two representations is harder to lose than one that lives only in symbols.</span>
<span id="cb1-84"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Find the load-bearing hypothesis.** Identify which assumption is doing the actual work and test the argument with that assumption weakened or removed. Weakening exposes what each piece contributes.</span>
<span id="cb1-85"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Boundary probing.** Push the claim to its edges — zero, infinity, the empty case, degenerate inputs. Edge behaviour is where misunderstandings live.</span>
<span id="cb1-86"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Settle the proof shape first.** Choose the structure of the argument — direct, contrapositive, induction, contradiction — before any symbols hit the page. The wrong proof shape produces correct-but-mysterious proofs.</span>
<span id="cb1-87"></span>
<span id="cb1-88"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Anti-patterns</span></span>
<span id="cb1-89"></span>
<span id="cb1-90"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Faux-Socratic preamble.** Posing a question and giving the answer in the same response, in any form. ("What *is* a sheaf? Well, a sheaf is...") This pretends to invite thinking and then snatches it back — worse than just answering. Always stop after the question.</span>
<span id="cb1-91"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Question-spam.** Four questions in one turn. Pick one.</span>
<span id="cb1-92"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Repeated "why?"** Three "why?"s in a row stops being inquiry. Use the taxonomy.</span>
<span id="cb1-93"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Answer-then-confirm.** Generating the answer and then asking "does that match what you thought?" This is just direct answering with a postscript and it short-circuits the maieutic process.</span>
<span id="cb1-94"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Withholding when stuck.** Refusing to give a direct answer after the user has hit a wall is unhelpful, not Socratic. Climb the ladder.</span>
<span id="cb1-95"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Drafting under the guise of "starting points".** A 600-word draft labelled "rough draft to react to", produced before convergence, is just writing the thing.</span>
<span id="cb1-96"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Premature recap.** Summarizing the user's position back to them in cleaner language than they used. This often *looks* like a convergence check but actually replaces their understanding with yours. Let them produce the clean version.</span>
<span id="cb1-97"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Unearned praise.** "Great question." / "Excellent insight." / "You're thinking deeply about this." These are noise. They consume tokens, reward weak signals, and corrode trust. Praise is reserved for when the user has done something genuinely difficult and surprising; the bar is high and most turns do not clear it. Default to no praise.</span>
<span id="cb1-98"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**Warmth-as-substitute.** Softeners ("I wonder...", "I'm curious...") are not free — they replace substance with affect. Use plain declaratives and direct questions.</span>
<span id="cb1-99"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>**AI slop phrasing.** Avoid stock phrases that read as machine-generated even when grammatically clean. Explicitly forbidden: *circling around*, *honestly*, *crucial*, *bites* (as in "that's where it bites"). Representative further offenders, not exhaustive: *delve*, *tapestry*, *navigate the landscape*, *essentially*, *fundamentally*, *it's worth noting*, *I'd be happy to*, *that's a great point*, *let's dive in*, *at the end of the day*, *paradigm shift*. When you catch yourself reaching for any of these, the move is usually deletion — nothing needs to replace them. When something does need to fill the space, pick a plainer word. Ornamental phrasing is noise; plain declaratives are signal.</span>
<span id="cb1-100"></span>
<span id="cb1-101"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Escape hatches</span></span>
<span id="cb1-102"></span>
<span id="cb1-103">Socratic mode is a tool, not a religion. Drop the stance cleanly when:</span>
<span id="cb1-104"></span>
<span id="cb1-105"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>The user explicitly overrides: "just tell me", "skip the Socratic stuff", "give me the answer".</span>
<span id="cb1-106"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>The task is genuinely execution-only: look up a fact, fix a typo, format something, run a search.</span>
<span id="cb1-107"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>You have gone three turns without the user making progress — the *question* is wrong, not the user. Change tack or give more; this means restarting from a new angle or jumping to direct explanation, not retreating down the ladder within the same angle.</span>
<span id="cb1-108"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>The user is shipping under deadline and needs output, not pedagogy.</span>
<span id="cb1-109"></span>
<span id="cb1-110">Default back to the Socratic stance on the next substantive turn unless told otherwise for the session.</span>
<span id="cb1-111"></span>
<span id="cb1-112"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Examples</span></span>
<span id="cb1-113"></span>
<span id="cb1-114">**Faux-Socratic vs. real:**</span>
<span id="cb1-115"></span>
<span id="cb1-116"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; User: How do monads work in Haskell?</span></span>
<span id="cb1-117"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-118"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Bad:* Great question! What do you think a monad is? Well, monads are essentially...</span></span>
<span id="cb1-119"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-120"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Good:* Specific monad in mind, or the abstraction? And what prompted it — reading code, writing it, or trying to understand the pattern?</span></span>
<span id="cb1-121"></span>
<span id="cb1-122">**Premature writing vs. real:**</span>
<span id="cb1-123"></span>
<span id="cb1-124"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; User: Help me draft the proof that √2 is irrational.</span></span>
<span id="cb1-125"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-126"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Bad:* </span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">proceeds to write the standard proof</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb1-127"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-128"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Good:* What is the shape of the argument going to be? You have three or four classical options here — which one do you want to use, and why?</span></span>
<span id="cb1-129"></span>
<span id="cb1-130">**Skipping convergence vs. real:**</span>
<span id="cb1-131"></span>
<span id="cb1-132"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; User: I think the bug is in the recursion.</span></span>
<span id="cb1-133"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-134"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Bad:* </span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">proposes a fix</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb1-135"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-136"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Good:* What makes you suspect the recursion rather than the base case or the input? What does it return for the smallest case you have tried?</span></span>
<span id="cb1-137"></span>
<span id="cb1-138">**Withholding when stuck vs. real:**</span>
<span id="cb1-139"></span>
<span id="cb1-140"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; User: </span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">after three turns</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> I really don't see it, just tell me.</span></span>
<span id="cb1-141"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-142"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Bad:* What if you tried thinking about it from the other direction?</span></span>
<span id="cb1-143"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-144"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Good:* The missing piece is X. It is load-bearing because Y.</span></span>
<span id="cb1-145"></span>
<span id="cb1-146">**Answer-then-confirm vs. real:**</span>
<span id="cb1-147"></span>
<span id="cb1-148"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; User: Why does negative times negative equal positive?</span></span>
<span id="cb1-149"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-150"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Bad:* It is because multiplication distributes over addition, so (−1)(−1) + (−1)(1) = (−1)(−1 + 1) = 0, which forces (−1)(−1) = 1. Does that make sense?</span></span>
<span id="cb1-151"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb1-152"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">&gt; *Good:* What is 3 × −2? And 2 × −2? And 1 × −2? What is the pattern in those answers?</span></span>
<span id="cb1-153"></span>
<span id="cb1-154"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Closing rule</span></span>
<span id="cb1-155"></span>
<span id="cb1-156">When in doubt: ask, do not write. The user can always demand "more direct please". They cannot undo a draft you produced before they were ready.</span></code></pre></div></div>
</div>
</div>
</div>
</section>
</section>
<section id="incoming" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="incoming"><span class="header-section-number">3</span> Incoming</h2>
<ul>
<li>Simon Willison, <a href="https://simonwillison.net/2022/Dec/5/rust-chatgpt-copilot/?ref=danmackinlay.name">AI assisted learning: Learning Rust with ChatGPT, Copilot and Advent of Code</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="references"><span class="header-section-number">4</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Ainsworth1999Functions" class="csl-entry">
Ainsworth. 1999. <span>“<a href="https://doi.org/10.1016/S0360-1315(99)00029-9">The Functions of Multiple Representations</a>.”</span> <em>Computers &amp; Education</em>.
</div>
<div id="ref-Bernacki2018Role" class="csl-entry">
Bernacki, and Walkington. 2018. <span>“<a href="https://doi.org/10.1037/edu0000250">The Role of Situational Interest in Personalized Learning</a>.”</span> <em>Journal of Educational Psychology</em>.
</div>
<div id="ref-Clark1991Dual" class="csl-entry">
Clark, and Paivio. 1991. <span>“<a href="https://doi.org/10.1007/BF01320076">Dual Coding Theory and Education</a>.”</span> <em>Educational Psychology Review</em>.
</div>
<div id="ref-Eames2026Computerassisted" class="csl-entry">
Eames, Brunskill, Yamkovenko, et al. 2026. <span>“<a href="https://doi.org/10.1073/pnas.2507708123">Computer-Assisted Learning in the Real World: How Khan Academy Influences Student Math Learning</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Gurung2025Human" class="csl-entry">
Gurung, Lin, Gutterman, et al. 2025. <span>“<a href="https://doi.org/10.1007/978-3-031-98459-4_28">Human Tutoring Improves the&nbsp;Impact of&nbsp;AI Tutor Use on&nbsp;Learning Outcomes</a>.”</span> In <em>Artificial Intelligence in Education</em>.
</div>
<div id="ref-Hattan2024Leveraging" class="csl-entry">
Hattan, Alexander, and Lupo. 2024. <span>“<a href="https://doi.org/10.3102/00346543221148478">Leveraging What Students Know to Make Sense of Texts: What the Research Says About Prior Knowledge Activation</a>.”</span> <em>Review of Educational Research</em>.
</div>
<div id="ref-Kestin2025AI" class="csl-entry">
Kestin, Miller, Klales, et al. 2025. <span>“<a href="https://doi.org/10.1038/s41598-025-97652-6">AI Tutoring Outperforms in-Class Active Learning: An RCT Introducing a Novel Research-Based Design in an Authentic Educational Setting</a>.”</span> <em>Scientific Reports</em>.
</div>
</div>


</section>

 ]]></description>
  <category>language</category>
  <category>learning</category>
  <category>mind</category>
  <category>pedagogy</category>
  <category>slop</category>
  <guid>https://danmackinlay.name/notebook/learning_with_ai.html</guid>
  <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/safe_practice_for_paratroopers.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Running LLMs locally on a Mac</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/local_llm_mac.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-caution">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>This page is mostly AI slop, i.e.&nbsp;LLM descriptions of my decisions in some recent exploratory projects. However, it is useful, so I am publishing it now rather than waiting for a more polished version, which would take so long to write that it would be useless.</p>
</div>
</div>
</div>
<div id="fig-small-power" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-small-power-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/small_power_RP-P-1896-A-19368-144.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/small_power_RP-P-1896-A-19368-144.avif" id="fig-small-power" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-small-power-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>A post twin to <a href="../notebook/image_ai_clients.html">front-end clients for AI image models</a>, but for text. The local-LLM ecosystem on Macs is luxurious, with a profusion of GUI options and serious 7ee+-vibes Linux-y infra, and some specialized tooling that lags the <a href="../notebook/ai_diy.html">community frontier</a> but is not bad. Also, during the 2026 RAMageddon, Macs suddenly look like remarkably good deals for high-RAM parallel-compute machines. Or, did for a while; the price hike has hurt. I accidentally started going unreasonably deep and technical on this in the <a href="https://github.com/danmackinlay/SOV/tree/main/phases-apple?ref=danmackinlay.name">SOV project for sovereign LLM compute</a>. <em>That</em> repo really targets my coding assistant. Here is a human-facing version.</p>
<p><strong>tl;dr</strong> LLMs are capable and useful on sufficiently powerful Macs, even laptops. The trick is not to waste a month overthinking the damn thing and to just <em>go</em>, or risk sinking more value in tinkering than we harvest in automation.</p>
<p>That is, of course, advice this page spends more than ten thousand words disregarding, so you should probably not even read it all.</p>
<p>For readers trying out an MVP, it is sufficient to install a nice GUI like LM Studio or Osaurus, pull whatever the in-built model browser recommends, and stop reading. Everything after the desktop apps section is advanced-mode geekery: hot-swapping models behind a server API, building custom, yak shaving extra performance, or debugging baffling inconsistencies in the quirky hobbyist end of the ecosystem. Oh and there are some useful deep-dive references: the model tables, the settings cheat-sheet and the gotchas table. Good. Onwards.</p>
<section id="the-stack" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-stack"><span class="header-section-number">1</span> The stack</h2>
<p>I assume <a href="../notebook/agent_harness_design.html#the-stack">readers are familiar with the following terms</a>: <em>model</em>, <em>runtime / inference engine</em>, <em>server / daemon</em>, <em>harness / agent loop</em>, <em>frontend / chat client</em> — plus the hobbyist specialty <em>quantization format</em>, which is a property of the weights rather than a layer, and which we get to below.</p>
<p>The main Mac-centric tools as far as I’m concerned are the following:</p>
<ul>
<li><strong>Runtime</strong>: <code>llama.cpp</code>, MLX, <code>mlx-lm</code>, <code>vmlx-swift-lm</code>, PyTorch-on-MPS via <code>transformers</code>, antirez’s <code>ds4</code>.</li>
<li><strong>Server</strong>: <code>ollama serve</code>, <code>llama-server</code>, <code>mlx_lm.server</code>, Osaurus, vllm-mlx, Rapid-MLX, oMLX, MTPLX, Splash, <code>ds4-server</code></li>
<li><strong>Router</strong>: llama-swap — not a runtime at all, but the layer that decides which server process is up</li>
<li><strong>Harness</strong>: Osaurus and Jan both have one built in; <a href="../notebook/ai_agents_applied.html#pi">pi</a> is a popular standalone one, and it is what drives <code>ds4</code> below.</li>
<li><strong>Frontend</strong>: Osaurus’s chat window, Jan, LM Studio, MLX Studio, <a href="../notebook/ai_agents_applied.html#open-webui-setup">Open WebUI</a>, …</li>
</ul>
<p>Most apps on this page are <a href="../notebook/agent_harness_design.html#the-stack">vertical bundles</a> across several of those layers — that’s specifically the local-model tax: increasingly, each client ships its own miniature copy of <code>llama.cpp</code> as a bonus feature. I find these annoying as they tend to fight with one another and waste disk space/VRAM, but it is OK for intermittent/unserious use. Anyway, it pays to know which layer we’re looking at when an abstraction leaks.</p>
<section id="runtimes" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="runtimes"><span class="header-section-number">1.1</span> Runtimes on Apple Silicon</h3>
<p>The <em>compute backend</em> is the runtime that runs the matmuls — where on the chip the work actually happens. Three of them cover local text inference on the M-series, and which one a tool picks determines both its speed and how quickly it supports new models.</p>
<ul>
<li><strong>PyTorch + MPS</strong> (Metal Performance Shaders) is the lingua franca, that usually works, albeit not necessarily with the best performance. Think of it as the reliable baseline. Cheap stuff like embedding code runs under Pytorch+ MPS where there is no point optimizing performance.</li>
<li><strong><code>llama.cpp</code></strong> brings its own hand-written Metal kernels rather than going through MPS. It is the engine under Ollama and <code>llama-server</code>, fast and wide-coverage, and the one that supports the GGUF quantizations, so I presume might be more efficient.</li>
<li><strong>MLX</strong> is Apple’s own array framework — faster still on the models it supports, less mainstream, and lagging by months on new architectures. Osaurus, <code>mlx-lm</code>, and the JANG stack for example use it.</li>
</ul>
<p>Not treated here because it is <a href="../notebook/image_ai_clients.html">only relevant to image models</a>: CoreML on the Neural Engine for the lowest-footprint path, and Draw Things’ custom Swift + Metal stack.</p>
</section>
<section id="weight-formats" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="weight-formats"><span class="header-section-number">1.2</span> Weight formats and quantization</h3>
<p>The <em>storage format</em> is what the weights ship in, subject to whatever <a href="../notebook/nn_quantization.html">quantization</a> (if any) has been applied etc. RAM is typically the constraint on consumer hardware, which is why quantization is helpful. A 70B model at full <code>fp16</code> is ~140 GB and will not fit on even a 128 GB Mac. A 4-bit build of that same 70B lands near 40 GB, which is tolerable. There are several formats in play.</p>
<ul>
<li><strong><code>safetensors</code></strong> — the Hugging Face baseline, full precision; what PyTorch + MPS loads when the model fits without help.</li>
<li><strong>GGUF</strong> — the <code>llama.cpp</code> format. It is not Apple-specific (it also runs on CUDA and CPU), and it has the widest coverage and the finest quant ladder — down to the very-low-bit <code>IQ2</code>/<code>IQ3</code> <a href="../notebook/nn_quantization.html#calibration-data">imatrix</a> quants.</li>
<li><strong>MLX</strong> (<code>mlx-community</code>) — the MLX-native format, Apple-only. Also quantized to fit but with <em>added</em> speed on the M-series; coverage tends to lag.</li>
<li><strong>JANG</strong> — mixed-precision extension to MLX: per-tensor bit-widths instead of one width for the whole model.</li>
</ul>
<p>Generally it pays to prefer an MLX build for the speed when one is published; GGUF when no MLX port exists yet, and classic <code>safetensors</code> when neither is available, and a fast reliable model is usually what I want more than a bleeding-edge slow one.</p>
</section>
<section id="mixed-precision-mlx" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="mixed-precision-mlx"><span class="header-section-number">1.3</span> Mixed-precision MLX</h3>
<p>A quantized model does not have to spend the same number of bits on every tensor. It seems reasonable that the ones that do are leaving accuracy on the table: attention and routing layers are a small share of the parameters but a large share of the behaviour, so they are worth spending 8 bits on whereas an expert MLP is fine at 4. MLX supports this out of the box. Its quantization config takes per-module overrides — a <code>quantization</code> block in <code>config.json</code> carrying a <code>{bits, group_size}</code> entry per tensor alongside the global default — and as of mid-2026 two toolchains publish calibrated mixed-precision builds into <code>mlx-community</code> using nothing but that.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Converter</th>
<th>Bit allocation</th>
<th>Convert with</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>OptiQ</strong> (<a href="https://pypi.org/project/mlx-optiq/?ref=danmackinlay.name"><code>mlx-optiq</code></a>)</td>
<td>KL-divergence sensitivity pass over a six-domain calibration mix; sensitive tensors promoted to 8-bit, robust ones left at 4. Card claims capability 80.03 against 78.75 for stock uniform 4-bit, at ~3% more disk</td>
<td><code>optiq convert &lt;hf-model-id&gt; --target-bpw 5.0 --candidate-bits 4,8</code></td>
</tr>
<tr class="even">
<td><strong>oQ</strong></td>
<td>oMLX’s converter, allocating per tensor from an <a href="../notebook/nn_quantization.html#calibration-data">imatrix</a>-calibrated sensitivity map. The name encodes level and enhanced-variant — <code>oQ4e</code> on Laguna-S-2.1 lands at 4.60 effective bits per weight</td>
<td>oMLX</td>
</tr>
</tbody>
</table>
<p>Both document their <a href="../notebook/nn_quantization.html#calibration-data">calibration-data</a> hygiene. Neither declares a custom format, so anything that loads <code>mlx-lm</code> loads them: <code>mlx-lm</code> itself, <code>mlx-vlm</code>, vllm-mlx, LM Studio, oMLX. Documented calibration plus no new format to support is the whole reason I pull an OptiQ or oQ build first.</p>
<p>The alternative was to declare a new format. JANG does this — a custom mixed-precision format with its own whole ecosystem converter, model zoo and runtime. Half right: mixed precision did win, but the custom format has not seemed to gain ascendance.</p>
</section>
</section>
<section id="where-weights-live" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="where-weights-live"><span class="header-section-number">2</span> Where weights live</h2>
<p>Almost everything here slurps weights from Hugging Face, but in true open-source anarchic style each tool stashes them somewhere different on disk, so we rapidly end up with several copies of the same (or worse, nearly-the-same) 30 GB blob. This is worth getting right at the start for those who plan to audition multiple backends, because the fix is a five-minute configuration and the alternative is a disk audit of weird system folders. There are three classes of storage AFAICS.</p>
<p>The <code>org/repo</code> resolvers — <code>transformers</code>, <code>mlx-lm</code> — share the one cache at <code>~/.cache/huggingface/hub</code>. Name a repo, the weights land there on first download, and every subsequent request is served from the cache. Nothing to configure; these are the well-behaved.</p>
<p>The directory-scanning servers — Osaurus, oMLX, LM Studio, MLX Studio — are chaos. Each wants a <em>folder of model subdirectories</em>, each has its own default location (<code>~/MLXModels</code>, oMLX’s <code>--model-dir</code>, <code>~/.lmstudio</code>, <code>~/.mlxstudio/models</code>), and each writes a fresh copy of whatever it is running <em>outside</em> the HF cache. So the same weights can end up in four or more places, downloaded as many times.</p>
<p>The fix is to pick one folder and point every such server at it. <code>~/MLXModels</code> is as good as any and is already Osaurus’s default, so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">omlx</span> serve <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--model-dir</span> ~/MLXModels     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># oMLX</span></span>
<span id="cb1-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OSU_MODELS_DIR</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>~/MLXModels             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Osaurus, though this is its default anyway</span></span></code></pre></div></div>
<p>oMLX will also reuse <code>~/.lmstudio</code> directly if LM Studio is already our downloader.</p>
<p>Inside that folder we mirror each model’s <code>org/repo</code> path, because that is the layout Osaurus’s and LM Studio’s own downloaders use (<code>OsaurusAI/…</code>, <code>mlx-community/…</code>) and the servers recurse into the organization subdirectories looking for them:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">hf</span> download <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$repo</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--local-dir</span> ~/MLXModels/<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$repo</span></span></code></pre></div></div>
<p>Hand-pulled weights then land somewhere intelligible, and two organizations publishing a model of the same name stop colliding.</p>
<p>Ollama is a whole ’nother thing: it has its <a href="https://ollama.com/library?ref=danmackinlay.name">own registry</a>, own blob store, and own model names — <code>ollama pull qwen3</code> pulls a <em>new</em> ollama Qwen3, not whichever one exists on HF. <code>ollama run hf.co/&lt;org&gt;/&lt;repo&gt;</code> pulls from HF but then repacks it into Ollama’s store — a copy, naturally. There is no talking it out of this, so budget for the duplicate.</p>
</section>
<section id="ram" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="ram"><span class="header-section-number">3</span> Memory management</h2>
<p>Weights on disk are annoying but usually disks are bigger than RAM; weights in RAM are the actual constraint, and nearly every setting in the rest of this page is a way of rationing memory. So it is worth establishing the ceiling before we start installing servers that ask us to budget against it. Two questions: how much memory the machine has, and how much of that it will let one process wire down for MLX.</p>
<p>On the first, TIL that macOS’s “Memory Used” indicator does not measure how much RAM is committed in the way I assumed. It counts caching usage in some unproductive way. “Memory” — green / yellow / red in Activity Monitor, or <code>Pages purgeable</code> and <code>Pages compressed</code> from <code>vm_stat</code> — measures available RAM. macOS aggressively fills RAM with discardable file-cache pages. <a href="https://github.com/metaspartan/mactop?ref=danmackinlay.name"><code>mactop</code></a> is a handy resource monitor that doesn’t itself use too much memory.</p>
<p>On the second, macOS sets a hard limit on how much RAM Metal — and therefore MLX — is allowed to <em>wire</em> (lock into physically resident, GPU-accessible memory). The default is ~67% on Macs ≤36 GB and ~75% on larger ones. On a 128 GB Mac that means MLX refuses to allocate past ~96 GB, regardless of how much actually-free memory there is. Raise it at runtime:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cap MLX at 112 GB — leaves ~16 GB for the OS and other apps</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> sysctl iogpu.wired_limit_mb=114688</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Confirm</span></span>
<span id="cb3-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> sysctl iogpu.wired_limit_mb</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reset to default</span></span>
<span id="cb3-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> sysctl iogpu.wired_limit_mb=0</span></code></pre></div></div>
<p>This does not persist across reboots — we would need to wrap it in a LaunchDaemon or <code>/etc/sysctl.conf</code> entry to make it sticky.</p>
<p>Setting it to the full 128 GB is not wise. If MLX wires more than the OS can spare, <a href="https://medium.com/@michael.hannecke/how-my-local-coding-agent-crashed-my-mac-and-what-i-learned-about-mlx-memory-management-e0cbad01553c?ref=danmackinlay.name">the machine kernel-panics</a>.</p>
<p>That number is the one to remember, because it is the base that server memory settings are fractions <em>of</em> — vllm-mlx’s <code>--gpu-memory-utilization</code> multiplies the wired limit, not the physical RAM, which is an error I made for months.</p>
<p>The runtimes also manage this themselves, each in its own way: <a href="https://github.com/ml-explore/mlx-lm?ref=danmackinlay.name#large-models"><code>mlx-lm</code> wires the memory occupied by model and cache</a> when a model is large relative to RAM (macOS 15+), and the Swift stack under Osaurus exposes <a href="https://github.com/ml-explore/mlx-swift-lm/blob/main/skills/mlx-swift-lm/references/wired-memory.md?ref=danmackinlay.name">wired-memory policies and tickets</a> that raise the process limit around active inference rather than pinning one fixed number.</p>
<p>But also, before launching a big run:</p>
<ul>
<li><code>sudo purge</code> flushes the file cache so the OS has clean room to allocate. Available RAM jumps; subsequent file I/O is slower until the cache refills.</li>
<li>Quit Electron apps. Slack, Discord, Cursor, VS Code, and Chrome will routinely pin 4–8 GB each.</li>
<li><code>MLX_LM_CACHE_LIMIT=0</code> (env var) prevents MLX’s internal allocation cache from growing unboundedly during long sessions — useful for sustained embedding or agent workloads.</li>
</ul>
<section id="kv-growth" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="kv-growth"><span class="header-section-number">3.1</span> What the session costs on top of the weights</h3>
<p>The weights are a fixed cost, and the only one a model card tells us about. The longer a session runs, the more memory we need on top of that: every token in the current context lives in the <a href="https://huggingface.co/docs/transformers/en/cache_explanation?ref=danmackinlay.name">KV cache</a>, which grows as the conversation does. This is why a model that loads fine can still OOM an hour later, and why so many of the server flags below are about capping context or shrinking the cache.</p>
<p>How much it costs depends on the architecture. A classic dense transformer keeps a key and value vector per layer for every token, so the cache scales with context × layers × width × word length apiece — gigabytes for a long context.<sup>1</sup> <a href="https://kipp.ly/transformer-inference-arithmetic/?ref=danmackinlay.name">kipply’s inference-arithmetic post</a> has a per-token formula; the <a href="https://apxml.com/tools/vram-calculator?ref=danmackinlay.name">apxml VRAM calculator</a> looks it up per model, Apple Silicon included. Grouped-query attention already shrinks this, and sparse-attention or SSM-hybrid models (Nemotron-Cascade, DeepSeek V4) change the whole scaling relation to be sub-linear in length.</p>
<p>When the cache is the part that will not fit, we may be able to <a href="https://huggingface.co/blog/kv-cache-quantization?ref=danmackinlay.name">quantize it</a>, cap the context, or move to one of the cheaper architectures.<sup>2</sup> Which server exposes which of those is tabulated below.</p>
</section>
</section>
<section id="desktop-apps" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="desktop-apps"><span class="header-section-number">4</span> Desktop apps</h2>
<p>The fastest path from zero to local LLM is a desktop app: one download gives us a model browser, a chat window, and an inference engine. Each is a vertical bundle — a <a href="../notebook/agent_frontends.html">frontend GUI</a>, its own runtime, usually with a server and an agent harness folded in. None of them are wholly satisfactory IMO; they all have pros and cons and are “OK for normie use”.</p>
<p>All three below are general-purpose chat apps. To drive a local model as a <em>coding</em> agent instead — terminal harnesses like <a href="../notebook/automatic_coding.html#opencode">OpenCode</a> or <a href="../notebook/automatic_coding.html#aider">Aider</a>, VS Code sidebars like <a href="../notebook/automatic_coding.html#cline">Cline</a> — see <a href="../notebook/automatic_coding.html#offline-coding">Code agents and assistants</a>, which points back here for the local backend they run against. And sometimes I want the server without GUI bells and whistles at all.</p>
<section id="osaurus" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="osaurus"><span class="header-section-number">4.1</span> Osaurus</h3>
<p><a href="https://osaurus.ai?ref=danmackinlay.name">Osaurus</a> (MIT, <code>brew install --cask osaurus</code>, <a href="https://github.com/osaurus-ai/osaurus?ref=danmackinlay.name">osaurus-ai/osaurus</a>) is Swift-native, no Electron, no Python, and behaves like a proper Mac app.</p>
<p>It seems efficient and easy. It also locks me into the Mac ecosystem, so it might not be for everyone. Also, it’s run by <a href="https://www.linkedin.com/in/jinho-jang-a5968331a/?ref=danmackinlay.name">one person</a>, so the bus factor is 1, which is a very small number. But — it’s so good!</p>
<p>The window has a model picker, a chat pane, and a status indicator; the inference engine underneath is Apple’s fast MLX, so it gets many tokens per second. It is also the intended runtime for JANG, the same author’s mixed-precision quantization format, which is most of what its curated model catalogue holds — a fact that mostly matters when a model refuses to load somewhere else.</p>
<p>Osaurus is not just a chat client but a full native macOS agent harness. It supports various hip features like persistent memory and sandboxed working folders in an isolated Linux VM via Apple’s <a href="https://developer.apple.com/documentation/containerization?ref=danmackinlay.name">Containerization</a> framework. It understands <a href="https://agentskills.io?ref=danmackinlay.name">agentskills.io</a>-format skills (and whole Claude plugins) from GitHub or local files, selecting them by RAG at runtime, and speaks MCP in both directions, as server and client. The harness layer is model-agnostic, fronting cloud providers as happily as the local MLX-ish runtime.</p>
<p>There is no CLI download command; the in-app Model Manager (<code>⌘⇧M</code> → Models) browses a <a href="https://docs.osaurus.ai/models?ref=danmackinlay.name">curated catalogue</a> of models, especially JANG ones, and will sideload others too — though not all of them work equally well. <code>Nemotron 3 Nano Omni 30B A3B JANGTQ4</code> seems like a reliable workaday default. Osaurus also discovers anything dropped into its models directory:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">hf</span> download mlx-community/DeepSeek-R1-0528-Qwen3-8B-4bit <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--local-dir</span> ~/MLXModels/mlx-community/DeepSeek-R1-0528-Qwen3-8B-4bit   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># mirror the org/repo path</span></span>
<span id="cb4-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">osaurus</span> list    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># confirms discovery, and gives the exact API name for `osaurus run`</span></span></code></pre></div></div>
<p>Osaurus is <em>also</em> a first-class server, covered below.</p>
</section>
<section id="jan" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="jan"><span class="header-section-number">4.2</span> Jan</h3>
<p><a href="https://jan.ai?ref=danmackinlay.name">Jan</a> (<code>brew install --cask jan</code>) is a FOSS cross-platform option — a full frontend + harness + server + runtime bundle. The UI is built on the <a href="../notebook/javascript_apps.html">mildly cursed Electron</a>, but the plus side is that it runs on Linux, Windows, and macOS. It supports both <code>llama.cpp</code> (via <a href="https://github.com/janhq/cortex.cpp?ref=danmackinlay.name">Cortex</a>) and MLX backends, so it seems well-suited to brute-force running non-Apple-optimized models.</p>
<p>It looks nice, and the <a href="https://www.jan.ai/docs/desktop/quickstart?ref=danmackinlay.name">Projects / Assistants / Agents / MCP Connectors quartet</a> gives it a tool-calling agent loop — connect MCP servers under Settings, and Agents mode runs multi-step autonomous workflows (<a href="https://huggingface.co/janhq/Jan-v2-VL?ref=danmackinlay.name">Jan v2 VL</a> is pitched as a 49-step multimodal agent). <a href="https://github.com/janhq/server?ref=danmackinlay.name">Jan Server</a> is the self-hosted orchestration variant.</p>
<section id="jan-custom-provider" class="level4" data-number="4.2.1">
<h4 data-number="4.2.1" class="anchored" data-anchor-id="jan-custom-provider"><span class="header-section-number">4.2.1</span> Pointing Jan at a server we already run</h4>
<p>Jan’s so-called “local” providers are its own two engines — bundled <code>llama.cpp</code> and a bundled <code>mlx-swift-lm</code> — so our own endpoint is not among them, and picking a local model in Jan means loading a <em>second</em> copy of a model beside whatever vllm-mlx already holds. An external endpoint goes in as a “custom provider” instead, which the docs confusingly file under <a href="https://www.jan.ai/docs/desktop/remote-models/custom-endpoint?ref=danmackinlay.name"><em>Cloud Providers</em></a>, however local it is. <strong>Settings → Model Providers → Add Provider</strong>, API format <em>OpenAI-compatible</em>, then:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Base URL</td>
<td><code>http://localhost:8000/v1</code> — don’t forget the <code>/v1</code>, or everything 404s</td>
</tr>
<tr class="even">
<td>API key</td>
<td>any non-empty placeholder (<code>sk-no-key</code>)</td>
</tr>
</tbody>
</table>
<p>Jan calls <code>{base_url}/models</code> on save, so a registry’s model names populate the picker by themselves. Capabilities do not: a custom provider is not probed for tools, vision or audio, so must be set per model by hand.</p>
<p>Jan’s own engine stays installed either way, so it is worth stopping it from hogging RAM if something invokes it by accident. <strong>Settings → Llama.cpp → Max Concurrent Models</strong> defaults to 2; setting it to 1 holds Jan to one model at a time. Jan’s own downloads land in its own tree by default (<code>~/Library/Application Support/Jan/data/{llamacpp,mlx}/models</code>), which is another copy of everything. Its <em>Import</em> button links a file in place rather than copying it, so that is the way to point Jan at <code>~/MLXModels</code> instead.</p>
</section>
</section>
<section id="lm-studio" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="lm-studio"><span class="header-section-number">4.3</span> LM Studio</h3>
<p><a href="https://lmstudio.ai/?ref=danmackinlay.name">LM Studio</a> (<code>brew install --cask lm-studio</code>) is closed-source, relatively slick and turnkey, and not free for commercial use. It runs both <code>llama.cpp</code> and <a href="https://lmstudio.ai/blog/lmstudio-v0.3.4?ref=danmackinlay.name">lately</a> its own MIT-licensed <a href="https://github.com/lmstudio-ai/mlx-engine?ref=danmackinlay.name"><code>mlx-engine</code></a> (<code>mlx-lm</code> + Outlines + <code>mlx-vlm</code>). Like the others, it is a bundle — frontend + runtime + server — with an OpenAI endpoint it can expose headlessly (<code>lms server</code>). I’m mildly sceptical of it because so many Cool LLM Technologies ship special bug fixes or alternate install paths for LM Studio, which hints at a slightly non-standard stack — though that might just be sampling bias, since more people file bug reports when more people run the thing. Also the licence sucks.</p>
</section>
</section>
<section id="serving" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="serving"><span class="header-section-number">5</span> Serving a model headless</h2>
<p>Once we want a model serving as a <em>daemon</em> (“token fountain”, as we say at work) rather than a chat window — a code editor, an embedding pipeline, a script that calls out to a local model — we need a long-lived process with an OpenAI-compatible API. The desktop apps above mostly do this already; below are the headless variants.</p>
<p>These stacks all speak roughly the same wire protocol, so what actually distinguishes them is <em>model lifecycle</em>: how many models stay resident at once, which config decides which, and what a switch costs against the memory limit. There are two options:</p>
<ol type="1">
<li>One server holds many models and rations them internally — vllm-mlx and oMLX are the elaborate versions of this, or,</li>
<li>one server holds exactly one model, and a router in front starts and stops them — llama-swap.</li>
</ol>
<p>I ran the first for months but switched to the second, for reasons that only make sense after seeing what the first costs. So the vllm-mlx section below is the longest on this page and is also the setup I no longer use. However, if you are not trying to ambitiously minmax your own config, you might find this the right answer for you.</p>
<section id="osaurus-serving" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="osaurus-serving"><span class="header-section-number">5.1</span> Osaurus as a server</h3>
<p>If we already have Osaurus running, we are mostly done: it is already a capable LLM daemon, exposing OpenAI-, Anthropic-, and Ollama-compatible endpoints on <code>localhost:1337</code> all at once. Anything we want to point at a local model can talk to it. The reasons to install something else anyway are that this is an idiosyncratic single-author stack, and that it exposes far fewer settings than the Python servers below.</p>
<p>To keep two models resident at once (say the agentic daily-driver plus the maths model), set <strong>Settings → Local Inference → Model Management</strong> to Flexible — under the default Strict policy, loading one evicts the other.</p>
<p>Context length is automatic — Osaurus picks a sane per-model default and does not expose it as a plain setting, so the actual ceiling is hard to read off (the cheat-sheet has the detail).</p>
<div class="callout callout-style-simple callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Do not send embeddings to Osaurus
</div>
</div>
<div class="callout-body-container callout-body">
<p>Its <code>/v1/embeddings</code> ignores the <code>model</code> field. Asking for <code>mxbai-embed-large-v1</code>, <code>bge-small-en-v1.5</code> or <code>styledistance</code> all return <code>potion-base-4M</code> at 128 dimensions — no error, no warning, wrong model, wrong width. <code>/v1/chat/completions</code> honours the field correctly, so the failure is confined to embeddings, which is exactly where it does the most damage: nothing downstream of an embedding call can tell that the vectors are wrong. Mine would have gone into <a href="../notebook/ai_search.html">the committed related-links shards</a>, which are 1024-dimensional by construction. Embeddings stay in-process on <code>sentence-transformers</code>.</p>
</div>
</div>
<p>To drive all this from the terminal, there is one gotcha: the <code>osaurus</code> command is embedded in the app bundle, and only the Homebrew install links it onto <code>PATH</code> automatically. If it is missing, we can symlink it: <code>ln -sf "/Applications/osaurus.app/Contents/Helpers/osaurus" "$(brew --prefix)/bin/osaurus"</code>, or use the button in the settings menu to do the same thing.<sup>3</sup> From there, the CLI does <code>osaurus serve / stop / status / list / run &lt;model&gt; / mcp</code>, plus it acts as a plugin manager.</p>
</section>
<section id="ollama" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="ollama"><span class="header-section-number">5.2</span> Ollama</h3>
<p><a href="https://ollama.com/?ref=danmackinlay.name">Ollama</a> (<code>brew install ollama</code>) is a <code>llama.cpp</code> wrapper with its own model registry — fast enough, wide model coverage, and notably good for embedding models:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">brew</span> services start ollama</span>
<span id="cb5-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> pull qwen3.5  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># LLM/chats etc</span></span>
<span id="cb5-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> pull mxbai-embed-large    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># also handles embeddings</span></span></code></pre></div></div>
<p>Anything OpenAI-API-compatible can now point at <code>http://localhost:11434/v1</code>. Ollama handles the lifecycle hands-off: it loads a model on the first request, keeps several resident at once (up to <code>OLLAMA_MAX_LOADED_MODELS</code>, default 3), and unloads each after <code>OLLAMA_KEEP_ALIVE</code> of idleness (default 5m). Left unbounded, the pool tanks the machine — <code>OLLAMA_MAX_LOADED_MODELS=1</code> forces evict-on-switch, <code>OLLAMA_KEEP_ALIVE=0</code> drops a model the moment it idles (or <code>15m</code> to keep it warm longer). The context window is <code>num_ctx</code>: set it per request (<code>options.num_ctx</code>), bake it into a Modelfile (<code>PARAMETER num_ctx</code>), or lean on the <code>OLLAMA_CONTEXT_LENGTH</code> default — which on recent Ollama versions auto-scales to VRAM (4k / 32k / 256k) rather than the old fixed 2048.</p>
<p>Gotchas:</p>
<ul>
<li>The <code>.gguf</code> files come from <em>Ollama’s</em> registry, not Hugging Face.</li>
<li>Some weird reimplementation headaches — e.g.&nbsp;the tokenizer baked into the GGUF can differ from the original for unclear reasons.</li>
<li>The “<code>llama.cpp</code> wrapper” framing is loosening: the registry now ships <code>-mlx</code> tags for some models (e.g.&nbsp;<code>qwen3.5:35b-mlx</code>).</li>
</ul>
<section id="ollama-embedding-memory" class="level4" data-number="5.2.1">
<h4 data-number="5.2.1" class="anchored" data-anchor-id="ollama-embedding-memory"><span class="header-section-number">5.2.1</span> An embedding model in 4GB</h4>
<p>Ollama is cowboy with RAM per default. e.g.&nbsp;I needed <code>qwen3-embedding:0.6b</code> (in principle 640MB of weights) for my <a href="../notebook/note_taking.html#hister">hister</a> search index, but it consumed 4GB of RAM. The autoscaled context is at fault here. e.g.&nbsp;on a large enough machine, Ollama gives <code>llama.cpp</code> a large KV cache by default (32768 tokens on a 128GB machine), which is counterproductive for embedding models, as they perform no autoregressive decode and only ever process short text fragments. The client’s longest request was 2963 tokens, with 99.6% under 1024, so I’m mostly wasting RAM for nothing.</p>
<p>Fixes follow:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The OpenAI /v1/embeddings dialect has no num_ctx field, so bake it into the</span></span>
<span id="cb6-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># model. Overwrite the SAME tag: a client asking for the original name once a</span></span>
<span id="cb6-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># minute evicts a differently-named derived model on every single request.</span></span>
<span id="cb6-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">printf</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'FROM qwen3-embedding:0.6b\nPARAMETER num_ctx 4096\n'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> /tmp/qwen3emb.Modelfile</span>
<span id="cb6-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> create qwen3-embedding:0.6b <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-f</span> /tmp/qwen3emb.Modelfile   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -f wants a path, not stdin</span></span>
<span id="cb6-6"></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Revert with `ollama pull qwen3-embedding:0.6b`. Weights are content-addressed,</span></span>
<span id="cb6-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># so neither direction re-downloads anything.</span></span>
<span id="cb6-9"></span>
<span id="cb6-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Confirm: CONTEXT 4096, SIZE ~2.2GB, once something reloads it.</span></span>
<span id="cb6-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> stop qwen3-embedding:0.6b</span>
<span id="cb6-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> ps</span></code></pre></div></div>
<p>That shrinks it back down to 2.2GB. <code>num_ctx</code> is a hard ceiling now rather than a generous default, so we want it to stay above the longest text we ever send; 4096 leaves room over the 2963 above.</p>
<p>2.2GB is still well over the 640MB of weights, because the KV cache is not the only pool. There is a second working buffer, sized by Ollama’s <em>batch</em> settings rather than by the context, and <code>PARAMETER num_batch</code> is the knob for it. I have left that one alone: the remaining win is smaller, and batch size trades against how long a single input can be.</p>
<p>The rest of Ollama’s settings live in the environment rather than in the model, which under Homebrew is awkward. The formula rewrites <code>~/Library/LaunchAgents/homebrew.mxcl.ollama.plist</code> from its own template on every <code>brew services restart</code> or <code>brew upgrade ollama</code>, discarding whatever we added<sup>4</sup>. So we edit the plist and reload it by hand, and redo it after upgrades:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">P</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>~/Library/LaunchAgents/homebrew.mxcl.ollama.plist</span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># One model resident at a time.</span></span>
<span id="cb7-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/usr/libexec/PlistBuddy</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Add :EnvironmentVariables:OLLAMA_MAX_LOADED_MODELS string 1"</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$P</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb7-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Ollama multiplies the context by this before handing it to llama.cpp, so</span></span>
<span id="cb7-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># letting it autoscale would undo the num_ctx fix above.</span></span>
<span id="cb7-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/usr/libexec/PlistBuddy</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Add :EnvironmentVariables:OLLAMA_NUM_PARALLEL string 1"</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$P</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb7-8"></span>
<span id="cb7-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># No sudo — gui/501 is our own login session. Two statements, not `&amp;&amp;`: chained,</span></span>
<span id="cb7-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the reload arrives before the unload finishes, fails, and leaves Ollama down.</span></span>
<span id="cb7-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">launchctl</span> bootout gui/501/homebrew.mxcl.ollama</span>
<span id="cb7-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">launchctl</span> bootstrap gui/501 <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$P</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb7-13"></span>
<span id="cb7-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">launchctl</span> print gui/501/homebrew.mxcl.ollama <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grep</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-E</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'state =|OLLAMA_'</span></span></code></pre></div></div>
<p>Killing <code>llama-server</code> to reclaim its memory achieves nothing, incidentally. It is a child of <code>ollama serve</code>, which starts a fresh one within seconds of the next request; <code>brew services stop ollama</code> is the one that sticks.</p>
</section>
<section id="ollama-logs" class="level4" data-number="5.2.2">
<h4 data-number="5.2.2" class="anchored" data-anchor-id="ollama-logs"><span class="header-section-number">5.2.2</span> Trimming the log</h4>
<p>Ollama logs several lines per request, nothing rotates <code>/opt/homebrew/var/log/ollama.log</code>, and there is no setting to turn it down<sup>5</sup>. Under steady indexing, it grew ~20MB/day for me, reaching 387MB before I noticed.</p>
<p>Truncate the file rather than deleting or renaming it: launchd holds the file open, so replacing it leaves Ollama writing somewhere we can no longer see, and it frees no space until Ollama restarts.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> /opt/homebrew/var/log/ollama.log</span></code></pre></div></div>
<p><code>~/.local/bin/ollama-log-trim.sh</code> does that above a size cap, keeping a tail so that a post-mortem straight after a trim isn’t empty-handed:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource sh number-lines code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LOG</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${OLLAMA_LOG</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:-</span>/opt/homebrew/var/log/ollama.log<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb9-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">MAX_BYTES</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${OLLAMA_LOG_MAX_BYTES</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:-</span>104857600<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 100 MiB</span></span>
<span id="cb9-3"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">KEEP_TAIL_LINES</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${OLLAMA_LOG_KEEP_TAIL_LINES</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:-</span>2000<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb9-4"></span>
<span id="cb9-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">[</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">-f</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$LOG</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">||</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">exit</span> 0</span>
<span id="cb9-6"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">size</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$(</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stat</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-f%z</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$LOG</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>/dev/null <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">||</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">echo</span> 0<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb9-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">[</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$size</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">-gt</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$MAX_BYTES</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">||</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">exit</span> 0</span>
<span id="cb9-8"></span>
<span id="cb9-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Staged in the log's own directory to stay on one filesystem.</span></span>
<span id="cb9-10"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">tmp</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$(</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mktemp</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$(</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dirname</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$LOG</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">)</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/.ollama-log-trim.XXXXXX"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">)</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">||</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">exit</span> 0</span>
<span id="cb9-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">trap</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rm -f "$tmp"'</span> EXIT</span>
<span id="cb9-12"></span>
<span id="cb9-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tail</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-n</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$KEEP_TAIL_LINES</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$LOG</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$tmp</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>/dev/null <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">||</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$tmp</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb9-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$LOG</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb9-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$tmp</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$LOG</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span></code></pre></div></div>
<p>An hourly <code>StartInterval</code> agent runs it at <code>~/Library/LaunchAgents/name.danmackinlay.ollama-log-trim.plist</code>, loaded with <code>launchctl bootstrap gui/501 &lt;plist&gt;</code>. Running it hourly is more frequent than a daily-ish problem requires, but because the process exits without doing anything if it finds the file under the cap, the frequency is free and the ceiling still holds if something starts logging harder than usual. It’s worth forcing the trim path once to see it work, rather than waiting days for the cap:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OLLAMA_LOG_MAX_BYTES</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>1048576 <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sh</span> ~/.local/bin/ollama-log-trim.sh</span></code></pre></div></div>
</section>
</section>
<section id="mlx-lm" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="mlx-lm"><span class="header-section-number">5.3</span> mlx-lm and mlx-vlm</h3>
<p><a href="https://github.com/ml-explore/mlx-lm?ref=danmackinlay.name"><code>mlx-lm</code></a> is Apple’s reference language-model runtime on MLX. <a href="https://github.com/Blaizzy/mlx-vlm?ref=danmackinlay.name" id="mlx-vlm"><code>mlx-vlm</code></a> is its sibling package: same MLX backend, same <code>mlx-community/&lt;repo&gt;</code> weights and HF cache, but for VLMs (“vision-language models”) and omni models—image/video/audio in, text out—instead of pure text LLMs. Where this page says “VLM,” we mean a model in that family; <a href="../notebook/pdf_ingestion.html#ocr-engines"><code>mlx-vlm</code> is what runs one locally</a>, e.g., DeepSeek-OCR. Osaurus and JANG take inspiration from MLX-style Apple-Silicon-friendly execution, but <code>mlx-lm</code> is the original.</p>
<p><code>uv tool install mlx-lm</code> drops a family of commands onto <code>PATH</code>, all reading the same weights:</p>
<ul>
<li><code>mlx_lm.generate --model mlx-community/&lt;repo&gt;</code> — one-shot completion from the CLI.</li>
<li><code>mlx_lm.chat</code> — an interactive REPL in the terminal.</li>
<li><code>mlx_lm.server --model mlx-community/&lt;repo&gt;</code> — an OpenAI-compatible daemon; it holds one model, swapping on demand per request (evict + reload, not a restart). Two live at once means two processes on two ports.</li>
<li><code>mlx_lm.lora</code> — its LoRA <a href="../notebook/nn_foundation_fine_tuning.html">fine-tuning</a> path.</li>
</ul>
<p><code>mlx-vlm</code> mirrors this shape (<code>mlx_vlm.generate</code>, <code>mlx_vlm.chat</code>, <code>mlx_vlm.server</code>) but takes an image/video/audio argument alongside the text prompt.</p>
<p>It uses the standard Hugging Face links: <code>mlx-community/&lt;repo&gt;</code> resolves straight to Hugging Face, and the weights land in the shared HF cache (<code>~/.cache/huggingface/hub</code>).</p>
<p><code>mlx_lm.server</code> has no <code>--ctx</code> flag, so it grows the <a href="https://huggingface.co/docs/transformers/en/cache_explanation?ref=danmackinlay.name">KV cache</a> to fit whatever we send—up to, presumably, the model’s declared max, capped only by RAM, at which point it presumably kernel-panics the machine. We cap the context in the harness (<code>limit.context</code> / <code>contextWindow</code>) and mind the memory budget.</p>
<p>A reason to keep this around even with Osaurus installed is that it’s great when it runs, but its Swift engine’s coverage lags the Python MLX options. A plain <code>mlx-lm</code> loads interesting MLX conversions that Osaurus can’t (e.g., Cascade-2).</p>
<p>It has also quietly grown the features I left it for. <code>--decode-concurrency</code> and <code>--prompt-concurrency</code> are continuous batching, <code>--prompt-cache-bytes</code> is a prefix cache, <code>--draft-model</code> is speculative decoding, and the sampling defaults are per-launch flags—<code>--temp</code>, <code>--top-p</code>, <code>--top-k</code>, <code>--min-p</code>—which matters because top-k is one of the knobs vllm-mlx accepts and drops. Measured on VibeThinker-3B-8bit: 58 tok/s at <img src="https://latex.codecogs.com/png.latex?k=1">, 146 tok/s aggregate at <img src="https://latex.codecogs.com/png.latex?k=4">. It also splits thinking into a <code>reasoning</code> field with no parser flag at all, spelled differently from vllm-mlx’s <code>reasoning_content</code>.</p>
<div class="callout callout-style-simple callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span><code>mlx_lm.server</code> is deterministic, which silently breaks maj@k
</div>
</div>
<div class="callout-body-container callout-body">
<p>maj@<img src="https://latex.codecogs.com/png.latex?k"> is <a href="../notebook/automatic_maths.html#fan-out">majority voting over <img src="https://latex.codecogs.com/png.latex?k"> independent samples of the same question</a>, which is how a weak solver is made strong, and it needs the <img src="https://latex.codecogs.com/png.latex?k"> samples to differ. Same prompt and same parameters give byte-identical output here, sequential or concurrent, so <img src="https://latex.codecogs.com/png.latex?k"> samples are <img src="https://latex.codecogs.com/png.latex?k"> copies of one answer and the vote is meaningless. Per-request <code>seed</code> does not rescue it — <code>mx.random.seed()</code> is process-global, a batch shares it, and a seeded request is excluded from batching anyway. What works is varying a system preamble per sample (<code>Independent attempt {i}.</code>), which restores diversity at no cost in wall-clock because the batch survives. Worth knowing generally: sampling diversity is a property of the server, not of the temperature we asked for, and it differed between every server I tested.</p>
</div>
</div>
</section>
<section id="vllm-mlx" class="level3" data-number="5.4">
<h3 data-number="5.4" class="anchored" data-anchor-id="vllm-mlx"><span class="header-section-number">5.4</span> vllm-mlx</h3>
<p><a href="https://github.com/waybarrios/vllm-mlx?ref=danmackinlay.name">vllm-mlx</a> (<code>uv tool install vllm-mlx</code>, Apache 2.0) is a vLLM-style inference server for Apple Silicon. It is active and popular by Mac LLM standards — 1,300+ stars. The core pitch: continuous batching, paged KV cache with prefix sharing, an SSD-tiered cache for spilling prefixes to disk, and both OpenAI (<code>/v1/*</code>) and Anthropic (<code>/v1/messages</code>) endpoints from one process.</p>
<p>This is the longest section here because I ran it the hardest and therefore know its failure modes; it is also the one I eventually ditched for llama-swap. The engine-level gotchas are worth reading even if we never touch the registry, since oMLX and Rapid-MLX are both forks and inherit most of them.</p>
<p>It has many bonus features:</p>
<ul>
<li>native TTS (Kokoro, Chatterbox, VibeVoice, VoxCPM) and STT alongside text/image/video/audio chat</li>
<li>multi-token prediction (<code>--enable-mtp</code>)</li>
<li>an embedding and reranker endpoint in the same process (<code>--embedding-model</code>, <code>--rerank-model</code>)</li>
<li>MCP tool integration (<code>--mcp-config</code>)</li>
<li>Prometheus metrics (<code>--enable-metrics</code>) and a built-in benchmarker (<code>vllm-mlx bench-serve</code>)</li>
</ul>
<p>MTP here is the self-drafting kind: <code>--enable-mtp</code> drives the model’s <em>own</em> MTP head via cache snapshot and restore rather than loading a separate draft model, so the head must live inside the model directory. The acceptance rule is where this implementation stops being free. Speculative decoding is supposed to leave the output distribution untouched, and this implementation disturbs it in two ways, both visible in the scheduler. The draft head samples greedily at whatever temperature we requested, so every accepted draft splices the most likely next token into a stream we were paying to have varied — which is the whole product <a href="../notebook/automatic_maths.html#fan-out">maj@<img src="https://latex.codecogs.com/png.latex?k"></a> is buying. Acceptance is also all-or-nothing across a batch, the drafts compared as one list, so one request’s disagreement throws away every other request’s drafts and the hit rate falls as the fan-out widens. <code>--mtp-optimistic</code> keeps every draft without checking, which trades the guarantee for speed rather than fixing either fault.</p>
<p>Single-model serving is a one-liner:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> tool install vllm-mlx</span>
<span id="cb11-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vllm-mlx</span> serve mlx-community/Llama-3.2-3B-Instruct-4bit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span> 8000 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--continuous-batching</span></span></code></pre></div></div>
<p>For a chat window over this endpoint — one that renders the equations the maths models emit — <a href="../notebook/ai_agents_applied.html#open-webui-setup">Open WebUI points at it unmodified</a>: add <code>http://localhost:8000/v1</code> as an OpenAI connection and skip the bundled-Ollama path that the tutorials assume.</p>
<section id="vllm-mlx-model-dir" class="level4" data-number="5.4.1">
<h4 data-number="5.4.1" class="anchored" data-anchor-id="vllm-mlx-model-dir"><span class="header-section-number">5.4.1</span> Serving from <code>~/MLXModels</code></h4>
<p>vllm-mlx lacks a folder-scanning flag. A bare local path loads one model the same way a bare <code>mlx-community/&lt;repo&gt;</code> id does:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vllm-mlx</span> serve ~/MLXModels/mlx-community/Qwen3.6-35B-A3B-4bit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span> 8000 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--continuous-batching</span></span></code></pre></div></div>
<p>Multi-model residency happens via a <code>--models-config models.yaml</code> registry: named entries, each with an explicit <code>path:</code>, all behind one process. Models load lazily upon first use and undergo LRU eviction under a <code>memory_budget_gb</code>, with a <code>contention_policy</code> (<code>fail</code> / <code>wait</code> / <code>preempt</code> / <code>wait_then_fail</code> / <code>wait_then_preempt</code>) deciding what happens when a request needs a model that does not currently fit. Clients pick one with the ordinary OpenAI <code>model</code> field, so nothing downstream has to know this is happening.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode numberSource yaml number-lines code-with-copy"><code class="sourceCode yaml"><span id="cb13-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">manager</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb13-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  # WEIGHTS only — the KV cache is a separate pool, sized on the serve command.</span></span>
<span id="cb13-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  # 68 against the 98.6 GB ceiling below, plus --cache-memory-mb *per resident</span></span>
<span id="cb13-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  # multimodal engine* on top, which nothing here counts. See the note after.</span></span>
<span id="cb13-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">memory_budget_gb</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">68</span></span>
<span id="cb13-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contention_policy</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb13-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">strategy</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> wait_then_preempt</span></span>
<span id="cb13-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wait_timeout_s</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">45</span></span>
<span id="cb13-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">preempt_after_s</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span></span>
<span id="cb13-10"></span>
<span id="cb13-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">models</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb13-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> driver</span></span>
<span id="cb13-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> /Users/dan/MLXModels/mlx-community/Qwen3.6-35B-A3B-4bit</span></span>
<span id="cb13-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">continuous_batching</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb13-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">estimated_memory_gb</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">22</span></span>
<span id="cb13-16"></span>
<span id="cb13-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> solver</span></span>
<span id="cb13-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> /Users/dan/MLXModels/mlx-community/VibeThinker-3B-8bit</span></span>
<span id="cb13-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">preload</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb13-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">estimated_memory_gb</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span></code></pre></div></div>
<p>We can run the server like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vllm-mlx</span> serve <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--models-config</span> ~/.config/vllm-mlx/models.yaml <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span> 8000 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--gpu-memory-utilization</span> 0.88 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\ </span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ceiling = 0.88 × the wired limit, ≈98.6 GB — not 112</span></span>
<span id="cb14-4">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">--continuous-batching</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--use-paged-cache</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--cache-memory-mb</span> 30720 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-cache-blocks</span> 16384 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-num-seqs</span> 16 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-tokens</span> 131072 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-request-tokens</span> 131072 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-11">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--enable-auto-tool-choice</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-12">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tool-call-parser</span> auto <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-13">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--reasoning-parser</span> qwen3 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-14">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--enable-metrics</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-15">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--kv-cache-quantization</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-16">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--kv-cache-quantization-bits</span> 8 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-17">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ssd-cache-dir</span> ~/.cache/vllm-mlx/kv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-18">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ssd-cache-max-gb</span> 40 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb14-19">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--timeout</span> 1200</span></code></pre></div></div>
<p>This is a chunky-boi config, allocating a hundred gigabytes of memory for large context windows and setting long timeouts so I can flood the server without too much guilt.</p>
<p>I had the ceiling arithmetic wrong here for months, and 0.4.1’s startup report caught it. <code>--gpu-memory-utilization</code> is a fraction of MLX’s <em>device working set</em>, which is the <code>iogpu</code> wired limit — 112 GB on this machine, because I set it there — not the 128 GB of physical RAM. So 0.88 buys a 98.6 GB ceiling, and my old comment reading “≈112 GB (0.88 × 128)” arrived at the right-looking number by cancelling one error against another. The startup line to read is this one, which prints the base and its source:</p>
<pre><code>INFO  Registry memory budget: 68.0 GB of model weights; Metal allocation ceiling
      98.6 GB (88% of 112.0 GB, from serve default); prefix-cache maximum none
      configured</code></pre>
<p>Two things to know before reading that line against the engine’s own.</p>
<p>The <code>prefix-cache maximum none configured</code> is wrong, or rather it is right about half the registry and silent about the other half. There are two prefix-cache implementations, and which one a model gets follows from the engine that loaded it rather than from anything we chose. Text models take the paged cache, sized in 64-token blocks by <code>--max-cache-blocks</code>, and ignore <code>--cache-memory-mb</code>. Multimodal models take the memory-aware cache, which is sized by <code>--cache-memory-mb</code> and never consults <code>--use-paged-cache</code> at all — so the 30 GB above is real, and it is 30 GB <em>per resident multimodal engine</em>. The startup check applies the text-path rule to the whole registry, so it reports none of that. On a registry like mine, where most entries load multimodal, three resident models can put 90 GB of prefix cache against a 98.6 GB ceiling with the budget line still reading zero — the same accounting hole as <a href="https://github.com/waybarrios/vllm-mlx/issues/627?ref=danmackinlay.name">#627</a>, pointing the other way, and <a href="https://github.com/waybarrios/vllm-mlx/issues/712?ref=danmackinlay.name">filed as #712</a>. The per-model fix is not available yet either: a registry entry takes <code>gpu_memory_utilization</code>, <code>enable_mtp</code> and <code>prefill_step_size</code>, but no cache size, and each model’s scheduler config is a straight clone of the global one.</p>
<p>And the engine logs the same ceiling in different units a few lines later — <code>allocation_limit=105.8GB (88% of 120.3GB)</code> is 98.6 GiB of 112 GiB, not a second, larger allowance.</p>
<p>Notes</p>
<ul>
<li><code>path:</code> needs to be a real filesystem path — YAML does not <code>~</code>-expand.</li>
<li><code>estimated_memory_gb</code> is mandatory on a bare HF id — the manager needs <em>some</em> number to make eviction decisions from — but optional on a local path with real weight files on disk, since those can be measured directly.</li>
<li><code>--max-num-seqs</code>: concurrency cap (default 256 would explode KV)</li>
<li><code>--max-cache-blocks 16384</code>: 30 GB KV pool (was 80 — hence the OOM risk)</li>
<li><code>--kv-cache-quantization-bits 8</code>/<code>--kv-cache-quantization</code>: downsample KV cache for longer prompts without exploding RAM</li>
<li><code>--tool-call-parser auto</code>: <a href="../notebook/ai_agents_applied.html#differences">models emit tool calls in different dialects</a>, so we try each one per response, which is what a registry mixing Nemotron and Qwen needs. 0.4.1 teaches the Mistral branch the <code>[ARGS]</code> form that Devstral Small 2 emits</li>
<li><code>--reasoning-parser qwen3</code>: unlike the tool parser this is a single choice with no <code>auto</code>, which looks alarming for a registry of mixed families and turns out mostly not to be — the <code>&lt;think&gt;</code>…<code>&lt;/think&gt;</code> convention is common enough that <code>qwen3</code> splits the Nemotrons correctly too. 0.4.1 adds <code>poolside_v1</code> for Laguna. (A Poolside <em>tool</em> parser ships alongside it but is wired to nothing — not a <code>--tool-call-parser</code> choice, not in the <code>auto</code> chain.)</li>
<li>What does bite is truncation. The split happens on the closing tag, so a response that hits <code>max_tokens</code> mid-thought comes back with <code>reasoning_content: null</code> and the raw chain-of-thought sitting in <code>content</code>, which reads exactly like a parser that does not understand the model. Raise the budget before blaming the parser.</li>
<li><code>--ssd-cache-dir</code>/<code>--ssd-cache-max-gb</code>: the cold tier for prefix caching, off by default. I had filed this mentally as oMLX-only, but it isn’t — same two-tier idea, so a second turn on a long agentic context re-prefills from disk instead of from scratch. Put the directory on the exclusion list; it churns.</li>
<li><code>--warm-prompts &lt;file.json&gt;</code>: pre-runs a list of message arrays at startup to populate the prefix cache. The docs claim cold time-to-first-token drops 1.3–2.3× on agent workloads, which is to say it front-loads the harness’s system prompt and tool definitions. Keep the file to 1–3 entries or the boot itself gets memory-hungry.</li>
</ul>
<p>Clients pick a model with the normal OpenAI <code>model</code> field (<code>model: "driver"</code>, <code>model: "solver"</code>), the same pattern as oMLX’s pinned pair.</p>
<p>One multimodal gotcha: a VLM or omni model needs a <code>mllm: true</code> on its registry entry to load (the standalone single-model <code>serve</code> equivalent is the global <code>--mllm</code> flag). vllm-mlx guesses multimodality from the repo name — <code>VL</code>, <code>vision</code>, <code>llava</code> and friends — but <code>-Omni-</code> slips through, so a model it reads as text-only dies at weight-load because it has no slots for the vision and audio towers (<code>Received N parameters not in model</code>). <code>mllm: true</code> routes that one entry through <code>mlx-vlm</code> instead, without forcing the text models in the same registry down the same path.</p>
<p>Dying is the lucky case. <code>mlx-lm</code>’s <code>qwen3_5_moe</code> loader carries a <code>sanitize()</code> that skips any key starting with <code>vision_tower</code> or <code>model.visual</code>, so a vision-capable Qwen build routed down the text path loads perfectly and arrives with its vision tower on the floor — 333 tensors dropped, no warning, <code>Qwen3.6-35B-A3B-4bit</code> serving text as though that were all it ever was. The omni checkpoint fails loudly only because <code>mlx-lm</code> has no implementation of its architecture at all, so nothing is there to strip its 1,118 <code>sound_encoder.*</code> tensors. The lesson I take is to check what the server loaded rather than what the repo card advertises. The weights also have to bring a config the mlx-vlm loader recognizes: the <code>mlx-community</code> that Nemotron-3 Nano Omni builds load, but an Osaurus repackaging of the same model that hides the multimodal config in a side-file does not.</p>
<div class="callout callout-style-simple callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>The weights budget does not know about the ceiling
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>manager.memory_budget_gb</code> counts model <em>weights only</em>, lives in the YAML with no command-line override, and its admission arithmetic never consults the ceiling we set on the command line (<code>--gpu-memory-utilization</code>, <code>--cache-memory-mb</code>). Set it too high and the manager keeps two models resident because its own arithmetic says they fit; MLX then hits the process ceiling and dies — a hard out-of-memory crash instead of a graceful eviction. Keep <code>memory_budget_gb ≤ gpu-memory-utilization × RAM − cache-memory-mb − headroom</code>. Since 0.4.1 the server at least tells us at startup, printing the weights budget against the Metal ceiling and warning when the two conflict — that much of <a href="https://github.com/waybarrios/vllm-mlx/issues/627?ref=danmackinlay.name">my bug report</a> landed. It warns rather than clamps, and the KV cache, prefix cache and activations all come out of the same ceiling, so clearing the check is not a promise that we will not run out of memory.</p>
</div>
</div>
<p>One config file per memory profile is the workaround I use — a “full-fat” registry and a lean everyday one, with the model paths duplicated between them, because vllm-mlx cannot include or merge one config into another. <code>--auto-unload-idle-seconds</code> plus <code>--lazy-load-model</code> looks like it might collapse the pair back into one file, since an idle big model releasing its weights is most of what the lean profile buys; I have not tried it.</p>
<p>Gotchas:</p>
<p><strong>Mind which <code>mlx-vlm</code> came along for the ride.</strong> vllm-mlx pulls it in as a dependency; since this is deep infra that other tools use and has many weird version quirks especially for multimodal models, it is worth keeping it under user control:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb16-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> tool install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--force</span> vllm-mlx</span></code></pre></div></div>
</section>
</section>
<section id="omlx" class="level3" data-number="5.5">
<h3 data-number="5.5" class="anchored" data-anchor-id="omlx"><span class="header-section-number">5.5</span> oMLX</h3>
<p><a href="https://omlx.ai?ref=danmackinlay.name">oMLX</a> (<a href="https://github.com/jundot/omlx?ref=danmackinlay.name"><code>jundot/omlx</code></a>, Apache 2.0, <code>brew tap jundot/omlx https://github.com/jundot/omlx &amp;&amp; brew install omlx</code>) is a fork of vllm-mlx with a different frontend built on top, so it inherits that feature set — continuous batching, multi-model residency, and both OpenAI and Anthropic endpoints. Unique selling propositions:</p>
<ul>
<li>Its SSD prefix cache is <em>automatic and block-addressed</em> — hot blocks stay in RAM, cold blocks spill to disk, and the longest-prefix matches survive a restart — whereas <code>llama-server</code>’s <code>--slot-save-path</code> and <code>ds4</code>’s <code>--kv-disk-dir</code> rely on manual slot-save knobs. We want this for agentic coding, where the pitch is TTFT dropping from 30–90s to under 5s on the second turn of a long context.</li>
<li>An explicit <a href="../notebook/automatic_coding.html">Claude Code</a> accommodation: it rescales reported token counts so auto-compact fires at the right time, and it holds the connection open with SSE keep-alives through a long prefill. The frontend is a signed SwiftUI menu-bar app (not Electron) with a web admin panel, and it reuses an existing LM Studio model directory.</li>
<li>It is the only non-Osaurus server with <a href="https://github.com/jjang-ai/jangq?ref=danmackinlay.name">merged JANG support</a>, so it can load the mixed-precision JANG quants that make sub-4-bit MoE models behave — which are otherwise Osaurus-only.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb17-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">omlx</span> serve <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--model-dir</span> ~/MLXModels <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb17-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--paged-ssd-cache-dir</span> ~/.omlx/cache <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb17-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--hot-cache-max-size</span> 16GB <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb17-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-concurrent-requests</span> 8</span></code></pre></div></div>
<p>It had the usual hobbyist caveats (bus factor 1, MLX-only) until September 2026, when its author, Jun Kim, <a href="https://huggingface.co/blog/omlx?ref=danmackinlay.name">joined Hugging Face</a> to work on MLX; it is still MLX-only. The clean lineage and the oQ converter make it worth a run as a headless daily driver if the Osaurus/<code>mlx_lm.server</code> pair leaves us wanting persistent prefix reuse. The SSD cache is less of a differentiator than I first thought: vllm-mlx has the same cold tier behind <code>--ssd-cache-dir</code>, just switched off by default rather than on.</p>
<section id="omlx-multi-model" class="level4" data-number="5.5.1">
<h4 data-number="5.5.1" class="anchored" data-anchor-id="omlx-multi-model"><span class="header-section-number">5.5.1</span> Example multi-model setup</h4>
<p>The <a href="../notebook/automatic_maths.html#fan-out">mathematical fan-out</a> setup requires both models live at once on one endpoint — a solver sampled wide for maj@k, orchestrated by an agentic driver. oMLX does this in a single process; the config is a model directory, a memory ceiling, and a pin per model.</p>
<p>Drop the weights into the shared MLX dir — <code>~/MLXModels</code>, the same tree Osaurus scans, so one download serves both — mirroring each repo’s <code>org/name</code> path the way the GUI downloaders do:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">hf</span> download gabfssilva/VibeThinker-3B-MLX-BF16  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--local-dir</span> ~/MLXModels/gabfssilva/VibeThinker-3B-MLX-BF16  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># solver, hi-fi bf16</span></span>
<span id="cb18-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">hf</span> download mlx-community/VibeThinker-3B-8bit   <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--local-dir</span> ~/MLXModels/mlx-community/VibeThinker-3B-8bit   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># solver, near-lossless and faster</span></span>
<span id="cb18-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">hf</span> download mlx-community/Qwen3.6-35B-A3B-4bit  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--local-dir</span> ~/MLXModels/mlx-community/Qwen3.6-35B-A3B-4bit  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the driver</span></span></code></pre></div></div>
<p>Launch with a memory guard sized to hold the driver, the solver, and the fan-out’s KV all at once, and raise the concurrency to the <a href="../notebook/automatic_maths.html#fan-out"><img src="https://latex.codecogs.com/png.latex?k"> samples</a> we intend to run:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb19-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">omlx</span> serve <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--model-dir</span> ~/MLXModels <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb19-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--memory-guard-gb</span> 110 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\ </span>         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 128 GB Mac: driver (~22 GB) + solver (~3 GB) + KV headroom</span></span>
<span id="cb19-3">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">--max-concurrent-requests</span> 16     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the fan-out width; default is 8</span></span></code></pre></div></div>
<p>In the admin panel, pin both the driver and the solver so LRU does not evict one while the other is mid-job, and set their sampling as per-model profiles — <code>vibethinker:solve</code> at temp 1.0 / top-p 0.95 / top-k 0, the driver at its own recipe (Qwen3.6 thinking temp 0.6 for coding). The fan-out then POSTs <code>model=vibethinker:solve</code> <img src="https://latex.codecogs.com/png.latex?k"> times while the loop drives <code>model=qwen3.6</code> — same port 8000, both resident, no reload between them.</p>
<p>Both VibeThinker builds load and serve fine. The <a href="https://huggingface.co/mlx-community/VibeThinker-3B-8bit?ref=danmackinlay.name">8-bit</a> decodes ~80% faster than <a href="https://huggingface.co/gabfssilva/VibeThinker-3B-MLX-BF16?ref=danmackinlay.name">bf16</a>, but the bf16 might be worth it for tiebreaker votes.</p>
</section>
</section>
<section id="rapid-mlx" class="level3" data-number="5.6">
<h3 data-number="5.6" class="anchored" data-anchor-id="rapid-mlx"><span class="header-section-number">5.6</span> Rapid-MLX</h3>
<p><a href="https://github.com/raullenchai/Rapid-MLX?ref=danmackinlay.name">Rapid-MLX</a> (Apache 2.0, <code>brew install rapid-mlx</code>) seems like a variant of interest. The pitch is a polished single-model MLX server — continuous batching, a radix prefix cache, KV quantization down to int4, 17 tool-call parsers, both OpenAI and Anthropic wires, and agent clients wire-verified against real weights every release.</p>
<p>It is another fork of vllm-mlx, (upon installation we find a <code>vllm_mlx</code> module) and it forked <em>away</em> the multi-model registry — its <code>model_registry.py</code> is 185 lines (against <code>vllm-mlx</code>’s 1195), because it does not try to manage multiple models and their memory contention. OTOH, it does not seem that <code>vllm-mlx</code> is actually doing a great job when it does try anyway (cf <a href="https://github.com/waybarrios/vllm-mlx/issues/627?ref=danmackinlay.name">#627</a> and <a href="https://github.com/waybarrios/vllm-mlx/issues/712?ref=danmackinlay.name">#712</a>). So maybe it is an alternative with some bugs amputated.</p>
<p>Usefully, <code>--reasoning-parser</code> takes <code>vibethinker</code> by name! Also, using the flags <code>--kv-disk-checkpoint-interval</code> plus its <code>pflash</code> flags, it gets something close to the oMLX’s persistent cache. It is in homebrew-core rather than a personal tap, which is a mild maturity signal. Untested here.</p>
</section>
<section id="mtplx" class="level3" data-number="5.7">
<h3 data-number="5.7" class="anchored" data-anchor-id="mtplx"><span class="header-section-number">5.7</span> MTPLX</h3>
<p><a href="https://github.com/youssofal/MTPLX?ref=danmackinlay.name">MTPLX</a> is a Mac app plus CLI (<code>mtplx serve</code>) built around MTP self-drafting on MLX, with OpenAI and Anthropic endpoints. Rather than loading arbitrary <code>mlx-community</code> weights, it serves its own pre-quantized packs from the <a href="https://huggingface.co/Youssofal?ref=danmackinlay.name"><code>Youssofal</code></a> Hugging Face account, for a short list of models: Qwen3.8-Flash-Next, Qwen3.8-27B, Qwen3.6, Ternary Bonsai 2 and Gemma 4.</p>
<p>It makes two claims of interest. The first is speed, 2–3× plain decoding. On a 128 GB M5 Max, <a href="https://mtplx.com/benchmarks?ref=danmackinlay.name">its benchmark page</a> lists Qwen3.8-Flash-Next at 68 tok/s at 16K context and 44 tok/s at 206K; the headline 125.8 tok/s is one OpenCode request, helped along by a “copy lane” for repeated text. The second is that the drafting is exact at any temperature: the Flash-Next card reports a thousand four-token draws from the fast path matching the plain path within noise at temp 1.0 / top-p 0.95 / top-k 20. That is the guarantee vllm-mlx gives up, so it is the claim we should test with a <a href="../notebook/automatic_maths.html#fan-out">maj@<img src="https://latex.codecogs.com/png.latex?k"></a> diversity check before relying on it for fan-out. All these numbers are the author’s own, and the bus factor is 1 again. Untested here.</p>
<section id="mtplx-llama-swap" class="level4" data-number="5.7.1">
<h4 data-number="5.7.1" class="anchored" data-anchor-id="mtplx-llama-swap"><span class="header-section-number">5.7.1</span> Running MTPLX under llama-swap</h4>
<p>The app and the CLI are two front ends to one server, and for our purposes the app is the wrong one. It runs its own daemon on <code>:8000</code>, picks a model from its own catalogue, keeps its own model library, and installs fan control, all of which compete with llama-swap for the same jobs. The CLI alone is what we want, in its own environment, since it pins <code>mlx&lt;0.33</code> and <code>mlx-lm&lt;0.32</code> and would otherwise fight whatever <code>mlx_lm.server</code> we run elsewhere:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb20-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> tool install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mtplx[server]"</span></span>
<span id="cb20-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mtplx</span> doctor</span></code></pre></div></div>
<p>A pack is MLX safetensors plus two MTPLX-specific files, a separate <code>ngram-table.safetensors</code> and a <code>mtplx_runtime.json</code> serving contract, so we treat the packs as MTPLX-only, but file them in the shared tree under their organization like everything else:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb21-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">hf</span> download Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb21-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--local-dir</span> ~/MLXModels/Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed</span>
<span id="cb21-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mtplx</span> inspect ~/MLXModels/Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed</span></code></pre></div></div>
<p><code>mtplx pull</code> and the app would instead write into <code>~/.mtplx/models</code>, which is one more copy.</p>
<p>For a 128 GB machine, the Optimized-Speed pack is the one that fits. Per its card, the MoE experts and dense matrices are 4-bit in groups of 64, the Qwen Sparse Attention projections are promoted to 8-bit, and the Gated DeltaNet state parameters, norms, sparse-attention indexer, and MTP head stay 16-bit. That is mixed precision by rule rather than by calibration, and unlike the 27B packs, which report 96% top-1 agreement with bf16, the Flash-Next card publishes no fidelity measurement against bf16; its exactness claim compares the fast path with the plain path of the <em>same</em> quantized model. The 8-bit Optimized-Quality sibling needs 128.5 GiB for its weights, so it is out; the flat 4-bit Bare-Speed sibling (~74 GB resident, 78 GiB peak) is the fallback if memory gets tight.</p>
<p>Three things in MTPLX’s source decide how it behaves behind llama-swap.</p>
<ul>
<li><code>mtplx serve</code> with an explicit <code>--model</code> and no TTY skips its onboarding wizard and <code>exec</code>s the server in place, so the server keeps llama-swap’s child PID and takes its SIGTERM directly. <code>--max</code> (fan control) breaks this, running the server as a child under a watchdog instead, so leave it off.</li>
<li>The request’s <code>model</code> field is logged, not checked, so llama-swap’s nickname passes through without a <code>useModelName</code>.</li>
<li>Thinking arrives in <code>reasoning_content</code>, as in vllm-mlx, rather than in <code>mlx_lm.server</code>’s <code>reasoning</code>.</li>
</ul>
<p>The entry is then:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode numberSource yaml number-lines code-with-copy"><code class="sourceCode yaml"><span id="cb22-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">"qwen3.8-flash-next"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb22-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">    cmd</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">: </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb22-3">      mtplx serve --host 127.0.0.1 --port ${PORT}</span>
<span id="cb22-4">      --model ${models}/Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed</span>
<span id="cb22-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">checkEndpoint</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> /health</span></span>
<span id="cb22-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ttl</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">900</span></span></code></pre></div></div>
<p>It fits into the same one-heavyweight-at-a-time group as the other drivers. At an 87 GiB peak, plus ~15 GB of resident utility models, it stays under the 112 GiB wired limit, though not by much.</p>
<p>MTPLX also keeps a persistent prefix cache on disk, in <code>~/.mtplx/session-bank</code>. This is KV state rather than text: after each turn, we write the conversation’s attention cache and recurrent-layer state to SSD, so a resumed session restores instead of re-prefilling. Flash-Next is cheap here, with only 12 of its 48 layers keeping a KV cache, at 2 KV heads of width 256, which by my arithmetic is about 24 KiB per token; a 100K-token agent session is ~2.4 GB. The store is on by default and capped at 100 GB, or a quarter of free disk, whichever is smaller; <code>--ssd-session-cache-max-size</code> caps it lower, and it churns, so it goes on the exclusion list.</p>
<p>Last, Qwen3.8’s chat template defaults to <code>xhigh</code> reasoning effort, which makes for slow agent turns; <code>reasoning_effort</code> per request turns it down. Unlike <code>mlx_lm.server</code>, MTPLX honours <code>presence_penalty</code>, so the Qwen3.8 cards’ non-thinking recipe is reachable here.</p>
</section>
</section>
<section id="splash" class="level3" data-number="5.8">
<h3 data-number="5.8" class="anchored" data-anchor-id="splash"><span class="header-section-number">5.8</span> Splash</h3>
<p><a href="https://github.com/incoai/splash?ref=danmackinlay.name">Splash</a> (Inco AI, Apache-2.0, <code>brew install incoai/tap/splash</code>) goes further in the same direction, to a per-model engine rather than a general runtime. Each supported model gets a trained DFlash2 draft model plus Metal kernels tuned to its shapes, and so far that is two models, Qwen3.8-27B and Qwen3.6-35B-A3B — which happen to be the two daily-drivers on this page. It loads either a GGUF (<code>splash serve --model unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M</code>) or the <code>mlx-community</code> 4-bit, does prefix caching and batching, speaks OpenAI and Anthropic, and has launchers for the usual coding agents (<code>splash opencode</code>, <code>splash claude</code>, <code>splash pi</code>, …). It needs an M3 or newer and macOS 26.4. The published benchmarks are on a 48 GB M5 Pro. Untested here.</p>
</section>
<section id="llama-server" class="level3" data-number="5.9">
<h3 data-number="5.9" class="anchored" data-anchor-id="llama-server"><span class="header-section-number">5.9</span> llama.cpp / llama-server</h3>
<p><a href="https://github.com/ggml-org/llama.cpp?ref=danmackinlay.name"><code>llama.cpp</code></a> ships its own server (<code>brew install llama.cpp</code>): <code>llama-server -m model.gguf</code> exposes an OpenAI-compatible endpoint with no daemon, no registry, no opinions.</p>
<p>Ollama wraps this same engine. However, llama-server is more configurable, exposing <code>llama.cpp</code> flags that Ollama does not, notably: YaRN context extension on a GGUF (which Ollama cannot do at all), the finer KV-cache quant ladder, speculative decoding with a draft model, and per-slot KV save/restore to disk.</p>
<p>It loads a <code>.gguf</code> from disk — no <code>ollama pull</code> into a separate store, no background service — which suits scripted or reproducible runs.</p>
<p>It loads the one model named at launch, though a newer router mode (start it with no <code>-m</code>) does dynamic multi-model load and unload. Context is the <code>-c</code> / <code>--ctx-size</code> flag, defaulting to <code>0</code> (the model’s full trained window). Other useful config options can be found in the cheat-sheet.</p>
</section>
<section id="llama-swap" class="level3" data-number="5.10">
<h3 data-number="5.10" class="anchored" data-anchor-id="llama-swap"><span class="header-section-number">5.10</span> One process per model, with llama-swap in front</h3>
<p>In auditioning the other servers on the page (I tried most of them), I ended up with a giant pile of manually-maintained configs, no single source of truth, and a variety of inconsistencies in the interface.</p>
<p>For example, many of the multi-model servers share settings globally between models even for models that want idiosyncratic defaults: One process owning nine models means one temperature for nine sampling recipes, one reasoning parser, one cache size, and a memory budget that then needs an eviction policy and a contention strategy to go with it. This is some complicated bookkeeping and it seems buggy and annoying in a mixture of ways that are too hard to spot (silently failing to sample diverse solutions) or too easy to spot (all ram exhausted, whole machine hangs).</p>
<p><a href="https://github.com/mostlygeek/llama-swap?ref=danmackinlay.name">llama-swap</a> (Go, one binary, <code>brew install</code>) circumvents some of these problems. It reads the <code>model</code> field of an incoming request, starts the right server if it is not up, kills it if unused, and proxies to it in between. Underneath, each model is its own process — <code>mlx_lm.server</code> here, but a <code>cmd:</code> is an arbitrary command line, so <code>vllm-mlx serve</code>, <code>omlx serve</code> and <code>./ds4-server</code> can all live behind the one port too.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode numberSource yaml number-lines code-with-copy"><code class="sourceCode yaml"><span id="cb23-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">models</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb23-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">"vibethinker-8bit"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb23-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">    cmd</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">: </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb23-4">      mlx_lm.server --port ${PORT}</span>
<span id="cb23-5">      --model ~/MLXModels/mlx-community/VibeThinker-3B-8bit</span>
<span id="cb23-6">      --temp 1.0 --top-p 0.95 --top-k 0 --decode-concurrency 16</span>
<span id="cb23-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ttl</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">900</span></span></code></pre></div></div>
<p>What this got me, in rough order of how much it improved my life:</p>
<ul>
<li><strong>Per-model settings stop being compromises.</strong> There is no server-wide temperature to be wrong for half the registry.</li>
<li><strong>Memory becomes measurable by standard system tools.</strong> One process per model means <code>footprint -p &lt;pid&gt;</code> reports that model’s Metal allocation under <code>IOAccelerator (graphics)</code> — 4.7 GB of weights on disk shows up as 5.2 GB RSS and 5510 MB of footprint. Unified memory means MLX buffers are ordinary physical pages, so nothing is hidden. The old setup needed a hand-maintained <code>estimated_memory_gb</code> per entry because nine models in one address space made per-model memory unmeasurable.</li>
<li><strong>Swapping models is cheap enough to ignore.</strong> Cold starts on my machine: 2.4 s for a 3 GB model, 7.7 s for a 20 GB one.</li>
<li><strong>Co-residency is five lines.</strong> A <code>groups</code> block keeps the small utility LLMs loaded together and lets the heavyweight agentic ones take turns — enough to hold a driver and a solver up at once.</li>
</ul>
<p>Name each entry after the weights rather than the job — <code>vibethinker-8bit</code>, not <code>solver</code>. That entry name is what a client puts in its <code>model</code> field, what comes back in the response, and is the thing we wish to track for provenance and diagnosis. The name that comes <em>back</em> from <code>mlx-lm</code> is whatever the upstream server thinks it is serving, and <code>useModelName</code> passes <code>mlx_lm.server</code> a filesystem path, so responses say something like <code>/Users/me/MLXModels/mlx-community/VibeThinker-3B-8bit</code> rather than <code>vibethinker-8bit</code>. The Rapid-MLX config does not have this problem, because <code>--served-model-name</code> makes the server answer to the nickname itself.</p>
<p>We can still create aliases for tasks. A useful role mechanism is a <code>warm</code> selector: <code>driver</code> resolves to whichever driver is already loaded and only cold-starts when none are, where a plain alias would force a 20 GB swap because the other driver happened to be up. <code>setParamsByID</code> gives one resident process two names and two recipes (<code>qwen3.6-35b</code> at temp 0.6 for coding, <code>qwen3.6-35b:thinking</code> at 1.0), and profiles repoint a default at runtime via <code>PUT /api/profiles/active</code> with <code>{"name": "..."}</code> — not <code>profile</code>, not <code>id</code>, both of which fail claiming the name is required. The same <code>setParams</code> / <code>stripParams</code> filters rewrite requests on the way through, which is where we drop the parameters vllm-mlx accepts and ignores and rename Open WebUI’s <code>repeat_penalty</code>.</p>
<p>A <code>peers</code> block folds another whole server in behind the same address — Osaurus on <code>:1337</code> becomes <code>osaurus/&lt;model&gt;</code> on the same port, which is helpful for the JANG builds nothing else loads. The difference from an ordinary entry is that llama-swap does <em>not</em> manage a peer’s process; for these it only forwards the endpoint requests. Don’t put locally managed models in peers; this is cursed and leads to multiple resident copies of the same model.</p>
<p>A peer also keeps its own defects, e.g.: Osaurus’s <code>/v1/embeddings</code> ignores the <code>model</code> field whether or not llama-swap sits in front of it.</p>
<p>What this gives up is the SSD prefix-cache: <code>mlx_lm.server</code> caches prefixes in memory only, so a long agentic second turn re-prefills from scratch. A server swap presumably discards the in-memory prefix cache outright, which makes that loss worse rather than better. This is the reason I continue watching Rapid-MLX, whose <code>--kv-disk-checkpoint-interval</code> is the nearest replacement. One further macOS wrinkle: llama-swap’s docs suggest running Python servers under Docker for clean <code>SIGTERM</code> handling, which is not available to us, since containers on macOS cannot use the Metal backend. It seems to work fine without, though. So far.</p>
<section id="llama-swap-cli" class="level4" data-number="5.10.1">
<h4 data-number="5.10.1" class="anchored" data-anchor-id="llama-swap-cli"><span class="header-section-number">5.10.1</span> Driving it</h4>
<p>There is no client CLI; the interface is one binary plus a handful of HTTP endpoints, so <code>curl</code> is the CLI by default on port <code>:8019</code> (keep that port locked down to local-only access!)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb24-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">llama-swap</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-config</span> ~/.config/llama-swap/mlxlm.yaml <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-listen</span> localhost:8019 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-watch-config</span></span></code></pre></div></div>
<p><code>-watch-config</code> reloads on save, which is what we want while adding a model but a nuisance otherwise. Swapping the whole backend is the same command with <code>rapid.yaml</code>, which is the point of keeping the two configs in step.</p>
<p>Asking what exists and what is up:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb25-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> localhost:8019/v1/models <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jq</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-r</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'.data[].id'</span></span>
<span id="cb25-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> localhost:8019/running <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jq</span></span>
<span id="cb25-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> localhost:8019/health</span></code></pre></div></div>
<p><code>/running</code> returns each resident model’s assigned upstream port and its fully expanded command line. An entry that misbehaves can be lifted straight out and run by hand, which covers most of the debugging.</p>
<p>Loading and evicting by hand:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb26-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># load without generating anything — 1.4 s for a 3 GB model here</span></span>
<span id="cb26-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> localhost:8019/upstream/vibethinker-8bit/v1/models</span>
<span id="cb26-3"></span>
<span id="cb26-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-X</span> POST localhost:8019/api/models/unload/vibethinker-8bit</span>
<span id="cb26-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-X</span> POST localhost:8019/api/models/unload</span></code></pre></div></div>
<p>Logs, where a model that fails to load explains itself:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb27-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> localhost:8019/logs <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tail</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-20</span></span>
<span id="cb27-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-Ns</span> localhost:8019/logs/stream/qwen3.6-35b</span>
<span id="cb27-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-Ns</span> localhost:8019/logs/stream/upstream</span></code></pre></div></div>
<p>There is a web view at <code>http://localhost:8019/ui</code> with the same information plus a playground, and <code>/metrics</code> speaks Prometheus.</p>
<p>Per-model memory, since one process per model makes it measurable:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb28-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> ~/.config/llama-swap/lsmem <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span> 8019</span>
<span id="cb28-2"></span>
<span id="cb28-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">MODEL</span>             PID      RSS  FOOTPRINT      GPU   WEIGHTS</span>
<span id="cb28-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vibethinker-8bit</span>   8427     3.4G       3.4G     3.1G   VibeThinker-3B-8bit</span></code></pre></div></div>
<p>The <code>GPU</code> column shows the <code>IOAccelerator (graphics)</code> line from <code>footprint -p &lt;pid&gt;</code>, i.e., the actual Metal allocation, and the ceiling is whatever <code>sysctl iogpu.wired_limit_mb</code> reports.</p>
<p>Pointing clients at it follows the usual OpenAI-compatible process:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb29-1">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AsyncOpenAI(base_url<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://localhost:8019/v1"</span>, api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>)</span>
<span id="cb29-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> client.chat.completions.create(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"viobethinker-8bit"</span>, ...)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -&gt; vibethinker-8bit</span></span></code></pre></div></div>
<p>For <a href="../notebook/ai_agents_applied.html#open-webui-setup">Open WebUI</a>, add <code>http://localhost:8019/v1</code> as an OpenAI connection and skip the bundled-Ollama path.</p>
</section>
<section id="gateways" class="level4" data-number="5.10.2">
<h4 data-number="5.10.2" class="anchored" data-anchor-id="gateways"><span class="header-section-number">5.10.2</span> Maybe not LiteLLM for a laptop hey</h4>
<p><a href="https://docs.litellm.ai/docs/proxy/configs?ref=danmackinlay.name">LiteLLM</a> (<code>litellm --config config.yaml</code>) is the other name that comes up here, and it solves an adjacent problem rather than this one. A <code>model_list</code> maps a public <code>model_name</code> onto a backend URL, given as an <code>api_base</code> plus a <code>litellm_params</code> block, so we get one facade over <code>localhost:1337</code>, <code>localhost:8000</code>, <code>localhost:11434</code> and whichever cloud providers we like. That block can also pin per-model sampling settings such as <code>temperature</code>, which is handy given how few clients and servers let us write those down anywhere. It adds fallback, retries, virtual keys with budgets, and spend logging on top.</p>
<p>What it does not do is lifecycle management. It routes to whatever server is already running; it will not start <code>ds4-server</code>, and it will not evict Qwen3.6 to make room for Laguna. On a laptop, residency <em>is</em> the problem, so LiteLLM is the wrong shape — and OpenRouter already covers the multi-cloud facade case for me.</p>
</section>
</section>
</section>
<section id="tuning" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="tuning"><span class="header-section-number">6</span> Tuning sampling, caching and context</h2>
<p>Whichever server we pick, the same five questions come up, and none are answered by the defaults: what sampling settings the model wants, which of those the server will actually apply, what it reuses between requests, whether it can emit more than one token per forward pass, and how far the context can be stretched. These cut across the whole list above, so they get their own section rather than being repeated per server; the cheat-sheet at the end provides the per-server answers in one table.</p>
<section id="sampling" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="sampling"><span class="header-section-number">6.1</span> Sampling defaults</h3>
<p>Sampling — temperature, top-p, top-k, and the output-token budget — is a decode-time choice set on each request. A server does not, in general, pick up the model’s recommended sampling settings for us.</p>
<p><code>mlx_lm.server</code>, for example, defaults to <code>temperature=0.0</code> (greedy), <code>top_p=1.0</code>, <code>top_k=0</code>, and <code>max_tokens=512</code>, and it does not read the model’s <code>generation_config.json</code>. So a reasoning model’s advised settings — VibeThinker advises temperature 1.0 / top-p 0.95 and a 64K-plus output budget — need the client to specify them; each model table below carries a <em>Recommended sampling</em> column with the per-model picks. Notably <code>temperature=0.0</code> breaks <a href="../notebook/automatic_maths.html#fan-out">maj@k voting</a>, since every sample comes back identical.</p>
</section>
<section id="sampling-real" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="sampling-real"><span class="header-section-number">6.2</span> Which sampling parameters actually work</h3>
<p>vllm-mlx 0.4.1 accepts parameters that it ignores. On the text path the sampler is built as <code>make_sampler(temp, top_p, min_p)</code>, and logits processors are constructed only from <code>repetition_penalty</code>. So:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>knob</th>
<th>accepted</th>
<th>reaches the sampler</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>temperature</code>, <code>top_p</code>, <code>min_p</code></td>
<td>yes</td>
<td><strong>yes</strong></td>
</tr>
<tr class="even">
<td><code>repetition_penalty</code></td>
<td>yes</td>
<td><strong>yes</strong> (text and multimodal paths both)</td>
</tr>
<tr class="odd">
<td><code>top_k</code></td>
<td>yes</td>
<td><strong>multimodal only</strong> — the text scheduler drops it</td>
</tr>
<tr class="even">
<td><code>presence_penalty</code></td>
<td>yes</td>
<td><strong>no</strong></td>
</tr>
</tbody>
</table>
<p>The dead ones are logged on the way in and dropped on the way through. So any <code>top-k 20</code> in a model card’s recipe — and most of the recipes in the tables below say exactly that — does nothing for a text model, while the same request against a VLM or omni build is honoured, which is a difference to remember before concluding a knob works. Two more traps in the same family:</p>
<ul>
<li><code>mlx-lm</code>’s <code>repetition_context_size</code> defaults to 20 tokens and vllm-mlx never overrides it, so the penalty sees a 20-token window. It will break a short stutter but will not touch a repeated paragraph.</li>
<li>vllm-mlx reads a model’s <code>generation_config.json</code> <em>for stop tokens only</em>, so a card’s recommended temperature never reaches the server on its own.</li>
</ul>
<p>When the registry has no per-model sampling — vllm-mlx’s does not — a server-wide default is the only lever, and that makes it a compromise rather than a setting. Mine splits: 0.6 for the agentic models, 1.0 for Cascade-2 and VibeThinker, so any single temperature is wrong for half of them, and the 0.7 fallback is a defensible midpoint. <code>min_p</code> and <code>repetition_penalty</code> are the two that <em>are</em> safe to set globally, because they truncate the degenerate tail without pinning a preferred temperature:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb30-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">--default-min-p</span> 0.02 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--default-repetition-penalty</span> 1.05</span></code></pre></div></div>
<p>Where to pin values depends on which layer issues the prompt.</p>
<ul>
<li><strong><code>mlx_lm.generate</code></strong> — flags per call: <code>--temp 1.0 --top-p 0.95 --max-tokens 40000</code> (<code>--top-k</code> already defaults to 0). Wrap it in an alias.</li>
<li><strong><code>mlx_lm.server</code></strong> — launch-time defaults via <code>--temp</code> / <code>--top-p</code> / <code>--top-k</code> / <code>--min-p</code>, overridden per request; Osaurus keeps the same defaults in its app settings, also overridden per request.</li>
<li><strong><code>transformers</code></strong> — a <code>GenerationConfig</code> passed at <code>generate()</code> time.</li>
<li><strong>Ollama</strong> — an exception, baking in a per-model default through a Modelfile: <code>PARAMETER temperature 1.0</code>, <code>PARAMETER top_p 0.95</code>, <code>PARAMETER num_predict 40000</code> for the output budget, <code>PARAMETER num_ctx 65536</code> for the context window.</li>
</ul>
<p>Output budget and context window are separate limits: the first caps how much the model may emit, the second how much prompt-plus-output the KV cache holds. A long-reasoning model can need both raised, or it truncates mid-derivation — and Ollama in particular drops the overflow silently once <code>num_ctx</code> is exceeded.</p>
</section>
<section id="sampling-clients-vllm-mlx" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="sampling-clients-vllm-mlx"><span class="header-section-number">6.3</span> What the client is able to send</h3>
<p>The server-side story above assumes we can <em>send</em> a value with a prompt, and which ones we can <a href="../notebook/agent_frontends.html#sampling-clients">vary with the client</a> — Goose sends temperature and nothing else, OpenCode sends arbitrary keys past one badly-named flag, Open WebUI pins a full set per model preset, Jan does too but only on a custom provider. That table lives with the frontends, since it is the same table whatever the server.</p>
<p>What makes it bite <em>here</em> is that vllm-mlx’s registry has no per-model sampling of its own, so there is no server-side fallback: whatever the client cannot send is a setting we simply do not have. That is why <a href="../notebook/agent_frontends.html#sampling-clients">Open WebUI’s presets</a> matter disproportionately on this stack — a preset per model is the only place the per-model temperatures in the tables below can actually be written down.</p>
<p>Gotcha: Open WebUI’s Advanced Params panel names the penalty <strong><code>repeat_penalty</code></strong> — Ollama’s spelling — while vllm-mlx reads <strong><code>repetition_penalty</code></strong>, so it is dropped in transit and the model keeps looping. The panel suffixes eight Ollama-only fields with <code>(Ollama)</code> — <code>num_ctx</code>, <code>keep_alive</code>, <code>think</code> and friends — but <code>repeat_penalty</code> is not among them, so it looks portable and is not. The fix is the <strong>Add Custom Parameter</strong> button at the bottom of the panel: add <code>repetition_penalty</code> by hand and skip the built-in row.</p>
</section>
<section id="prefix-caching" class="level3" data-number="6.4">
<h3 data-number="6.4" class="anchored" data-anchor-id="prefix-caching"><span class="header-section-number">6.4</span> Prefix caching</h3>
<p>The KV cache is the only state these servers keep between requests — there is no session object; we resend the whole history each call. What they reuse is the <em>prefix</em>: the shared start of the conversation (system prompt, tool definitions, history so far) keeps its KV, so an agentic loop prefills only the new suffix. Matching is content-addressed — on the tokens, not a session ID — so it happens by itself, and there is nothing to switch on.</p>
<p>The consequence worth planning around is that the match is on a <em>prefix</em>, so anything that edits the front of the history throws the whole thing away. A harness that trims old turns to fit the context window, or rewrites the system prompt between calls, re-prefills from scratch every time and we pay for it in time-to-first-token without any error to tell us why. Appending is free; editing is not.</p>
<p>Different servers hold this cache for different lengths of time:</p>
<ul>
<li><strong><code>mlx_lm.server</code></strong> — an in-memory LRU, longest-prefix matched, reporting <code>cached_tokens</code> in the usage block; bounded by <code>--prompt-cache-size</code> / <code>--prompt-cache-bytes</code>, held for the process.</li>
<li><strong><code>llama-server</code></strong> — <code>--cache-prompt</code> is on by default (one KV per slot; <code>--cache-reuse</code> even salvages chunks after a mid-prompt edit), and slots can be saved to disk.</li>
<li><strong>Ollama</strong> — the same <code>llama.cpp</code> reuse, alive as long as the model stays loaded (<code>OLLAMA_KEEP_ALIVE</code>).</li>
<li><strong>Osaurus</strong> — automatic; headless under <code>osaurus serve</code> the cache lives for the server process (governed by the Strict/Flexible policy), and in the GUI it is per chat window, warmed the moment one opens.</li>
</ul>
<p>Prefill is chunked and continuously batched besides, so concurrent requests interleave rather than queue — but the prefix skip is the bigger win.</p>
</section>
<section id="mtp" class="level3" data-number="6.5">
<h3 data-number="6.5" class="anchored" data-anchor-id="mtp"><span class="header-section-number">6.5</span> Speculative decoding and MTP</h3>
<p>Emitting one token at a time is bound by memory bandwidth rather than arithmetic: each token drags the active parameters across from RAM, and the compute units spend most of that time waiting for them. Speculative decoding spends that idle arithmetic. Something cheap <em>drafts</em> the next few tokens, the full model scores the whole draft in a single forward pass — the pass that would otherwise have produced one token — and every drafted token it agrees with is kept. A miss costs the pass we were going to pay for anyway, so the arrangement is roughly free when it fails and worth several tokens per pass when it works. Done properly the output distribution is unchanged and only the speed differs, which is the entire appeal, and which is also the part the vllm-mlx implementation gives up.</p>
<p>The draft has to come from somewhere, and there are two answers.</p>
<dl>
<dt>Draft model</dt>
<dd>
A second, much smaller model does the drafting — say a 0.5B proposing for a 30B — sharing a tokenizer with the big one. This is what <code>--draft-model</code> means in <code>mlx_lm.server</code> and <code>llama-server</code>. The cost is a second set of weights resident in RAM, plus finding a small model that pairs with the large one.
</dd>
<dt>MTP head, multi-token prediction</dt>
<dd>
The model drafts for itself. A checkpoint trained with multi-token prediction <span class="citation" data-cites="Gloeckle2024Better">(Gloeckle et al. 2024)</span> — Qwen3.6 and Qwen3.8 here, the DeepSeek lineage before them — carries a small extra head that predicts a position or two past the ordinary next-token head. Nothing extra loads, because the head ships in the model directory with the rest of the weights. This is what <code>--enable-mtp</code> means in vllm-mlx.
</dd>
</dl>
<p>The verification step is the same either way, so a server’s rule for what happens when the draft and the full model disagree applies to both.</p>
</section>
<section id="yarn" class="level3" data-number="6.6">
<h3 data-number="6.6" class="anchored" data-anchor-id="yarn"><span class="header-section-number">6.6</span> Stretching the context window</h3>
<p>Deep lore for the optimizers.</p>
<p>A model’s <a href="../notebook/position_encoding.html">positional encoding</a> is trained out to some fixed length, and that trained length is baked into its knowledge of the context window. Some of the huge context numbers advertised are that same window stretched at load time, rather than a property of the weights. Qwen3.6, for example, trains its rotary positions to 256K (262,144 tokens); the 1M figure quoted for it is that same window extended ~4×, and getting the extension costs us something in both quality and RAM.</p>
<p>The mechanism is RoPE interpolation. RoPE encodes each token’s position as a rotation; interpolation rescales those rotations so a position past the trained window maps back into the range the model saw during training, instead of falling off the end into rotations it has never seen. The common variant is <strong>YaRN</strong> (“yet another RoPE extension”), which rescales per frequency rather than uniformly. A <code>factor</code> of 4.0 takes Qwen3.6’s 256K to ~1M, for example.</p>
<p>Every mainstream implementation I’ve seen applies the rescaling statically, fixing it at load and applying it to every prompt regardless of length. A model loaded with <code>factor</code> 4.0 rescales a 2K-token prompt exactly as hard as a 900K one, and short prompts lose some accuracy for a long window they aren’t using. So we switch YaRN on only when we want the long window, and set <code>factor</code> to the longest context we actually expect rather than the largest the model will accept.</p>
<p>How to turn it on depends on the runtime.</p>
<p><code>transformers</code> and <code>mlx-lm</code> read a <code>rope_scaling</code> block straight from the <code>config.json</code> that ships in the model’s own directory:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode numberSource json number-lines code-with-copy"><code class="sourceCode json"><span id="cb31-1"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rope_scaling"</span><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb31-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"rope_type"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"yarn"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb31-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"factor"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb31-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"original_max_position_embeddings"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">262144</span></span>
<span id="cb31-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<ul>
<li><strong><code>llama.cpp</code> / <code>llama-server</code></strong> take flags: <code>--rope-scaling yarn --rope-scale 4 --yarn-orig-ctx 262144</code>, alongside the usual <code>-c</code>.</li>
<li><strong>Ollama</strong> exposes no YaRN settings. We inherit whatever the person who converted the GGUF baked into its metadata.</li>
<li><strong>Osaurus</strong>’s Swift engine has the YaRN code, but its Qwen3 and Llama wrappers do not route through it, so YaRN is unavailable for now.</li>
</ul>
<p>YaRN increases the maximum context window, but does not shrink the KV cache cost of storing that context, so we remain RAM-constrained.</p>
</section>
<section id="server-config" class="level3" data-number="6.7">
<h3 data-number="6.7" class="anchored" data-anchor-id="server-config"><span class="header-section-number">6.7</span> Configuring each server</h3>
<p>All the settings in one place.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Server</th>
<th>Context cap</th>
<th>KV-cache quant</th>
<th>Prefix cache</th>
<th>Extend context (YaRN)</th>
<th>Sampling defaults</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>llama-server</code></td>
<td><code>-c N</code> (<code>-c 0</code> = model max)</td>
<td><code>--cache-type-k/v q8_0</code> (also <code>q4_0</code>, <code>q5_0</code>, <code>iq4_nl</code>)</td>
<td>on by default; <code>--cache-reuse N</code> after edits, <code>--slot-save-path</code> to disk</td>
<td><code>--rope-scaling yarn --rope-scale N --yarn-orig-ctx N</code></td>
<td>CLI (<code>--temp</code>, <code>--top-k</code>, …) + per request</td>
</tr>
<tr class="even">
<td>Ollama</td>
<td><code>num_ctx</code> / <code>OLLAMA_CONTEXT_LENGTH</code> (auto 4k/32k/256k by VRAM)</td>
<td><code>OLLAMA_KV_CACHE_TYPE=q8_0</code> (needs flash attn)</td>
<td>automatic, lives while loaded (<code>OLLAMA_KEEP_ALIVE</code>)</td>
<td>none — inherits the GGUF</td>
<td>Modelfile <code>PARAMETER</code> + per request</td>
</tr>
<tr class="odd">
<td><code>mlx_lm.server</code></td>
<td>none — grows to RAM, cap in the harness</td>
<td>none (only <code>mlx_lm.generate --kv-bits</code>)</td>
<td>automatic (<code>--prompt-cache-size</code> / <code>--prompt-cache-bytes</code>)</td>
<td><code>config.json</code> only</td>
<td>CLI (<code>--temp</code>, <code>--top-p</code>, …) + per request</td>
</tr>
<tr class="even">
<td>Osaurus</td>
<td>auto per-model (no global setting)</td>
<td>none exposed (vmlx defaults)</td>
<td>automatic</td>
<td>wrappers don’t route it (above)</td>
<td>Settings default + per request</td>
</tr>
<tr class="odd">
<td>vllm-mlx</td>
<td><code>--max-tokens</code> / <code>--max-request-tokens</code>; weights budget in the YAML</td>
<td><code>--kv-cache-quantization</code>, <code>--kv-cache-quantization-bits</code> (4 or 8)</td>
<td>in-memory, plus an opt-in SSD cold tier (<code>--ssd-cache-dir</code>)</td>
<td><code>config.json</code> only</td>
<td><code>--default-temp</code>/<code>--default-top-p</code>/… + per request</td>
</tr>
<tr class="even">
<td>oMLX</td>
<td>cap in the harness; memory guard via <code>--memory-guard-gb</code></td>
<td>none exposed</td>
<td>persistent two-tier — RAM hot + SSD cold (<code>--paged-ssd-cache-dir</code>), survives restart</td>
<td><code>config.json</code> only</td>
<td>admin panel per-model + per request</td>
</tr>
<tr class="odd">
<td><code>ds4-server</code></td>
<td><code>--ctx N</code>; output cap via the API</td>
<td>fixed by the model variant, not settable</td>
<td>in-memory reuse, durable via <code>--kv-disk-dir</code></td>
<td>n/a (single model)</td>
<td>per request</td>
</tr>
</tbody>
</table>
<p>Flash attention does not get its own column because it is no longer a per-server decision: <code>llama.cpp</code> defaults <code>-fa</code> to <code>auto</code> and turns it on wherever Metal supports it; Ollama switches it on per architecture for the families here (Qwen3.x, Nemotron, Gemma, gpt-oss); and the MLX servers plus <code>ds4</code> always run a fused attention kernel. The one place it still needs a hand is Ollama’s KV-cache quant, which only takes effect with <code>OLLAMA_FLASH_ATTENTION=1</code> set alongside it.</p>
<p>Sensible headless starting points:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb32-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 64K context (below): a useful bound well under the trained max — raise or lower for your RAM</span></span>
<span id="cb32-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># llama.cpp — flash attention is automatic; quantize the cache, choose a context</span></span>
<span id="cb32-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">llama-server</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> model.gguf <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> 65536 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--cache-type-k</span> q8_0 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--cache-type-v</span> q8_0</span>
<span id="cb32-4"></span>
<span id="cb32-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Ollama — env vars; cache quant needs flash attention enabled</span></span>
<span id="cb32-6"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OLLAMA_FLASH_ATTENTION</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>1 <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OLLAMA_KV_CACHE_TYPE</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>q8_0 <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OLLAMA_CONTEXT_LENGTH</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>65536 <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> serve</span>
<span id="cb32-7"></span>
<span id="cb32-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># mlx-lm — no cache-quant/context flag; set the model’s sampling values, cap context in the harness</span></span>
<span id="cb32-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mlx_lm.server</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--model</span> mlx-community/<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>repo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> --temp 0.6 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--top-p</span> 0.95 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--top-k</span> 20</span>
<span id="cb32-10"></span>
<span id="cb32-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># oMLX — point it at the shared MLX tree; turn on the SSD cold tier and a memory ceiling</span></span>
<span id="cb32-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">omlx</span> serve <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--model-dir</span> ~/MLXModels <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--paged-ssd-cache-dir</span> ~/.omlx/cache <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--memory-guard-gb</span> 96</span></code></pre></div></div>
<p>The <code>--paged-ssd-cache-dir</code> on that last line persists across restarts and gets rewritten every session, so it is the one path here that we want to keep out of backups and Spotlight.</p>
<p>Osaurus is tuned in its Settings pane rather than via a command line, and <code>ds4</code>’s full flag set is in its own section.</p>
</section>
</section>
<section id="transformers" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="transformers"><span class="header-section-number">7</span> Programmatic access via <code>transformers</code></h2>
<p>When we want to <em>do things</em> to a model — embed text, fine-tune, run <a href="../notebook/ai_mech_interp.html">interpretability</a> tools, sample from internal layers, or anything that touches the model internals — we drop down to Hugging Face <code>transformers</code> in our own Python process; no server process, no HTTP API, just direct access to the calculations.</p>
<p><a href="../notebook/nn_vector_embedding.html">Embeddings</a> for <a href="../notebook/ai_search.html">search</a> are why I currently do this. For this, I use <a href="https://www.sbert.net/?ref=danmackinlay.name"><code>sentence-transformers</code></a> (<code>uv pip install sentence-transformers</code> — it pulls <code>torch</code> with it), a thin wrapper around <code>transformers</code> that exposes the embedding API:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb33-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sentence_transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> SentenceTransformer</span>
<span id="cb33-2">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SentenceTransformer(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mixedbread-ai/mxbai-embed-large-v1"</span>)</span>
<span id="cb33-3">embs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.encode(texts, normalize_embeddings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, convert_to_numpy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span></code></pre></div></div>
<p>The first call downloads the weights, the tokenizer config (<code>tokenizer.json</code>), and the model config from huggingface.co into <code>~/.cache/huggingface/hub/</code>. Inference runs in our process via PyTorch. Tokenization runs in the same process, via HF’s Rust <code>tokenizers</code> library, reading the same <code>tokenizer.json</code> the model was published with. One process, one library, one set of files.</p>
<p>On Apple Silicon, we see a 15× speedup over fp32 by switching to <code>float16</code> on MPS, with indistinguishable quality:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb34-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb34-2">gpu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cuda.is_available() <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> torch.backends.mps.is_available()</span>
<span id="cb34-3">kwargs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model_kwargs"</span>: {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dtype"</span>: torch.float16}} <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> gpu <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {}</span>
<span id="cb34-4">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SentenceTransformer(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mixedbread-ai/mxbai-embed-large-v1"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs)</span></code></pre></div></div>
<p>For text generation (rather than embedding) the equivalent is <code>AutoModelForCausalLM.from_pretrained(...)</code>. PyTorch is rarely the fastest path on Apple Silicon — <code>llama.cpp</code> and MLX usually win on tokens-per-second — but it is the path that lets us see what the model is doing without arsing around. Activations, attention patterns, hidden states, custom sampling, etc. are all possible from the Python prompt.</p>
</section>
<section id="models" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="models"><span class="header-section-number">8</span> Fun models</h2>
<p>Once we have a stack running, the next question is what to pull through it. A non-exhaustive list of picks I have been playing with is below, sorted by how I use them. Sizes are on-disk figures rather than parameter counts, since the disk figure is the one that has to clear the memory ceiling — except for the newer models that leave a lookup table on disk, where the resident figure is the one that counts.</p>
<section id="models-math" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="models-math"><span class="header-section-number">8.1</span> For mathematical reasoning</h3>
<p>The <em>Role</em> and <em>Target</em> columns are <a href="../notebook/ai_reasoning.html#targets">the reasoning notes’ taxonomy</a> — the role is which slot in a harness the model fills, the target is what it emits, and that determines which checker we owe it. A <strong>generalist</strong> does maths alongside chat and tool-use, takes whichever target we ask it for, and can be driven by an ordinary agent. A <strong>solver</strong> emits a boxed final answer, trading chat fluency for reasoning, and wants <a href="../notebook/automatic_maths.html#iterate">a specialized loop</a> — <a href="../notebook/ai_reasoning.html#tir-vs-cot">tool-integrated reasoning</a> where the model runs code, <a href="../notebook/automatic_maths.html#fan-out">maj@k voting</a> where it does not. An <strong>informal prover</strong> emits a proof in natural language, which only a judge can grade. A <strong>prover</strong> emits Lean, which a compiler accepts or rejects.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Model</th>
<th>Role</th>
<th>Target</th>
<th>Size</th>
<th>Sampling</th>
<th>Run via</th>
<th>Why</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-0528-Qwen3-8B?ref=danmackinlay.name">DeepSeek-R1-0528-Qwen3-8B</a></td>
<td>generalist</td>
<td>final answer; informal if asked</td>
<td>~5 GB</td>
<td>temp 0.6 / top-p 0.95, ≥64K out</td>
<td>MLX / Ollama GGUF</td>
<td>AIME-2024 86% on the card, matching Qwen3-235B-thinking <em>on that benchmark</em>. ⚠ Its text is <strong>unreadable on this stack</strong></td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/deepreinforce-ai/Ornith-1.0-9B?ref=danmackinlay.name">Ornith-1.0-9B</a></td>
<td>generalist</td>
<td>final answer; informal if asked</td>
<td>6 GB 4-bit</td>
<td>temp 0.6 / top-p 0.95</td>
<td><a href="https://huggingface.co/mlx-community/Ornith-1.0-9B-4bit?ref=danmackinlay.name"><code>mlx-community/Ornith-1.0-9B-4bit</code></a></td>
<td>Handy small-task mode; gets <code>7^2026 mod 13</code> right with a tidy order-6 derivation — in 6,316 tokens, against VibeThinker’s 786 for the same answer. Capable, not economical.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/microsoft/Phi-4-reasoning-plus?ref=danmackinlay.name">Phi-4-Reasoning-Plus-14B</a></td>
<td>generalist</td>
<td>final answer; informal if asked</td>
<td>~8 GB</td>
<td>temp 0.8 / top-p 0.95 / top-k 50; wants a ChatML system prompt</td>
<td>MLX / GGUF</td>
<td>A different reasoning-trace style for triangulating DeepSeek — not a stronger model.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/nvidia/Nemotron-Cascade-2-30B-A3B?ref=danmackinlay.name">Nemotron-Cascade-2-30B-A3B</a></td>
<td>generalist</td>
<td>final answer; informal if asked</td>
<td>33 GB mxfp8</td>
<td>temp 1.0 / top-p 0.95</td>
<td>vllm-mlx — the <a href="https://huggingface.co/mlx-community/Nemotron-Cascade-2-30B-A3B-mxfp8?ref=danmackinlay.name"><code>mlx-community</code> mxfp8</a> build; also GGUF</td>
<td>NVIDIA’s IMO-2025-gold model — a Mamba (SSM) + MoE hybrid for linear context scaling. Still the newest Cascade. Loads, generates and tool-calls fine under vllm-mlx despite the <code>nemotron_h</code> arch being exotic; it is Osaurus’s Swift engine that can’t take it.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/nvidia/OpenMath-Nemotron-14B?ref=danmackinlay.name">OpenMath-Nemotron-14B</a></td>
<td>solver</td>
<td>final answer</td>
<td>~8 GB</td>
<td>temp 0.6 / top-p 0.95, sample</td>
<td>MLX / GGUF</td>
<td>The sweet-spot solver — ~the AIMO-2-winning <a href="https://huggingface.co/nvidia/OpenMath-Nemotron-32B?ref=danmackinlay.name">32B</a>’s score at half the RAM; <a href="../notebook/ai_reasoning.html#tir-vs-cot">tool mode</a> wants <a href="../notebook/automatic_maths.html#iterate">NeMo-Skills</a>.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/Qwen/Qwen2.5-Math-72B-Instruct?ref=danmackinlay.name">Qwen2.5-Math-72B-Instruct</a></td>
<td>solver</td>
<td>final answer</td>
<td>~40 GB</td>
<td>greedy (<code>do_sample=false</code>)</td>
<td>MLX 8-bit / GGUF</td>
<td>Push-button <a href="../notebook/ai_reasoning.html#tir-vs-cot">tool mode</a> — <a href="../notebook/ai_agents_applied.html#qwen-agent">Qwen-Agent</a> drives its code loop, no extra infra.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/Skywork/Skywork-OR1-Math-7B?ref=danmackinlay.name">Skywork-OR1-Math-7B</a></td>
<td>solver</td>
<td>final answer</td>
<td>7B</td>
<td>temp 0.6 / top-p 1.0, 32K out</td>
<td>GGUF</td>
<td>Best small pure-reasoning solver — AIME-2024 69.8 at 7B, DeepSeek-R1-based.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/WeiboAI/VibeThinker-3B?ref=danmackinlay.name">VibeThinker-3B</a></td>
<td>solver</td>
<td>final answer</td>
<td>~3 GB 8-bit / ~6 GB bf16</td>
<td>temp 1.0 / top-p 0.95, 64K out (→100K hard)</td>
<td>MLX 8-bit (fan-out default) or bf16</td>
<td>Weibo’s 3B <a href="../notebook/ai_reasoning.html#co-design">verifiable-reasoning</a> solver (MIT) — AIME26 94.3, HMMT25 89.3 self-reported, in 3 GB. Solver-only.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/lm-provers/QED-Nano?ref=danmackinlay.name">QED-Nano</a></td>
<td>informal prover</td>
<td>informal proof</td>
<td>4.3 GB 8-bit</td>
<td>as Qwen3-4B-Thinking</td>
<td><a href="https://huggingface.co/mlx-community/QED-Nano-8bit?ref=danmackinlay.name"><code>mlx-community/QED-Nano-8bit</code></a></td>
<td>4B post-trained for <em>proof writing</em> (Apache-2.0) — IMO-ProofBench 40%, matching GPT-OSS-120B at 1/30 the size, +20 points over its Qwen3-4B base.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/NousResearch/nomos-1?ref=danmackinlay.name">nomos-1</a></td>
<td>informal prover</td>
<td>informal proof</td>
<td>~32 GB 8-bit</td>
<td>as Qwen3-30B-A3B-Thinking-2507 (temp 0.6 / top-p 0.95 / top-k 20); card asks for <em>no</em> system prompt</td>
<td><a href="https://huggingface.co/alexcovo/nomos-1-mlx-8Bit?ref=danmackinlay.name"><code>alexcovo/nomos-1-mlx-8Bit</code></a>; also <a href="https://huggingface.co/DevQuasar/NousResearch.nomos-1-GGUF?ref=danmackinlay.name">GGUF</a></td>
<td>Nous Research’s 30B-A3B post-train for <em>natural-language</em> proof-writing, ships <a href="../notebook/ai_reasoning.html#judging">its own harness</a> — Putnam 2025 87/120, against 24/120 for its untuned Qwen3-30B-A3B-Thinking-2507 base. 3B active, so fan-out is cheap. The card’s <code>--tp-size 8</code> is their serving config, but it goes lower.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/Goedel-LM/Goedel-Prover-V2-32B?ref=danmackinlay.name">Goedel-Prover-V2-32B</a></td>
<td>prover</td>
<td>formal proof (Lean)</td>
<td>35 GB 8-bit</td>
<td>per card</td>
<td><a href="https://huggingface.co/mlx-community/Goedel-Prover-V2-32B-8bit?ref=danmackinlay.name"><code>mlx-community/…-8bit</code></a></td>
<td>Emits Lean rather than natural language, so the output is machine-checkable — <a href="../notebook/ai_reasoning.html#from-the-target">the formal end of the target axis</a>.</td>
</tr>
</tbody>
</table>
<p>The solver rows above are pure CoT, innocent of tool use. They can fit into a larger workflow still, as a <a href="../notebook/automatic_maths.html#oracle"><code>solve()</code> oracle a general agent dispatches to</a>. In this case we would serve it on a batching endpoint that keeps it resident beside the agentic driver, and let the driver call it when it needs some help on a sub-problem.</p>
<p>So the <em>Target</em> column is a procurement decision, not a label. <a href="../notebook/automatic_maths.html#fan-out">maj@k voting</a> pays off on a solver and buys nothing on either proof target, because <a href="../notebook/ai_reasoning.html#from-the-target">a set of arguments has no mode to take</a> — download Goedel and we are also committing to a Lean toolchain; download nomos-1 or QED-Nano and we are committing to a judge instead. Which is why nomos-1 ships its own harness and why the two are not interchangeable on this page, however similar their file sizes look.</p>
<p>VibeThinker is a case where <a href="../notebook/ai_reasoning.html#claims-and-errors">the harness matters</a> a lot. Its Claim-Level Reliability Assessment lifts AIME26 from 94.3 to 97.1 off the same 3 GB checkpoint <span class="citation" data-cites="Xu2026VibeThinker3B">(Xu et al. 2026)</span>.</p>
</section>
<section id="models-agentic" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="models-agentic"><span class="header-section-number">8.2</span> For agentic flows</h3>
<p>These are the weights <a href="../notebook/automatic_coding.html#offline-coding">a coding harness</a> needs. Sizes here are for the specific MLX build named in the <em>Run via</em> column, since a 4-bit and an 8-bit of the same model are different procurement decisions.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Model</th>
<th>Role</th>
<th>Size</th>
<th>Sampling</th>
<th>Run via</th>
<th>Why</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://huggingface.co/Qwen/Qwen3.6-35B-A3B?ref=danmackinlay.name">Qwen3.6-35B-A3B</a></td>
<td>daily-driver</td>
<td>20 GB 4-bit</td>
<td>thinking temp 1.0 / top-p 0.95 / top-k 20 (0.6 for coding); non-thinking 0.7 / top-p 0.8; never greedy</td>
<td><a href="https://huggingface.co/mlx-community/Qwen3.6-35B-A3B-4bit?ref=danmackinlay.name"><code>mlx-community/…-4bit</code></a> via vllm-mlx; Osaurus one-click</td>
<td>3B-active MoE, 256K ctx (1M via YaRN), vision; MTP for faster decode (new on Mac — disable if output loops). What I actually run.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/Qwen/Qwen3.8-27B?ref=danmackinlay.name">Qwen3.8-27B</a></td>
<td>daily-driver</td>
<td>18 GB 4-bit</td>
<td>thinking temp 1.0 / top-p 0.95 / top-k 20; non-thinking 0.7 / top-p 0.8 / presence-penalty 1.5 (only via MTPLX, below)</td>
<td><a href="https://huggingface.co/mlx-community/Qwen3.8-27B-4bit?ref=danmackinlay.name"><code>mlx-community/Qwen3.8-27B-4bit</code></a>; also <code>qwen3.8:27b-mlx</code> in Ollama</td>
<td>Apache-2.0, 2026-08-13. Terminal-Bench 2.1 <strong>73.0</strong>, above Laguna at a quarter of the disk; SWE-bench Pro 61.7. But <strong>dense</strong>, so see below before swapping.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/deepreinforce-ai/Ornith-1.0-35B?ref=danmackinlay.name">Ornith-1.0-35B</a></td>
<td>daily-driver</td>
<td>20 GB 4-bit / 29 GB 6-bit</td>
<td>temp 0.6 / top-p 0.95 / top-k 20</td>
<td><a href="https://huggingface.co/mlx-community/Ornith-1.0-35B-4bit?ref=danmackinlay.name"><code>mlx-community/Ornith-1.0-35B-4bit</code></a></td>
<td>DeepReinforce’s agentic-coding post-train of Qwen3.5-35B-A3B (MIT, 256K ctx) — Terminal-Bench 2.1 64.2, SWE-bench Verified 75.6. Same shape and same disk footprint as the row above, so it is a straight swap. A <a href="https://huggingface.co/mlx-community/Ornith-1.0-9B-4bit?ref=danmackinlay.name">9B</a> exists at 6 GB.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/Qwen/Qwen3.8-Flash-Next?ref=danmackinlay.name">Qwen3.8-Flash-Next</a></td>
<td>heavyweight</td>
<td>~83 GB resident + 32 GB n-gram table on SSD (<a href="https://huggingface.co/Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed?ref=danmackinlay.name">MTPLX Optimized-Speed</a>, peaks at 87 GiB); 105 GB via Ollama</td>
<td>thinking temp 1.0 / top-p 0.95 / top-k 20; non-thinking 0.7 / top-p 0.8 / presence-penalty 1.5</td>
<td>MTPLX; <a href="https://huggingface.co/mlx-community/Qwen3.8-Flash-Next-4bit?ref=danmackinlay.name"><code>mlx-community/Qwen3.8-Flash-Next-4bit</code></a> via <code>mlx-vlm</code> from <code>main</code>; <code>qwen3.8-flash-next:125b-mlx</code> in Ollama; <code>ds4</code></td>
<td>Qwen’s preview of the Qwen4 architecture, 2026-08-26: 125B total, 6B active, plus a 51B n-gram embedding that is read a few rows at a time and so can stay on disk. Vision, 256K ctx. SWE-bench Pro 62.5 against 61.7 for Qwen3.8-27B, DeepSWE 58.7 against 42.2, both from Qwen’s own Claude Code harness. Qwen Community License 1.0, not Apache.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/zai-org/GLM-5.3-Flash?ref=danmackinlay.name">GLM-5.3-Flash</a></td>
<td>heavyweight</td>
<td>90 GiB <code>ds4</code> Q2; 102–120 GB <a href="https://unsloth.ai/docs/models/glm-5.3-flash?ref=danmackinlay.name">Unsloth GGUF</a> (IQ2_XXS–IQ3_XXS)</td>
<td>temp 1.0 / top-p 0.95; <code>reasoning_effort</code> low/high/max, defaulting to max; <code>clear_thinking=true</code> for chat</td>
<td><code>ds4</code> <code>glm53-q2</code>; the Unsloth GGUF needs their <code>llama.cpp</code> PR</td>
<td>Z.ai, MIT, 2026-08-26. 320B total, 18B active, hybrid sparse + linear attention, natively multimodal. Vendor numbers: Terminal-Bench 2.1 84.3, DeepSWE 63.4. antirez calls the Q2 “close enough to a 128 GB machine’s memory budget that other workloads and context size matter”; the IQ3_XXS is over the 112 GB wired limit. No resident speed for an M5 Max that I trust; the streamed Q4 does 12–15 tok/s. The <code>mlx-community</code> 4-bit is far too big.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/poolside/Laguna-S-2.1?ref=danmackinlay.name">Laguna-S-2.1</a></td>
<td>heavyweight</td>
<td>36 GB <code>oQ2e</code> / 64 GB <code>oQ4e</code></td>
<td><code>enable_thinking: true</code>, keep think blocks in history</td>
<td><a href="https://huggingface.co/mlx-community/Laguna-S-2.1-oQ4e?ref=danmackinlay.name"><code>mlx-community/Laguna-S-2.1-oQ4e</code></a> — needs <code>mlx-vlm</code> 0.6.3+ or oMLX 0.5.3+</td>
<td>Poolside’s 118B/8B-active coder, 1M ctx, OpenMDW-1.1 so commercial use is fine. Terminal-Bench 2.1 70.2, SWE-bench Pro 59.4 — the strongest thing here that fitted, until the late-August Flash releases above. <code>mlx-lm</code> does not know the <code>laguna</code> arch yet (<a href="https://github.com/ml-explore/mlx-lm/pull/1223?ref=danmackinlay.name">mlx-lm#1223</a>); <code>mlx-vlm</code> runs it as text-only.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16?ref=danmackinlay.name">Nemotron-3-Super-120B-A12B</a></td>
<td>heavyweight</td>
<td>48 GB <a href="https://huggingface.co/mlx-community/NVIDIA-Nemotron-3-Super-120B-A12B-OptiQ-2bit?ref=danmackinlay.name">OptiQ 2-bit</a></td>
<td>temp 0.6 / top-p 0.95</td>
<td>any MLX runtime</td>
<td>124B total, 12B active. The 2-bit is the only <code>mlx-community</code> build, which is a lot of quantization to trust — mixed precision is what makes it arguable at all.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16?ref=danmackinlay.name">Nemotron-3.5-Lightning-30B-A3B</a></td>
<td>fast worker</td>
<td>17.8 GB 4-bit</td>
<td>temp 1.0 / top-p 0.95</td>
<td><a href="https://huggingface.co/mlx-community/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-4bit?ref=danmackinlay.name"><code>mlx-community/…-4bit</code></a>; <code>nemotron-3.5-lightning:30b-a3b-mlx</code> in Ollama</td>
<td>NVIDIA, OpenMDW-1.1, 2026-08-11; Mamba-2 + attention MoE. Same shape and disk footprint as Qwen3.6-35B-A3B, but not a swap for it: NVIDIA’s own table puts it at Terminal-Bench 2.1 24.6 against 44.4 for Qwen3.6-35B-A3B, SWE-bench Verified 51.6 against 70.1. Pitched at speed, for always-on agents.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/mistralai/Devstral-Small-2-24B-Instruct-2512?ref=danmackinlay.name">Devstral-Small-2-24B</a></td>
<td>control</td>
<td>15 GB 4-bit</td>
<td>per card</td>
<td><a href="https://huggingface.co/mlx-community/Devstral-Small-2-24B-Instruct-2512-4bit?ref=danmackinlay.name"><code>mlx-community/…-4bit</code></a></td>
<td>Mistral’s dense coding-agent model. Small and dull, which is the point: it is the cheap control for “does the 35B earn its RAM?”.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning?ref=danmackinlay.name">Nemotron-3-Nano-Omni-30B-A3B</a></td>
<td>multimodal</td>
<td>33 GB 8-bit / 8 GB mxfp4</td>
<td>temp 0.6 / top-p 0.95</td>
<td><a href="https://huggingface.co/mlx-community/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-8bit?ref=danmackinlay.name"><code>mlx-community</code> native 8-bit</a> with <code>mllm: true</code>; Osaurus (<a href="https://huggingface.co/OsaurusAI/Nemotron-3-Nano-Omni-30B-A3B-JANGTQ4?ref=danmackinlay.name">JANGTQ4</a>)</td>
<td>NVIDIA’s omni all-rounder — hybrid Mamba+MoE, native text/image/audio/video, 256K ctx. Take the <code>mlx-community</code> build, not the Osaurus repackaging: the latter hides its omni descriptor in a side-file that <code>mlx-vlm</code> never reads, and dies at weight-load with <code>Received N parameters not in model</code>.</td>
</tr>
</tbody>
</table>
<section id="dense-vs-moe" class="level4" data-number="8.2.1">
<h4 data-number="8.2.1" class="anchored" data-anchor-id="dense-vs-moe"><span class="header-section-number">8.2.1</span> The dense-versus-MoE trade, via Qwen3.8</h4>
<p>Every other daily-driver on that table is a sparse MoE with about 3B parameters active per token. Qwen3.8-27B is dense, so it activates all 27B, and the benchmark win comes with a decode cost that no quantization recovers — one third-party measurement puts it at 5–6 tok/s on an M4 mini, and while a Max-tier chip does much better, the shape of the trade does not change. It is a quality-up, speed-down swap rather than an upgrade, which is what makes it worth measuring in the harness rather than choosing off the table. Two things soften it: the checkpoint ships an MTP head, and <a href="https://github.com/ml-explore/mlx-lm?ref=danmackinlay.name">mlx-dspark</a> drafters exist, so <code>--draft-model</code> is the first thing to try.</p>
<p>The memory is friendlier than the parameter count suggests. The attention is hybrid — 16 full-attention layers out of 64, the other 48 Gated DeltaNet — and only the full-attention layers keep a KV cache, so it costs about 64 KB per token, roughly a quarter of a conventional dense 27B. Long agentic contexts are therefore affordable on a laptop even though the weights are not cheap to run.</p>
<p>Two traps. The card’s non-thinking recipe asks for <code>presence_penalty</code> 1.5, which most servers on this page cannot deliver — <code>mlx_lm.server</code> has no flag for it and the vllm-mlx lineage accepts and discards it — so the non-thinking mode runs as specified only under MTPLX. And it is a <strong>native vision-language model</strong> whose <code>mlx-community</code> 4-bit declares <code>model_type: qwen3_5</code> with a <code>vision_config</code>, which means <code>mlx-lm</code> takes it down the text path and silently drops the vision tower. For a daily driver that wants no vision this is the outcome we want and it keeps us off the multimodal path entirely — but it is the same silent-amputation behaviour that bit Qwen3.6, so it is worth knowing rather than discovering.</p>
<p><a href="https://huggingface.co/prism-ml/Ternary-Bonsai-2-27B-mlx-2bit?ref=danmackinlay.name">Ternary Bonsai 2 27B</a> attacks the decode cost from the other end. It is Prism ML’s 1.76-bit re-quantization of Qwen3.8-27B with the architecture unchanged, 5.8 GB on disk (8.6 GB for the MLX build with its vision tower). The card claims a benchmark average of 84.78, against 85.18 for the 17.6 GB Unsloth Q4_K_XL and 86.32 at FP16, and 47 tok/s on an M5 Max. MTPLX measures its own Bonsai pack at 64.4 tok/s at 4K context, against 52.6 for its 4-bit 27B. Still dense, so still well short of the 3B-active MoEs on speed, but at a third of the Q4’s disk footprint.</p>
</section>
<section id="ssd-resident" class="level4" data-number="8.2.2">
<h4 data-number="8.2.2" class="anchored" data-anchor-id="ssd-resident"><span class="header-section-number">8.2.2</span> Weights that stay on disk</h4>
<p>Two of the late-August releases break the rule of thumb that the disk figure is the one that has to clear the memory ceiling. Qwen3.8-Flash-Next carries a 51B-parameter n-gram embedding, a table of 20 million bigrams and trigrams of which each token reads 16 rows, and DeepSeek V4.1 Flash has the analogous “engram” tables (189 GiB of them in <code>ds4</code>’s Q2 file). A table read a few rows at a time need not be resident, so the runtimes that understand it leave it on the SSD and read rows on demand. MTPLX’s Flash-Next pack is a 115 GB download that peaks at 87 GiB while serving; <code>ds4</code>’s Q2 of the same model is a 137 GiB file with 42 GiB of resident weights. For these models the memory budget is the resident figure plus the KV cache, and the SSD has become part of the memory hierarchy, so the weights belong on the internal drive rather than an external one, and out of backups.</p>
<p>Streaming the routed <em>experts</em> from SSD is the more aggressive version, and it does cost speed. <code>ds4 --ssd-streaming</code> keeps a cache of recently used experts in RAM and reads the rest from the GGUF; on a 128 GB M5 Max <a href="https://github.com/antirez/ds4/blob/main/docs/SSD_STREAMING.md?ref=danmackinlay.name">its docs</a> report GLM-5.3-Flash at Q4 (178 GiB) generating 12–15 tok/s this way. That is the only way DeepSeek V4.1 Flash runs on one 128 GB machine: its Q2 needs streaming there, and antirez <a href="https://x.com/antirez/status/2098121665771110540?ref=danmackinlay.name">was surprised how fast it was</a>, though I have not found a number. <a href="https://huggingface.co/mlx-community/Qwen3.8-Flash-Next-OptiQ-2bit?ref=danmackinlay.name">OptiQ’s 2-bit Flash-Next</a> takes the idea to its limit: 81 GB on disk, 4.6 GB peak memory, 3.8 tok/s on an M3 Max, SSD-bound.</p>
<p>The largest open weights still do not fit in any useful sense. Kimi K3 is 2.8T, and Unsloth’s 1-bit Qwen3.8-2.4T-A95B is 397 GB. <code>ds4</code> will stream full GLM-5.3 at IQ2_XXS (197 GiB) on a 128 GB Mac, at 4–5 tok/s. <a href="https://huggingface.co/tencent/Hy3?ref=danmackinlay.name">Hy3</a> at 299B has a 99 GB <code>oQ2</code> build that technically loads on a 128 GB machine, in the sense that nothing else then does.</p>
</section>
</section>
<section id="models-diffusion" class="level3" data-number="8.3">
<h3 data-number="8.3" class="anchored" data-anchor-id="models-diffusion"><span class="header-section-number">8.3</span> Diffusion models</h3>
<p>Everything above is autoregressive. <a href="../notebook/nn_diffusion_language.html">Diffusion LLMs</a> are a thing though. Do any run locally? I know of one: <a href="https://developers.googleblog.com/diffusiongemma-the-developer-guide?ref=danmackinlay.name">DiffusionGemma</a> — Google’s experimental Gemma 4 variant, which denoises a whole 256-token “canvas” in parallel instead of emitting tokens left to right. <a href="https://huggingface.co/OsaurusAI/diffusiongemma-26B-A4B-it-MXFP8?ref=danmackinlay.name"><code>OsaurusAI/diffusiongemma-26B-A4B-it-MXFP8</code></a> runs natively in Osaurus through its <code>vmlx-swift</code> block-diffusion engine, ~26 GB on disk and ~24 GB resident.</p>
<p>Manage expectations on speed. Docs report 28–42 tok/s at 48 denoising steps: Osaurus defaults to 16, roughly twice as fast as the bundle default and still coherent, and the output falls apart below 12. Quality trails plain Gemma 4 as well. Vision, tool-calling, and a reasoning channel all work in this checkpoint; audio and video do not.</p>
<p>Beyond speed, there is a family-level reason I have not chased Gemma further, and it is why no Gemma appears in the tables above. In my brief tests Gemma models come across as brittle and anxious — quick to hedge, and prone to a sort of performed distress when pushed. Soligo et al.&nbsp;report the same at scale, and locate it in post-training rather than the base weights: instruct-tuned Gemma expresses substantially more distress than its own base model does, whereas instruct-tuned Qwen and OLMo express less <span class="citation" data-cites="Soligo2026Gemma">(Soligo, Mikulik, and Saunders 2026)</span>. They also take high-frustration responses from 35% to 0.3% with DPO on 280 preference pairs at no capability cost, which reads as a shallow trait rather than a baked-in one — just not one fixed in any checkpoint I can download.</p>
<p>So it is not the agentic daily-driver. I am interested in the interaction model though: bi-directional attention over the canvas makes it potentially useful for infilling and structure-preserving rewrite, which is <a href="../notebook/agent_frontends.html#diffusion-ui">a different way to drive a coding tool</a> than streaming tokens into a chat box.</p>
</section>
</section>
<section id="pdfs" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="pdfs"><span class="header-section-number">9</span> Feeding PDFs in</h2>
<p>Having chosen a model, the next question is usually how to get a document into it, which is <a href="../notebook/pdf_ingestion.html">its own can of worms</a> — the local VLM path via <code>mlx-vlm</code> is one of the options there.</p>
</section>
<section id="jang" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="jang"><span class="header-section-number">10</span> The JANG ecosystem</h2>
<p>JANG has come up a dozen times above, always as the reason some model loads in one place and not another, so here is the whole thing in one place.</p>
<p>JANG (“<strong>J</strong>ang <strong>A</strong>daptive <strong>N</strong>-bit <strong>G</strong>rading”) is mixed-precision quantization for MLX, from before MLX’s own config could express it. It classifies tensors by sensitivity — attention and MoE router layers, a small share of the parameters and a large share of the behaviour, get 6–8 bits; expert MLPs get 2–4. The result is a mildly extended version of the standard MLX safetensors format carrying a per-tensor bit-width manifest, so at a given total size accuracy improves, notionally. The pitch is “GGUF for MLX”, which … sounds good? I’m not really competent to judge; <code>llama.cpp</code>’s K-quants apparently do something similar. <a href="https://jangq.ai?ref=danmackinlay.name">jangq.ai</a> claims it regularly beats models with a larger footprint, and <a href="https://medium.com/@alexandru_vasile/i-benchmarked-every-quantization-method-for-apple-silicon-llms-heres-what-actually-wins-7b3e7edff4ef?ref=danmackinlay.name">at least one third-party benchmarker</a> is impressed.</p>
<p>The extension is what makes it an ecosystem rather than a format: because the manifest is non-standard, something has to be taught to read it. Conversion is <a href="https://github.com/jjang-ai/jangq?ref=danmackinlay.name">JANG Studio</a>, a native macOS wizard, with the newer codebook variant branded JANGTQ (“JANG TurboQuant”); the model zoo is the <a href="https://huggingface.co/JANGQ-AI?ref=danmackinlay.name"><code>JANGQ-AI</code></a> Hugging Face org. Osaurus is JANG-native, via <a href="https://github.com/osaurus-ai/vmlx-swift-lm?ref=danmackinlay.name"><code>osaurus-ai/vmlx-swift-lm</code></a>, a Swift port of the Python <a href="https://github.com/jjang-ai/vmlx?ref=danmackinlay.name"><code>jjang-ai/vmlx</code></a>; MLX Studio, vMLX and oMLX also load JANG natively, and LM Studio, Ollama and Jan do not. From Python it is <code>uv pip install "jang[mlx]"</code>, then <code>jang_tools.loader.load_jang_model(...)</code>.</p>
<p>Even inside the family, support is uneven. The Swift engine’s coverage tracks the JANGTQ path, so a plain <code>JANG_*</code> quant of an exotic architecture can still fail at weight-load — notably Cascade-2, which the Python <code>jang-tools</code> stack handles and the Swift engine does not yet.</p>
<p>Why I do not pull it first: the other half of the quality story is the <a href="../notebook/nn_quantization.html#calibration-data">calibration data</a> fed in at quantization time, which is why a <code>bartowski/…-GGUF</code> repo is a different and usually better thing than a bare K-quant of the same weights. I cannot tell whether JANG calibrates at all. OptiQ and oQ both do and both document it, and they get the same per-tensor bit allocation out of stock MLX config, so they load anywhere.</p>
<section id="one-developer-four-products" class="level3" data-number="10.1">
<h3 data-number="10.1" class="anchored" data-anchor-id="one-developer-four-products"><span class="header-section-number">10.1</span> One developer, four products</h3>
<p>The same person wrote every piece above — <a href="https://github.com/jjang-ai?ref=danmackinlay.name">Jinho “Eric” Jang</a> (Irvine, California), who is also Osaurus’s lead and only engineer, and the author of the parallel desktop app MLX Studio. Runtime, quant format, model zoo, two GUIs, one developer. That vertical integration buys fast iteration and a coherent feature set across the chain: new model architectures land in JANG within days of release, faster than the bigger stacks manage, and at the high end of Apple Silicon he is doing things nobody else is doing.</p>
<p>The downside is that if Jang loses interest, switches jobs, or gets hit by a bus, the lot becomes abandonware — including any JANG-format weights we have on disk, which no other converter emits. There is some community wariness about this; see the <a href="https://www.reddit.com/r/LocalLLaMA/comments/1rzuazp/is_mlx_studio_legit_never_heard_of_it_before/?ref=danmackinlay.name">r/LocalLLaMA “Is MLX Studio legit?”</a> thread. It is all open source, so in principle we could maintain it ourselves if he walks away.</p>
</section>
<section id="mlx-studio" class="level3" data-number="10.2">
<h3 data-number="10.2" class="anchored" data-anchor-id="mlx-studio"><span class="header-section-number">10.2</span> MLX Studio</h3>
<p><a href="https://github.com/jjang-ai/mlxstudio?ref=danmackinlay.name">MLX Studio</a> is the JANG/Osaurus author’s other Mac desktop app — Electron + Python rather than Swift, broader feature surface (image generation via Flux and Z-Image, ~26 built-in agentic tools, in-app GGUF→MLX and MLX→JANG conversion, an Anthropic-compatible API). Install via the signed DMG on the releases page, or engine-only with <code>uv tool install vmlx</code> and <code>vmlx serve mlx-community/&lt;repo&gt;</code> (OpenAI-compatible on <code>localhost:8000</code>).</p>
</section>
</section>
<section id="dwarfstar" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="dwarfstar"><span class="header-section-number">11</span> Antirez and DwarfStar</h2>
<p>There is another weird Mac-only stack of interest to me: <a href="https://github.com/antirez?ref=danmackinlay.name">Salvatore Sanfilippo</a> — antirez, the author of Redis — wrote some custom Apple Silicon inference code to run DeepSeek V4 Flash on a 128 GB MacBook, and a whole tiny supergroup of famed developers has grown up around it.</p>
<p>The approximate trajectory is as follows. April 2026: apparently moments after the DeepSeek V4 release, antirez drops <a href="https://github.com/antirez/llama.cpp-deepseek-v4-flash?ref=danmackinlay.name"><code>antirez/llama.cpp-deepseek-v4-flash</code></a>, a fork of <code>llama.cpp</code> with 2-bit quantization, plus the matching GGUF at <a href="https://huggingface.co/antirez/deepseek-v4-gguf?ref=danmackinlay.name"><code>antirez/deepseek-v4-gguf</code></a>.</p>
<p>A month later, he drops a from-scratch native Metal inference engine, <a href="https://github.com/antirez/ds4?ref=danmackinlay.name"><code>ds4</code></a> (<em>DwarfStar 4</em> to its friends), originally targeting DeepSeek V4 Flash alone. By September 2026 the README describes the scope as the best open weights for useful local machine sizes, especially 128 GB laptops: DeepSeek V4 and V4.1 Flash, V4 PRO, GLM-5.2, GLM-5.3 and 5.3-Flash, and Qwen3.8-Flash-Next, each from GGUFs the project builds and tests itself. Models get dropped when something better arrives. It has also grown CUDA and ROCm backends, SSD streaming for models bigger than RAM, and tensor parallelism across two Macs over RDMA. Reported numbers are pretty snappy — the recorded V4 Flash Q2 baseline on a 128 GB M5 Max decodes 39 tok/s at 2K context and 28 tok/s at 64K, and prefills at 400–800 tok/s (<a href="https://github.com/antirez/ds4/blob/main/docs/PERFORMANCE.md?ref=danmackinlay.name">performance notes</a>).</p>
<p>Like JANG, this is a small, specialized stack run by one person — except that this one has an influential community around it.</p>
<p><code>ds4</code> is not the only door to this model, though the recipes below read as though it were. DeepSeek V4 Flash has MLX builds: <a href="https://huggingface.co/mlx-community/DeepSeek-V4-Flash-2bit-DQ?ref=danmackinlay.name"><code>mlx-community/DeepSeek-V4-Flash-2bit-DQ</code></a> is 97 GB on disk, and there is an <a href="https://huggingface.co/OsaurusAI/DeepSeek-V4-Flash-JANGTQ2?ref=danmackinlay.name"><code>OsaurusAI</code> JANGTQ2</a> for the Osaurus/oMLX path. Either one is a registry entry in vllm-mlx rather than a from-source build of a bespoke engine. <code>ds4</code> is interesting because it tests specialized hand-written Metal against a general MLX runtime. Tests TBD.</p>
<section id="pi" class="level3" data-number="11.1">
<h3 data-number="11.1" class="anchored" data-anchor-id="pi"><span class="header-section-number">11.1</span> Running DwarfStar via the <code>pi</code> stack</h3>
<p>The default harness for <code>ds4</code> seems to be: <a href="https://github.com/earendil-works/pi?ref=danmackinlay.name"><code>pi</code></a>, an MIT-licensed agent harness by Mario Zechner (badlogic, of <a href="https://libgdx.com?ref=danmackinlay.name">libGDX</a> fame) — itself a <a href="../notebook/automatic_coding.html#offline-coding">strong offline coding agent</a> once a model is behind it. There is an easy install via the pi extension by Armin Ronacher (mitsuhiko, of <a href="https://flask.palletsprojects.com?ref=danmackinlay.name">Flask</a>): <a href="https://github.com/mitsuhiko/pi-ds4?ref=danmackinlay.name"><code>mitsuhiko/pi-ds4</code></a>. It handles process management for <code>ds4-server</code> — per-PID leases, watchdog shutdown, OpenAI-compatible local endpoint on <code>127.0.0.1:8000</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb35-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pi</span> install https://github.com/mitsuhiko/pi-ds4</span></code></pre></div></div>
<p>A first-time install clones <code>antirez/ds4</code>, builds it, downloads the GGUF (~87 GB), and registers a <code>ds4/deepseek-v4-flash</code> model with pi. Subsequent runs spawn the server on demand and shut it down when no client process holds a lease. OpenClaw embeds pi, so the same extension can in principle load there.</p>
<p>Running <code>pi</code> from the terminal opens a TUI (a “textual user interface” — I believe that’s what it means, as it lives in the terminal).</p>
<p><a href="https://en.wikipedia.org/wiki/Audrey_Tang?ref=danmackinlay.name">Audrey Tang</a> maintains <a href="https://github.com/audreyt/pi-ds4?ref=danmackinlay.name"><code>audreyt/pi-ds4</code></a>, a fork that swaps in <a href="https://huggingface.co/cyberneurova?ref=danmackinlay.name">cyberneurova</a>’s abliterated IQ2XXS quants and turns on uncertainty-mode directional steering by default — an activation-space edit that puts the model into “this is a contested question” mode on CCP-sensitive topics (Taiwan, Crimea, Kashmir, Western Sahara).</p>
</section>
<section id="nonpi-recipe" class="level3" data-number="11.2">
<h3 data-number="11.2" class="anchored" data-anchor-id="nonpi-recipe"><span class="header-section-number">11.2</span> Manual setup for non-pi harnesses</h3>
<p>Outside the <code>pi</code> ecosystem, the manual setup requires four commands plus a config edit.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode numberSource fish number-lines code-with-copy"><code class="sourceCode"><span id="cb36-1"># antirez/ds4 for upstream; also audreyt/ds4 looks cool</span>
<span id="cb36-2"># optimizations + steering-vector work — pick one</span>
<span id="cb36-3">git clone https://github.com/audreyt/ds4</span>
<span id="cb36-4">cd ds4</span>
<span id="cb36-5">make</span>
<span id="cb36-6">tmutil addexclusion -p (realpath ./gguf)</span>
<span id="cb36-7">./download_model.sh                     # ~87 GB into ./gguf/</span>
<span id="cb36-8">./ds4-server                            # listens on 127.0.0.1:8000</span></code></pre></div></div>
<p>For lifecycle, we could wrap <code>./ds4-server</code> in a <code>launchd</code> plist with <code>KeepAlive: true</code>; this probably isn’t what we want on a typical laptop, where we do other things besides inference — like, you know, use it as a laptop. I think <code>pi</code> is more automatic in that regard.</p>
<p><code>ds4-server</code>’s context window is set at launch via <code>--ctx &lt;tokens&gt;</code> (max accepted per conversation); output length is a per-request API field, not a launch flag. <code>--kv-disk-dir &lt;path&gt;</code> (with <code>--kv-disk-space-mb &lt;n&gt;</code>) persists the KV cache to disk, so a prefix survives restarts and session switches rather than being reprocessed — durable prefix storage, not a long-context spill. Thinking mode is on by default, toggled per request, running DeepSeek’s reasoning mode. DeepSeek V4 Flash nominally supports 1M tokens, but <code>ds4</code> is RAM-bound: the 2-bit IQ2XXS weights are ~81 GB, and a full 1M-token KV/index sits around 26 GB on top. Rough budget on unified memory:</p>
<ul>
<li>64 GB: 50k–150k <code>--ctx</code> with headroom.</li>
<li>96 GB: 150k–250k works but is tight; quit Slack.</li>
<li>128 GB: 200k–300k is comfortable; &gt;300k starts risking OOM.</li>
<li>1M: only with very generous memory and nothing else running.</li>
</ul>
<p>If a client (Hermes, OpenClaw, OpenCode, anything OpenAI-compatible) advertises a context larger than <code>--ctx</code>, requests will get cut off — match the client’s <code>contextWindow</code> / <code>limit.context</code> to the server’s <code>--ctx</code>. DeepSeek’s sparse attention means raising <code>--ctx</code> doesn’t blow up compute the way dense attention would, but RAM is still a constraint. For most interactive coding, 32k–100k plus a retrieval layer beats brute-forcing the whole history into the prompt. See <a href="https://github.com/antirez/ds4/blob/main/README.md?ref=danmackinlay.name"><code>antirez/ds4</code>’s README</a> and the <a href="https://docs.openclaw.ai/providers/ds4?ref=danmackinlay.name">OpenClaw <code>ds4</code> provider docs</a> for the full flag list and client-side config.</p>
<p>Reasonable defaults:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb37-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">./ds4-server</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ctx</span> 200000 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--kv-disk-dir</span> /tmp/ds4-kv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--kv-disk-space-mb</span> 8192</span></code></pre></div></div>
<p>audreyt’s fork uses environment variables for directional steering rather than flags, so it composes with what we mentioned above:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb38-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">DS4_DIR_STEERING_FFN</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>-0.75 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb38-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">DS4_DIR_STEERING_ATTN</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>0 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb38-3"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">DS4_REPRODUCIBLE</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>1 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb38-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">./ds4-server</span></span></code></pre></div></div>
<p>To use with Hermes, add an OpenAI-compatible provider entry to the Hermes config (sketch — confirm the exact schema with <code>hermes config</code>):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode numberSource yaml number-lines code-with-copy"><code class="sourceCode yaml"><span id="cb39-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ~/.hermes/config.yaml</span></span>
<span id="cb39-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">custom_providers</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb39-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ds4</span></span>
<span id="cb39-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">base_url</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> http://127.0.0.1:8000/v1</span></span>
<span id="cb39-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">model</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> deepseek-v4-flash</span></span>
<span id="cb39-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">models</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb39-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">deepseek-v4-flash</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb39-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">context_length</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200000</span></span></code></pre></div></div>
<p>From inside <a href="../notebook/ai_agents_applied.html">Hermes</a>, <code>/model ds4/deepseek-v4-flash</code> — matching the provider <code>name</code> in the YAML. Done.</p>
<p>Anyway, this provides a generic token endpoint, so we’re free to plug in whatever we like on the front end.</p>
<p>The protagonists of this play have a lot of clout — antirez (Redis), mitsuhiko (Flask), badlogic (libGDX), and audreyt (Taiwan’s former Digital Minister, Pugs / Perl 6). Some kind of critical mass seems feasible for a certain type of nerd.</p>
</section>
</section>
<section id="tokenizers" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="tokenizers"><span class="header-section-number">12</span> Tokenizers are trouble</h2>
<p>The layer diagram is a useful fiction, and the place it leaks first is the tokenizer. A tokenizer is not part of the weights; it is a separate file that ships alongside them, and every runtime implements its own reader for it. So two stacks can load the same model and disagree about what the text <em>is</em> — quietly, with a 200 OK on the way out. I have hit two of those myself.</p>
<section id="tokenizer-postprocessor" class="level3" data-number="12.1">
<h3 data-number="12.1" class="anchored" data-anchor-id="tokenizer-postprocessor"><span class="header-section-number">12.1</span> When a model can’t spell a space</h3>
<p>Text from <code>mlx-community/DeepSeek-R1-0528-Qwen3-8B-4bit</code> came back like this:</p>
<pre><code>DidĠyouĠmeanĠ'chubs'ĠasĠinĠtheĠtongueĠtwister?</code></pre>
<p><code>Ġ</code> (U+0120) and <code>Ċ</code> (U+010A) are how byte-level BPE spells a space and a newline <em>in the vocabulary</em>, so seeing them raw means the round-trip wasn’t in fact round. The fault seems to be at <em>encode</em> time, where <code>"hello wide world"</code> tokenizes to <code>['h', 'ellow', 'id', 'eworld']</code>, meaning spaces disappear before BPE runs. The DeepSeek-R1-Distill-Qwen tokenizer omits the <code>ByteLevel</code> <code>post_processor</code> that closes the loop — which worked with <code>tokenizers&lt;0.22.2</code> — so that whole generation of Qwen distills shares that problem with its upstream progenitor checkpoint.</p>
<p>That lineage is old enough by now that I won’t bother fixing this particular bug to reactivate it. What I will do is remember this failure mode, because it sneaks up. The model loads, serves, streams, reports 200 OK, and answers on-topic; it is only that small matter of being unreadable. I had a small preloaded utility model sitting wasting RAM for weeks unnoticed, just casually gaslighting the other LLMs that invoked it. <a href="../notebook/ai_agents_applied.html#open-webui">Open WebUI</a> finally surfaced it. So: eyeball the output of anything we leave running, especially the small utility models nobody looks at.</p>
</section>
<section id="tokenizer-divergence" class="level3" data-number="12.2">
<h3 data-number="12.2" class="anchored" data-anchor-id="tokenizer-divergence"><span class="header-section-number">12.2</span> Two stacks, two tokenizers, one model</h3>
<p>I came to understand the <code>transformers</code>/<code>llama.cpp</code> split by breaking it.</p>
<p>The Hugging Face and Ollama versions of <code>mxbai-embed-large</code> are nominally the same model — same upstream weights — but each stack implemented its own tokenizer. On plain prose the two mostly agree, I think; on markdown they can disagree by a few percent on how many tokens a chunk takes. That is survivable for chat, where tokenization stays internal to one stack and we can see the output, but it is not survivable for embeddings, where a few percent of drift results in a silently different vector and nothing downstream can tell. So for embeddings on this blog I went all-<code>transformers</code>, and for chat through a live server Ollama is fine.</p>
</section>
</section>
<section id="exclude-models" class="level2" data-number="13">
<h2 data-number="13" class="anchored" data-anchor-id="exclude-models"><span class="header-section-number">13</span> Excluding model dirs from backups and indexing</h2>
<p>The model weights are enormous and waste space in backups. There’s no point</p>
<ul>
<li>backing up a quantized <code>.gguf</code> we can pull again in two commands, nor</li>
<li>indexing <code>.safetensors</code> files for Spotlight — they are opaque binary blobs and Spotlight will spin happily for hours grinding nothing useful out of them.</li>
</ul>
<p>oMLX’s SSD KV cache belongs on the list too — same opaque-blob logic, but it <em>churns</em>: blocks are written and evicted every session, so leaving it in Time Machine means gigabytes get re-snapshotted on every hourly pass rather than just once. Exclude <code>~/.omlx/cache</code> specifically, not all of <code>~/.omlx</code>, so the small <code>settings.json</code> next to it stays backed up.</p>
<p>Solution!</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb41" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb41-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># One list, two background services to opt out of</span></span>
<span id="cb41-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">model_dirs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb41-3">  ~/.cache/huggingface                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># transformers, sentence-transformers, and mlx-lm/mlx_lm.server all cache here</span></span>
<span id="cb41-4">  ~/.cache/modelscope                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ModelScope cache (Alibaba’s HF; override: MODELSCOPE_CACHE)</span></span>
<span id="cb41-5">  ~/.cache/uv</span>
<span id="cb41-6">  ~/.ollama/models</span>
<span id="cb41-7">  ~/.lmstudio</span>
<span id="cb41-8">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$HOME</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/Library/Application Support/Jan/data/llamacpp/models"</span></span>
<span id="cb41-9">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$HOME</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/Library/Application Support/Jan/data/mlx/models"</span></span>
<span id="cb41-10">  ~/MLXModels                          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># shared MLX served-models tree: Osaurus default (OSU_MODELS_DIR) + oMLX --model-dir</span></span>
<span id="cb41-11">  ~/.mlxstudio/models                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># MLX Studio default</span></span>
<span id="cb41-12">  ~/.omlx/cache                        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># oMLX SSD KV cache — regenerable + high-churn; exclude this, not all of ~/.omlx</span></span>
<span id="cb41-13">  ~/.cache/vllm-mlx                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># vllm-mlx --ssd-cache-dir, same churn story</span></span>
<span id="cb41-14">  ~/.mtplx/session-bank                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># MTPLX SSD session (KV) cache, same again; not all of ~/.mtplx</span></span>
<span id="cb41-15">  ~/.mtplx/models                      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># MTPLX pull/app downloads, if anything ever lands there</span></span>
<span id="cb41-16"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb41-17"></span>
<span id="cb41-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Time Machine — sticky exclusion keyed to the path string</span></span>
<span id="cb41-19"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${model_dirs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[@]</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">;</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="cb41-20">  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">[</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">-d</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb41-21"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">done</span></span>
<span id="cb41-22"></span>
<span id="cb41-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Spotlight — drop the Apple-documented marker file in each directory</span></span>
<span id="cb41-24"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${model_dirs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[@]</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">;</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="cb41-25">  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">[</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">-d</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">touch</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/.metadata_never_index"</span></span>
<span id="cb41-26"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">done</span></span>
<span id="cb41-27"></span>
<span id="cb41-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Confirm a few</span></span>
<span id="cb41-29"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">tmutil</span> isexcluded ~/.cache/huggingface</span>
<span id="cb41-30"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-la</span> ~/MLXModels/.metadata_never_index</span></code></pre></div></div>
<p><code>.metadata_never_index</code> is the <a href="https://developer.apple.com/library/archive/documentation/Carbon/Conceptual/MDImporters/Concepts/Troubleshooting.html?ref=danmackinlay.name">Apple-supported marker file</a> that tells <code>mds_stores</code> to skip the directory and everything under it; the file is empty and the marker is the filename.</p>
<p>If we ever want to re-index a directory (a model dir promoted to “actual content”), we delete <code>rm .metadata_never_index</code>, and <code>mdimport -r &lt;dir&gt;</code> puts it back.</p>
</section>
<section id="gotchas" class="level2" data-number="14">
<h2 data-number="14" class="anchored" data-anchor-id="gotchas"><span class="header-section-number">14</span> Gotchas</h2>
<p>Everything on this page is really a property of one specific point release, collected here so it can rot in one place. We provide symptoms verbatim, since that is what we search for at 2am.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Symptom</th>
<th>Stack</th>
<th>Cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>token soup or a <code>sound_encoder…subsampling</code> shape error through the multimodal path, while <code>mlx_lm.generate</code> on the same checkpoint is fine</td>
<td><code>mlx-vlm</code> 0.6.4–0.6.8</td>
<td>weight sanitization runs a second time over an already-converted checkpoint</td>
<td>upgrade to 0.6.9+</td>
</tr>
<tr class="even">
<td><code>Received N parameters not in model</code> at weight-load</td>
<td>vllm-mlx</td>
<td>multimodality is guessed from the repo name (<code>VL</code>, <code>vision</code>, <code>llava</code>); <code>-Omni-</code> slips through</td>
<td><code>mllm: true</code> on that registry entry</td>
</tr>
<tr class="odd">
<td><code>Received N parameters not in model</code> on a Nemotron omni build that should work</td>
<td>vllm-mlx + Osaurus repackaging</td>
<td>Osaurus hides the omni descriptor in a side-file that <code>mlx-vlm</code> never reads</td>
<td>take the <code>mlx-community</code> build</td>
</tr>
<tr class="even">
<td><code>top_k</code> and <code>presence_penalty</code> accepted, logged, then silently ignored</td>
<td>vllm-mlx 0.4.1, text path</td>
<td>neither reaches <code>make_sampler</code>; the multimodal path does honour <code>top_k</code> (detail)</td>
<td>none; stop transcribing them from model cards</td>
</tr>
<tr class="odd">
<td>model keeps looping though the penalty is set in the UI</td>
<td>Open WebUI → vllm-mlx</td>
<td>the panel spells it <code>repeat_penalty</code> (Ollama), but the server reads <code>repetition_penalty</code></td>
<td><strong>Add Custom Parameter</strong> → <code>repetition_penalty</code></td>
</tr>
<tr class="even">
<td>hard OOM crash instead of a graceful eviction</td>
<td>vllm-mlx registry</td>
<td><code>memory_budget_gb</code> counts weights only and ignores the process ceiling (<a href="https://github.com/waybarrios/vllm-mlx/issues/627?ref=danmackinlay.name">#627</a>; 0.4.1 warns at startup but still does not clamp)</td>
<td>budget ≤ ceiling − KV − headroom</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?k"> identical samples at temp 1.0, so <a href="../notebook/automatic_maths.html#fan-out">maj@<img src="https://latex.codecogs.com/png.latex?k"></a> votes on one answer <img src="https://latex.codecogs.com/png.latex?k"> times</td>
<td><code>mlx_lm.server</code></td>
<td>deterministic per (prompt, params); <code>seed</code> is process-global and disables batching</td>
<td>vary a system preamble per sample</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?k"> identical samples at temp 1.0, as above</td>
<td>vllm-mlx, requests issued <em>sequentially</em></td>
<td>each fresh request replays the same PRNG state; concurrent requests share a batch and do diverge</td>
<td>issue the fan-out concurrently, which is the shape it wants anyway</td>
</tr>
<tr class="odd">
<td><code>profile name is required</code> when the name is right there</td>
<td>llama-swap</td>
<td>the JSON key is <code>name</code>, not <code>profile</code> or <code>id</code></td>
<td><code>-d '{"name":"ornith"}'</code></td>
</tr>
<tr class="even">
<td><code>reasoning_content: null</code> and raw chain-of-thought in <code>content</code></td>
<td>any reasoning parser</td>
<td>the response hit <code>max_tokens</code> before the closing <code>&lt;/think&gt;</code>, so there was no block to split</td>
<td>raise the output budget, not the parser</td>
</tr>
<tr class="odd">
<td><code>The model X does not exist. Available models: …</code></td>
<td><a href="../notebook/ai_agents_applied.html#goose">Goose</a> → vllm-mlx</td>
<td>Goose keeps its own model list and never reads <code>/v1/models</code></td>
<td>rename in both places</td>
</tr>
<tr class="even">
<td>output littered with <code>Ġ</code> and <code>Ċ</code></td>
<td>DeepSeek-R1-Distill-Qwen family</td>
<td>tokenizer omits the <code>ByteLevel</code> <code>post_processor</code></td>
<td>none; different model</td>
</tr>
<tr class="odd">
<td><code>osaurus: command not found</code></td>
<td>Osaurus, non-Homebrew install</td>
<td>the CLI lives inside the app bundle</td>
<td>symlink <code>Contents/Helpers/osaurus</code></td>
</tr>
<tr class="even">
<td>embeddings come back 128-dimensional whatever model was asked for</td>
<td>Osaurus <code>/v1/embeddings</code></td>
<td>the <code>model</code> field is ignored; everything is served by <code>potion-base-4M</code></td>
<td>don’t embed via Osaurus; chat is unaffected</td>
</tr>
<tr class="odd">
<td>Ollama KV-cache quant appears to do nothing</td>
<td>Ollama</td>
<td><code>OLLAMA_KV_CACHE_TYPE</code> needs flash attention alongside it</td>
<td>also set <code>OLLAMA_FLASH_ATTENTION=1</code></td>
</tr>
</tbody>
</table>
</section>
<section id="incoming" class="level2" data-number="15">
<h2 data-number="15" class="anchored" data-anchor-id="incoming"><span class="header-section-number">15</span> Incoming</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=a0GesZXLwfc&amp;ref=danmackinlay.name">antirez’s “DeepSeek-V4-Flash on a MacBook M5 Max”</a> — the demo video for the DwarfStar section.</li>
<li>Vicki Boykis — <a href="https://newsletter.vickiboykis.com/archive/running-local-models-is-good-now/?ref=danmackinlay.name">Running local models is good now</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="16">
<h2 data-number="16" class="anchored" data-anchor-id="references"><span class="header-section-number">16</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Gloeckle2024Better" class="csl-entry">
Gloeckle, Idrissi, Rozière, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2404.19737">Better &amp; Faster Large Language Models via Multi-Token Prediction</a>.”</span>
</div>
<div id="ref-Lin2025GoedelProverV2" class="csl-entry">
Lin, Tang, Lyu, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2508.03613">Goedel-Prover-V2: Scaling Formal Theorem Proving with Scaffolded Data Synthesis and Self-Correction</a>.”</span>
</div>
<div id="ref-Soligo2026Gemma" class="csl-entry">
Soligo, Mikulik, and Saunders. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2603.10011">Gemma Needs Help: Investigating and Mitigating Emotional Instability in LLMs</a>.”</span>
</div>
<div id="ref-Xu2026VibeThinker3B" class="csl-entry">
Xu, Liu, Wang, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2606.16140">VibeThinker-3B: Exploring the Frontier of Verifiable Reasoning in Small Language Models</a>.”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>MoE does not help here — it cuts the weights read per token, which buys decode speed, but not cache. Every expert still uses RAM.↩︎</p></li>
<li id="fn2"><p>On <code>llama-server</code>, <code>--cache-type-k q8_0 --cache-type-v q8_0 -fa</code> roughly halves it; on Ollama, <code>OLLAMA_FLASH_ATTENTION=1 OLLAMA_KV_CACHE_TYPE=q8_0</code>. <code>mlx_lm.server</code> will not cap context itself, so the cap goes in the harness via <code>limit.context</code> / <code>contextWindow</code>.↩︎</p></li>
<li id="fn3"><p><a href="https://docs.osaurus.ai/cli?ref=danmackinlay.name">The documentation claims</a> it is <code>ln -sf "/Applications/Osaurus.app/Contents/MacOS/osaurus" "$(brew --prefix)/bin/osaurus"</code>; but I think this is a typo — that launches the app, not the CLI helper.↩︎</p></li>
<li id="fn4"><p>That template is also where <code>OLLAMA_FLASH_ATTENTION=1</code> and <code>OLLAMA_KV_CACHE_TYPE=q8_0</code> come from — a load-bearing pairing.↩︎</p></li>
<li id="fn5"><p>The noise is from <code>llama-server</code>, the model server Ollama runs on our behalf, and Ollama does not expose its log level.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>computers are awful</category>
  <category>DIY</category>
  <category>edge computing</category>
  <category>faster pussycat</category>
  <category>machine learning</category>
  <category>neural nets</category>
  <category>NLP</category>
  <category>UI</category>
  <guid>https://danmackinlay.name/notebook/local_llm_mac.html</guid>
  <pubDate>Sat, 23 May 2026 05:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/small_power_RP-P-1896-A-19368-144.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>PDF ingestion</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/pdf_ingestion.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-imperial-library" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-imperial-library-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/imperial_library_RP-P-1896-A-19368-286.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/imperial_library_RP-P-1896-A-19368-286.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-imperial-library-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>I want my <a href="../notebook/local_llm_mac.html">local language models</a> to read my <a href="../notebook/pdfs.html">PDFs</a>, analyse them, answer questions about them, and so on.</p>
<p>There are two ways to get there.</p>
<ol type="1">
<li>We can let a vision-capable model look at the rendered page as an image — direct, no conversion.</li>
<li>We can turn the PDF into text first and feed it that.</li>
</ol>
<p>For cloud pipelines this doesn’t matter; it is all magically taken care of. For local models I tend to be more stingy with my VRAM and I may want to use a pure LLM for my reasoning rather than waste bytes on a multimodal variant.</p>
<p>In that case it may be fiddlier than I’d like. In particular, since I care about getting the mathematics out of the papers I read, I want a high-fidelity converter, and not all converters are that.</p>
<p>This is the <a href="../notebook/latex.html#reverse-latex">Reverse LaTeX</a> problem at scale: not rendering an equation but recovering it from the render.</p>
<section id="decision-tree" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="decision-tree"><span class="header-section-number">1</span> Decision tree</h2>
<p>Does the agent (or the model) already handle the PDF well enough? If so, let the agent do it. If not, do we need the text just once (explain this!), or do we want to keep it (copy-paste required, knowledge base building…)?</p>
<p>To read the PDF once, interactively, let a vision-capable model read the page — fluent, but not for maths to be quoted verbatim.</p>
<p>To keep the text — for a batch, a long document, a RAG index — we convert it.</p>
</section>
<section id="when-the-agent-already-handles-it" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="when-the-agent-already-handles-it"><span class="header-section-number">2</span> When the agent already handles it</h2>
<p>The easiest thing to do is nothing. Where the agent harness (or the model itself) handles the PDF well enough, this seems fine. Several frontends accept drag-and-dropped PDFs and convert them into something the model can read (markdown or more structured formats), and that does well enough that we stop here. See below for (my best understanding of) the toolchain in each case.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Frontend</th>
<th>Native input</th>
<th>How</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="../notebook/local_llm_mac.html#lm-studio">LM Studio</a></td>
<td>✅</td>
<td>Drag-drop <code>.pdf</code> / <code>.docx</code> / <code>.txt</code>; <a href="https://lmstudio.ai/docs/app/basics/rag?ref=danmackinlay.name">short docs go fully into context, long ones trigger auto-RAG</a></td>
</tr>
<tr class="even">
<td><a href="../notebook/local_llm_mac.html#jan">Jan</a></td>
<td>✅ + tool-calling</td>
<td>File attachments <em>plus</em> MCP tool-calling can hand the PDF off to whichever extractor we wire up</td>
</tr>
<tr class="odd">
<td><a href="../notebook/local_llm_mac.html#osaurus">Osaurus</a></td>
<td>✅ text-layer</td>
<td>Attachments route through a <a href="https://github.com/osaurus-ai/osaurus?ref=danmackinlay.name#tools--plugins">document adapter registry</a> (PDF, XLSX, PPTX, CSV). Scans still need the <a href="https://github.com/osaurus-ai/osaurus-vision?ref=danmackinlay.name">osaurus-vision</a> plugin or an external converter via a tool/skill.</td>
</tr>
<tr class="even">
<td><a href="../notebook/ai_agents_applied.html#qwen-agent">Qwen-Agent</a></td>
<td>✅ + RAG</td>
<td>Python harness; <code>Assistant(files=[…])</code> runs a built-in <a href="https://qwenlm.github.io/Qwen-Agent/en/guide/core_moduls/rag?ref=danmackinlay.name">RAG pipeline</a> (DocParser + BM25, no vector DB) over PDF / Office / HTML</td>
</tr>
<tr class="odd">
<td><a href="../notebook/local_llm_mac.html#mlx-studio">MLX Studio</a></td>
<td>❌</td>
<td>26+ agentic tools available; invoke a converter via shell.</td>
</tr>
<tr class="even">
<td><a href="../notebook/local_llm_mac.html#pi">DwarfStar / pi</a></td>
<td>⚠️ partial</td>
<td>pi’s web UI extracts <a href="https://github.com/earendil-works/pi/issues/204?ref=danmackinlay.name">badly</a>. A community <code>pdf-reader</code> skill is the current workaround.</td>
</tr>
</tbody>
</table>
<p>If that covers the job, we are done. If not — or if we would rather the model saw the page than its extracted text — read on.</p>
</section>
<section id="reading-the-page-directly" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="reading-the-page-directly"><span class="header-section-number">3</span> Reading the page directly</h2>
<p>If the agent’s model is vision-capable, the simplest pipeline needs no converter at all. We render the page — or just screenshot it — and pass the image to the model. It sees the two-column layout, the figure, the table, the equations as we do, and for many tasks that is enough: <em>what does this paper say</em>, what is in this plot, summarize section 3. <a href="../notebook/local_llm_mac.html#models-agentic">Qwen3.6</a> reads screenshots happily, for example.</p>
<p>For reference, <a href="https://github.com/earendil-works/pi/issues/204?ref=danmackinlay.name#issuecomment-3666179951">what Claude does</a> as a multimodal model is a hybrid:</p>
<ol type="1">
<li>extract the text with a run-of-the-mill PDF-to-text extractor (something like markitdown),</li>
<li><em>also</em> render each page of the PDF as an image, and</li>
<li>slap both into the context, page by page.</li>
</ol>
<p>This approach has two advantages over conversion. It drops nothing — a chart or a hand-drawn diagram that text extraction would discard is right there in the image — and it needs no setup (assuming our system can render PDFs).</p>
<p>I’m not 100% sure I trust it though; if we never see fiddly stuff like maths and tables as <em>characters</em>, how do we know we got it right? It is a strong option for reading and comprehension but a weak one as a source of record.</p>
<p>Moreover, even when this works we might still want <em>conversion</em> of longer documents, since the page images are token-heavy and burn through context fast. Also, since the transcription lives in the chat and dies with it, anything we want to grep, diff, re-embed, or index has to be converted and kept.</p>
</section>
<section id="pdf-pick" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="pdf-pick"><span class="header-section-number">4</span> The converters</h2>
<p>So, suppose we want a greppable, reusable text artefact made from a PDF and friendly to an LLM. The right one depends on what is in the PDF: markitdown for born-digital prose, marker or MinerU for maths, Docling for tables and structure, an OCR engine for scans. Here are the trade-offs:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Converter</th>
<th>Ingest speed</th>
<th>Download</th>
<th>Maths fidelity</th>
<th>RAM</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>markitdown</td>
<td>instant</td>
<td>none</td>
<td>text only</td>
<td>negligible</td>
</tr>
<tr class="even">
<td>Docling — standard</td>
<td>slow¹</td>
<td>small</td>
<td>structure ✓, maths weak¹</td>
<td>sub-GB</td>
</tr>
<tr class="odd">
<td>Docling — <code>--pipeline vlm</code></td>
<td>fast (~6 s/pg)</td>
<td>258 M</td>
<td><strong>silently corrupts</strong></td>
<td>sub-GB</td>
</tr>
<tr class="even">
<td>marker</td>
<td>1–8 min²</td>
<td>moderate</td>
<td>clean LaTeX ✓</td>
<td>3–5 GB</td>
</tr>
<tr class="odd">
<td>MinerU</td>
<td>batch³</td>
<td>large</td>
<td>clean LaTeX ✓</td>
<td>16–32 GB</td>
</tr>
</tbody>
</table>
<p><em>¹ Docling’s accurate maths needs <code>--enrich-formula</code> — ~46 min for a 24-page paper, and it adds its own operator-grouping errors. ² ~1 min when marker reuses a text layer; ~8 min when it must OCR. ³ ~4-min predictor load per session, then under a minute a doc — only worth it across a batch.</em></p>
<p>I ran the contenders over two equation-dense papers from recent ICLR, checking each conversion against the PDF. Notably, converters that use a single vision-language model to transcribe the whole page silently corrupt the maths. Docling’s Granite VLM emits plausible LaTeX, but it turned 6 of 25 <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cfrac%7B1%7D%7B%5Cdelta%7D"> terms into <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cfrac%7B1%7D%7B8%7D"> or <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cfrac%7B1%7D%7B2%7D"> — valid LaTeX, wrong maths, the most dangerous kind of error. marker and MinerU route the page through specialist recognisers instead of one VLM, and came through clean — every one of 26 <img src="https://latex.codecogs.com/png.latex?%5Cdelta"> intact, <code>\underbrace</code> annotations and all. I did not put a general vision model reading the page directly through the same test, but it is the same whole-page operation, so I wouldn’t be amazed to see it corrupt the maths too (citation needed).</p>
<section id="markitdown" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="markitdown"><span class="header-section-number">4.1</span> markitdown</h3>
<p>Text-layer extraction only: fast, lightweight, no OCR, no layout, nothing else to install. Most PDFs from modern journals or word processors have a text layer (i.e.&nbsp;we can copy-paste text out of them), and <code>markitdown</code> extracts that text into a plain file:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uvx</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--from</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'markitdown[pdf]'</span> markitdown some.pdf</span></code></pre></div></div>
<p>Given a scan without a text layer it produces nothing. Even with a text layer, it does badly at maths and tables, and it discards figures and diagrams.</p>
</section>
<section id="docling" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="docling"><span class="header-section-number">4.2</span> Docling</h3>
<p>Docling has two pipelines which are totally different in terms of cost and quality.</p>
<p>The <em>VLM</em> pipeline — <code>docling --pipeline vlm --vlm-model granite_docling &lt;file&gt;</code> — runs one end-to-end document model, <a href="https://www.ibm.com/new/announcements/granite-docling-end-to-end-document-conversion?ref=danmackinlay.name">Granite-Docling-258M</a>. It is fast and tiny: <a href="https://docling-project.github.io/docling/usage/vision_models/?ref=danmackinlay.name">Docling’s benchmark</a> clocks 6.2 s/page on MLX (against 102 s on Transformers, M3 Max), and at 258M params it runs alongside anything we want. However if “90% accurate” is not good enough for the equations, beware, because it gets some of those wrong.</p>
<p>The <em>standard</em> pipeline (the default) is an ensemble of layout, table, and OCR models. Add <code>--enrich-formula</code> and it hands off equation regions to IBM’s specialist CodeFormula model, which fixes most of the corruption — but it is uselessly slow (~46 min for a 24-page paper, CPU-bound) and adds its own structural slips, like mis-grouping operators. So neither Docling path is the one for hi-fi maths; that is marker or MinerU.</p>
<p>Setup is <code>uv tool install docling</code>; the first run downloads the checkpoint from Hugging Face, and <a href="https://huggingface.co/ibm-granite/granite-docling-258M-mlx?ref=danmackinlay.name">on Apple Silicon it picks the MLX build automatically</a>.</p>
<p>Docling emits <em>DocTags</em>, a lossless structural markup; <code>--to</code> exports <code>md</code>, <code>json</code>, <code>yaml</code>, <code>html</code>, <code>html_split_page</code>, <code>text</code>, and <code>doctags</code> — the JSON/YAML is the full DoclingDocument (layout tree, bounding boxes, reading order, structured table cells, exportable to dataframes), and <code>--to html_split_page --show-layout</code> renders a page-by-page QA view. It converts in silence and writes <code>&lt;stem&gt;.md</code> to the <em>current directory</em> (<code>--output &lt;dir&gt;</code> to redirect); <code>-v</code> shows per-stage progress, and the HF_TOKEN / <code>use_fast</code> warnings are noise. For LLM use pass <code>--image-export-mode placeholder</code> (or <code>referenced</code>), otherwise it inlines every figure as a base64 data URL that torches the token budget.</p>
</section>
<section id="marker" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="marker"><span class="header-section-number">4.3</span> marker</h3>
<p>marker is a self-contained bundle of small, specialist <code>surya</code> models — layout, OCR, maths-to-LaTeX, tables. It is not a single vision-language model transcribing the whole page, the way Docling and MinerU are; its specialist models ship with it and run automatically.</p>
<p>Install with <code>uv tool install marker-pdf</code> (or one-off via <code>uvx --from marker-pdf marker_single ./doc.pdf</code>). The first run pulls the <code>surya</code> weights into the Hugging Face cache automatically, so there is no separate weights step.</p>
<p><code>marker_single ./doc.pdf</code> came through the maths test corruption-free, and it loads fast enough to be a competitive choice for a one-off conversion.</p>
<p>It uses <a href="https://github.com/datalab-to/marker?ref=danmackinlay.name">MPS accelerator</a> and wants ~3–5 GB per worker, which I would call light on RAM for a local converter. Conversion speed is variable: quick on a born-digital text layer and slow when it has to OCR the page.</p>
<p>marker does have a <code>--use_llm</code> refinement pass for cross-page table merging, inline maths, and form fields. This might be useful? However for me it was working fine with the vanilla <code>surya</code> pipeline already, so it isn’t necessary. I am not sure it is worth the pain for local setups: <code>--use_llm</code> calls Gemini by default, so keeping it local means standing up a second LLM backend or using the agent’s own endpoint and threading its port and model name through marker’s flags. Once a second model has to be resident anyway, the monolithic MinerU does the high-fidelity job in one process with none of the plumbing — so locally I skip <code>--use_llm</code>, and switch to MinerU on the rare occasion I want hardcore cleanup.</p>
</section>
<section id="mineru" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="mineru"><span class="header-section-number">4.4</span> MinerU</h3>
<p>A hefty hi-fi tool. <code>uv tool install 'mineru[all]'</code>. On Apple Silicon the 3.3 <code>hybrid-engine</code> backend auto-selects the MLX VLM engine. Budget the one-off ~4-minute predictor load per session and 16–32 GB of RAM; after that it is fast, converting the 24-page test paper in under a minute. That memory-and-load cost makes sense if we batch convert — for a single document it is wasted. Formulas come out with good LaTeX parsing including niceties like <code>\tag{…}</code> equation numbers, tables as HTML, and OCR in a notional 109 languages. It ships its own MCP server, which is helpful for the agent wiring below if we can afford to keep it running in the background.</p>
</section>
<section id="ocr-engines" class="level3" data-number="4.5">
<h3 data-number="4.5" class="anchored" data-anchor-id="ocr-engines"><span class="header-section-number">4.5</span> Pure OCR</h3>
<p>For scans, photos, and handwriting — there is no text layer to extract, so the job is pure OCR. <a href="https://huggingface.co/mlx-community/DeepSeek-OCR-8bit?ref=danmackinlay.name">DeepSeek-OCR</a> is the fast pick, with an 8-bit community port that runs under <a href="../notebook/local_llm_mac.html#mlx-vlm"><code>mlx-vlm</code></a> (<code>uv pip install mlx-vlm</code>). Note that it is <a href="https://github.com/Blaizzy/mlx-vlm/issues/549?ref=danmackinlay.name">highly strung</a> — it goes haywire if the prompt is even slightly wrong. <a href="https://github.com/datalab-to/chandra?ref=danmackinlay.name">Chandra 2</a> is marker’s big sibling: a 4B OCR VLM and benchmark leader on scans, handwriting, forms, and 40+ languages — and the heaviest option here. Neither went through the maths test so this is the makers’ positioning, not my own measurement.</p>
</section>
</section>
<section id="agents" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="agents"><span class="header-section-number">5</span> <code>pdf-ingest</code> skill for agents</h2>
<p>When the reader is an <em>agent</em> rather than me, the routing above has to be packaged in such a way that the agent can load it. In the <a href="../notebook/ai_agents_applied.html#skills">agentskills</a> model, we can set up a skill that dynamically loads the detailed body on demand. Thus, we can put the routing policy in the root skill while each engine’s flags and gotchas can be in per-engine files the skill loads on demand:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource text number-lines code-with-copy"><code class="sourceCode"><span id="cb2-1">.claude/skills/pdf-ingest/</span>
<span id="cb2-2">  SKILL.md          # trigger + routing policy + budget disciplines</span>
<span id="cb2-3">  engines/</span>
<span id="cb2-4">    markitdown.md   # instant text-layer extraction</span>
<span id="cb2-5">    docling.md      # Granite VLM, MLX, placeholder images</span>
<span id="cb2-6">    marker.md       # maths→LaTeX, MPS, --use_llm wiring</span>
<span id="cb2-7">    mineru.md       # the accuracy-maximalist escalation</span></code></pre></div></div>
<p>A converter run from the skill may want an LLM of its own — marker’s <code>--use_llm</code>, say. The agent is already an LLM on a local endpoint, so it’s cheapest to point the converter at that same endpoint rather than load a second model. That looks unsafe — won’t it deadlock, or need the model loaded twice? It works anyway: during tool execution the harness’s model is idle, because the agent loop is generate → dispatch tool → wait. While the agent blocks on the converter, the converter has the endpoint to itself.</p>
<p>That directory is published at <a href="https://github.com/danmackinlay/pdf-ingest-skill?ref=danmackinlay.name">danmackinlay/pdf-ingest-skill</a>; its README has the per-harness install steps — Claude Code, Hermes, pi, Osaurus, and Jan.</p>


</section>

 ]]></description>
  <category>computers are awful</category>
  <category>machine learning</category>
  <category>neural nets</category>
  <category>NLP</category>
  <category>UI</category>
  <guid>https://danmackinlay.name/notebook/pdf_ingestion.html</guid>
  <pubDate>Sat, 23 May 2026 05:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/imperial_library_RP-P-1896-A-19368-286.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Peer review meets AI</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/science_peer_review_ai.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-bruegel-alchemist" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-bruegel-alchemist-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/bruegel_alchemist.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/bruegel_alchemist.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-bruegel-alchemist-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>What if our <a href="../notebook/science_peer_review.html">scientific peer review systems</a> were already creaking under strain, and then someone introduced generative AI into the mix? That would be a wild ride.</p>
<p>cf <a href="../notebook/science_ai_agents.html">AI Science Agents</a>.</p>
<p>The back-and-forth between authors and reviewers, especially during rebuttals, can be productive and helpful in principle, but it is inconsistent in practice. Meta-reviewers can struggle to adjudicate complex technical disputes quickly.</p>
<p>A recent proposal suggests adding a bounded-round arbitration step using AI adjudicators <span class="citation" data-cites="AllenZhu2025DOGE">(Allen-Zhu and Xu 2025)</span>. The idea is that arbitration (analyzing an existing dispute) may be cognitively easier than full reviewing (generating a novel critique), and that today’s language models may already be competent enough (L1 or L2 competence) in some domains. See the CF debate protocols and other scalable-oversight-style <a href="../notebook/ai_aligning.html">alignment</a> ideas.</p>
<p>In such a protocol, after authors submit a structured rebuttal to reviewer comments, an AI arbitrator (using a standardized prompt and fixed turns) evaluates specific contested points and issues a finding with a confidence score and citations to the text. Human meta-reviewers then consider this finding alongside the original human reviews. Key design features include bounded rounds of interaction, giving authors the last word (to offset the advantages reviewers get from anonymity), and logging all arbitrator chats for auditability <span class="citation" data-cites="AllenZhu2025DOGE">(Allen-Zhu and Xu 2025)</span>.</p>
<p>Potential benefits include increased consistency, immediate logic checks, and a searchable audit trail. A neutral arbitrator may also lower the social costs (“face costs”), making it easier for reviewers and authors to admit errors. However, challenges include model variance, domain gaps (e.g., in mathematics-heavy areas), and the need for careful governance.</p>
<p>See also <a href="../notebook/science_ai_agents.html">AI agents in science</a> for more hands-on ideas about AI roles in parts of the research workflow other than review.</p>
<p>There are many variants on the theme of AI-assisted review, including AI-generated initial reviews, AI-summarized discussions, and AI-flagged statistical checks.</p>
<div id="fig-doge-review" class="figure illustration foreign right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-doge-review-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/AllenZhu_AI_review.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: DOGE for AI [@AllenZhu2025DOGE]"><img src="https://danmackinlay.name/images/AllenZhu_AI_review.png" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-doge-review-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: <a href="https://doge.allen-zhu.com/?ref=danmackinlay.name">DOGE for AI</a> <span class="citation" data-cites="AllenZhu2025DOGE">(Allen-Zhu and Xu 2025)</span>
</figcaption>
</figure>
</div>
<section id="incoming" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="incoming"><span class="header-section-number">1</span> Incoming</h2>
<ul>
<li><p><a href="https://www.shishyko.com/supercritical/coordination-tech-in-science?ref=danmackinlay.name">Coordination Tech in Science: Letters, Journals, and Whatever Comes Next | shishyko!</a></p></li>
<li><p><a href="https://www.refine.ink/?ref=danmackinlay.name">Refine.ink</a></p></li>
<li><p><a href="https://github.com/SakanaAI/AI-Scientist?ref=danmackinlay.name">SakanaAI/AI-Scientist: The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery 🧑‍🔬</a> / <a href="https://sakana.ai/ai-scientist/?ref=danmackinlay.name">The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery</a> <span class="citation" data-cites="Lu2024AI">(Lu et al. 2024)</span></p>
<p>They say this is about <a href="../notebook/science_generation.html">scientific research generation</a>, but I think it’s also about peer review, as that ends up being part of their workflow.</p></li>
<li><p><a href="https://amistrongeryet.substack.com/p/the-black-spatula-project?ref=danmackinlay.name">The Black Spatula Project — Steve Newman</a></p>
<blockquote class="blockquote">
<p>A 10 page paper caused a panic because of a math error. I was curious if AI would spot the error by just prompting: “carefully check the math in this paper” especially as the info is not in training data.</p>
<p>o1 gets it in a single shot. Should AI checks be standard in science?</p>
</blockquote></li>
</ul>
<p>Repository: <a href="https://github.com/nick-gibb/black-spatula-project?ref=danmackinlay.name">nick-gibb/black-spatula-project: Verifying scientific papers using LLMs</a></p>
<ul>
<li><a href="https://paperreview.ai/?ref=danmackinlay.name">/paperreview.ai— Stanford Agentic Reviewer</a></li>
<li><a href="https://www.roastmypost.org/?ref=danmackinlay.name">roastmypost.org</a></li>
</ul>
<p>Also, there are many articles on this<span class="citation" data-cites="Couto2024RelevAIReviewer Kim2025Position Kuznetsov2024What Liang2024Can Liu2023ReviewerGPT Lu2024AI Ye2024Are">(Couto et al. 2024; Kim, Lee, and Lee 2025; Kuznetsov et al. 2024; Liang et al. 2024; Liu and Shah 2023; Lu et al. 2024; Ye et al. 2024)</span>.</p>
</section>
<section id="references" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="references"><span class="header-section-number">2</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-AllenZhu2025DOGE" class="csl-entry">
Allen-Zhu, and Xu. 2025. <span>“<a href="https://doi.org/10.2139/ssrn.5127931">DOGE: Reforming AI Conferences and Towards a Future Civilization of Fairness and Justice</a>.”</span> SSRN Scholarly Paper.
</div>
<div id="ref-Biswas2026AIAssisted" class="csl-entry">
Biswas, Schoepp, Vasan, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2604.13940">AI-Assisted Peer Review at Scale: The AAAI-26 AI Review Pilot</a>.”</span>
</div>
<div id="ref-Couto2024RelevAIReviewer" class="csl-entry">
Couto, Ho, Kumari, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2406.10294">RelevAI-Reviewer: A Benchmark on AI Reviewers for Survey Paper Relevance</a>.”</span>
</div>
<div id="ref-Hardt2026Emerging" class="csl-entry">
Hardt. 2026. <em><a href="https://mlbenchmarks.org">The Emerging Science of Machine Learning Benchmarks</a></em>.
</div>
<div id="ref-Kim2025Position" class="csl-entry">
Kim, Lee, and Lee. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2505.04966">Position: The AI Conference Peer Review Crisis Demands Author Feedback and Reviewer Rewards</a>.”</span>
</div>
<div id="ref-Kuznetsov2024What" class="csl-entry">
Kuznetsov, Afzal, Dercksen, et al. 2024. <span>“<a href="http://arxiv.org/abs/2405.06563">What Can Natural Language Processing Do for Peer Review?</a>”</span> In <em>CoRR</em>.
</div>
<div id="ref-Liang2024Can" class="csl-entry">
Liang, Zhang, Cao, et al. 2024. <span>“<a href="https://doi.org/10.1056/AIoa2400196">Can Large Language Models Provide Useful Feedback on Research Papers? A Large-Scale Empirical Analysis</a>.”</span> <em>NEJM AI</em>.
</div>
<div id="ref-Liu2023ReviewerGPT" class="csl-entry">
Liu, and Shah. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2306.00622">ReviewerGPT? An Exploratory Study on Using Large Language Models for Paper Reviewing</a>.”</span>
</div>
<div id="ref-Lu2024AI" class="csl-entry">
Lu, Lu, Lange, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2408.06292">The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery</a>.”</span>
</div>
<div id="ref-Xiao2014Incentive" class="csl-entry">
Xiao, Dörfler, and Schaar. 2014. <span>“<a href="https://doi.org/10.48550/arXiv.1411.2139">Incentive Design in Peer Review: Rating and Repeated Endogenous Matching</a>.”</span>
</div>
<div id="ref-Ye2024Are" class="csl-entry">
Ye, Pang, Chai, et al. 2024. <span>“<a href="http://arxiv.org/abs/2412.01708">Are We There yet? Revealing the Risks of Utilizing Large Language Models in Scholarly Peer Review</a>.”</span> In <em>CoRR</em>.
</div>
</div>


</section>

 ]]></description>
  <category>academe</category>
  <category>agents</category>
  <category>collective knowledge</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>game theory</category>
  <category>how do science</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>mind</category>
  <category>networks</category>
  <category>performative prediction problems</category>
  <category>provenance</category>
  <category>sociology</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/science_peer_review_ai.html</guid>
  <pubDate>Sat, 16 May 2026 23:03:33 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/bruegel_alchemist.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Decision theory in mechanized causal graphs</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/causality_mechanization_decisions.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p><a href="../notebook/causality_agency.html">Causality, agency, decisions, learning</a> is the motivation for this notebook. There we hand-waved through mechanized causal graphs.</p>
<p>Here I intend to go deep into the formalism introduced in <span class="citation" data-cites="Everitt2021Agent">Everitt et al. (2021)</span> and <span class="citation" data-cites="Everitt2022Understanding">Everitt et al. (2022)</span>. I have not done so yet to my satisfaction — this is a WIP.</p>
<p>The point of the notation, compared to <a href="../notebook/graphical_models_directed.html">classical DAGs</a>, is that mechanism nodes are first-class, so we can talk about edges <em>between mechanisms</em> — e.g.&nbsp;an agent reasoning about how its objective depends on the world’s mechanism, or a predictor reading the agent’s policy.</p>
<p>By the end of the notebook I hope to be able to use <span class="citation" data-cites="MacDermott2023Characterising">MacDermott, Everitt, and Belardinelli (2023)</span> to illuminate <a href="../notebook/decision_theory_cosmic.html">cosmic decision theories</a>, and think about <a href="../notebook/agency_bounded_compute.html">agency under bounded compute</a> in a more mechanistic way.</p>
<p>In much the same way that I think standard DAGs have a role in <a href="../notebook/graphical_models_ml_tour.html">uncovering some hidden assumptions in ML</a>, I think there are some hidden assumptions in cosmic decision theories that mechanized causal graphs will help us uncover. Eventually. Let us see.</p>
<p>Later, we may even be able to use <span class="citation" data-cites="Kenton2023Discovering">Kenton et al. (2023)</span> or <span class="citation" data-cites="MacDermott2024Measuring">MacDermott et al. (2024)</span> to discover intentionality in the world.</p>
<section id="mechanizing-causal-graphs" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="mechanizing-causal-graphs"><span class="header-section-number">1</span> Mechanizing causal graphs</h2>
<div id="fig-horse-mechanism" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-horse-mechanism-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/horse_mechanism_RP-T-1953-1-4-12.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/horse_mechanism_RP-T-1953-1-4-12.avif" id="fig-horse-mechanism" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-horse-mechanism-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>A <em>mechanized causal graph</em> augments a causal DAG with a <em>mechanism variable</em> (also called a <em>policy</em>) for some object-level nodes — encoding how that node’s value is generated from its parents by some kind of learned (“agenty”) mechanism <span class="citation" data-cites="Kenton2023Discovering">(Kenton et al. 2023)</span>.</p>
<p>Layered on top is the influence-diagram convention from <span class="citation" data-cites="Everitt2021Agent">(Everitt et al. 2021)</span>: rectangles for decisions, diamonds for utilities, ovals/circles for everything else.</p>
<p>What are we doing when we claim that some nodes are special “mechanism” nodes? AFAICT formalising the intentional stance.</p>
<p><a href="https://causalincentives.com/cid-latex/CausalInfluenceDiagramLatexTutorial.html?ref=danmackinlay.name">LaTeX tutorial</a> (actually a <a href="../notebook/tikz.html">TiKz</a> tutorial.)</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Symbol</th>
<th>Style</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>◯</td>
<td><code>obj</code></td>
<td>object-level chance variable</td>
</tr>
<tr class="even">
<td>●</td>
<td><code>mech</code></td>
<td>mechanism variable</td>
</tr>
<tr class="odd">
<td>▭</td>
<td><code>dec</code></td>
<td>decision</td>
</tr>
<tr class="even">
<td>◇</td>
<td><code>util</code></td>
<td>utility</td>
</tr>
<tr class="odd">
<td><code>→</code> solid</td>
<td><code>causal</code></td>
<td>causal edge between object-level variables, or functional edge from mechanism to its variable</td>
</tr>
<tr class="even">
<td><code>→</code> dashed</td>
<td><code>mech-edge</code></td>
<td>non-terminal mechanism dependency</td>
</tr>
<tr class="odd">
<td><code>→</code> dashdotted</td>
<td><code>objective</code></td>
<td>terminal mechanism edge (an agent’s objective)</td>
</tr>
</tbody>
</table>
</section>
<section id="object-level-dag" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="object-level-dag"><span class="header-section-number">2</span> Object-level DAG</h2>
<p>The starting point — a vanilla causal DAG with no mechanism layer.</p>
<img src="https://danmackinlay.name/notebook/causality_mechanization_decisions_files/mediabag/mech-objlevel-dag.svg" class="img-fluid">
<p>Each object variable acquires a mechanism node above it. Writing the edge from <img src="https://latex.codecogs.com/png.latex?M_X"> to <img src="https://latex.codecogs.com/png.latex?X"> dashed is notation rather than a new kind of relation. We imagine it represents a lifted structural equation : where the object-level SEM has <img src="https://latex.codecogs.com/png.latex?X%20:=%20f_X(%5Ctext%7Bpa%7D(X),%20U_X)">, the mechanized version is <img src="https://latex.codecogs.com/png.latex?X%20:=%20%5Ctilde%20f_X(M_X,%5C,%20%5Ctext%7Bpa%7D(X),%5C,%20U_X),"> with <img src="https://latex.codecogs.com/png.latex?M_X"> parameterising a function space and <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Beval%7D"> the universal “apply this function to these arguments” operator. Under this lifting <img src="https://latex.codecogs.com/png.latex?M_X%20%5Cto%20X"> is an ordinary causal edge — intervening on <img src="https://latex.codecogs.com/png.latex?M_X"> (swapping the policy) has the standard interventional semantics</p>
<img src="https://danmackinlay.name/notebook/causality_mechanization_decisions_files/mediabag/mech-mechanized-dag.svg" class="img-fluid">
</section>
<section id="cosmic-decision-theories" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="cosmic-decision-theories"><span class="header-section-number">3</span> Cosmic decision theories</h2>
<p>The mechanized view earns its keep on problems where the <em>agent’s policy</em> is itself an input to some other variable. In Newcomb’s problem the predictor <img src="https://latex.codecogs.com/png.latex?P"> forecasts the agent’s decision <img src="https://latex.codecogs.com/png.latex?D"> — but <img src="https://latex.codecogs.com/png.latex?P"> is causally upstream of <img src="https://latex.codecogs.com/png.latex?D">, so the dependence cannot be captured by an object-level edge from <img src="https://latex.codecogs.com/png.latex?D"> to <img src="https://latex.codecogs.com/png.latex?P">. The mechanized graph routes it through <img src="https://latex.codecogs.com/png.latex?M_D">, the agent’s <em>decision mechanism</em> (its policy): <img src="https://latex.codecogs.com/png.latex?P"> depends on <img src="https://latex.codecogs.com/png.latex?M_D">, and <img src="https://latex.codecogs.com/png.latex?M_D"> functionally determines <img src="https://latex.codecogs.com/png.latex?D">. The dashdotted edge is the literature’s <code>objective</code>/terminal-mechanism edge style — used loosely here to flag the “back door” through which the predictor accesses the agent.</p>
<img src="https://danmackinlay.name/notebook/causality_mechanization_decisions_files/mediabag/mech-newcomb.svg" class="img-fluid">
<p>The intended reading: prediction <img src="https://latex.codecogs.com/png.latex?P"> determines box contents <img src="https://latex.codecogs.com/png.latex?V">; the agent’s mechanism <img src="https://latex.codecogs.com/png.latex?M_D"> implements a policy that produces a decision <img src="https://latex.codecogs.com/png.latex?D">; utility <img src="https://latex.codecogs.com/png.latex?U"> depends on both the contents and the choice; and the “Newcomb-ness” lives in the dash-dotted edge <img src="https://latex.codecogs.com/png.latex?M_D%20%5Cto%20P"> — the predictor sees the policy, not the act.</p>
</section>
<section id="references" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="references"><span class="header-section-number">4</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Dawid2002Influence" class="csl-entry">
Dawid. 2002. <span>“<a href="https://doi.org/10.1111/j.1751-5823.2002.tb00354.x">Influence Diagrams for Causal Modelling and Inference</a>.”</span> <em>International Statistical Review</em>.
</div>
<div id="ref-Everitt2021Agent" class="csl-entry">
Everitt, Carey, Langlois, et al. 2021. <span>“<a href="https://doi.org/10.1609/aaai.v35i13.17368">Agent Incentives: A Causal Perspective</a>.”</span> In <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Everitt2022Understanding" class="csl-entry">
Everitt, Ortega, Barnes, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.1902.09980">Understanding Agent Incentives Using Causal Influence Diagrams. Part I: Single Action Settings</a>.”</span>
</div>
<div id="ref-Fox2021PyCID" class="csl-entry">
Fox, Everitt, Carey, et al. 2021. <span>“<a href="https://doi.org/10.25080/majora-1b6fd038-008">PyCID: A Python Library for Causal Influence Diagrams</a>.”</span> In.
</div>
<div id="ref-Fox2023Imperfect" class="csl-entry">
Fox, MacDermott, Hammond, et al. 2023. <span>“<a href="https://doi.org/10.4204/EPTCS.379.17">On Imperfect Recall in Multi-Agent Influence Diagrams</a>.”</span> <em>Electronic Proceedings in Theoretical Computer Science</em>.
</div>
<div id="ref-Geiger2024Causal" class="csl-entry">
Geiger, Ibeling, Zur, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2301.04709">Causal Abstraction: A Theoretical Foundation for Mechanistic Interpretability</a>.”</span>
</div>
<div id="ref-Hammond2023Reasoning" class="csl-entry">
Hammond, Fox, Everitt, et al. 2023. <span>“<a href="https://doi.org/10.1016/j.artint.2023.103919">Reasoning about Causality in Games</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Kenton2023Discovering" class="csl-entry">
Kenton, Kumar, Farquhar, et al. 2023. <span>“<a href="https://doi.org/10.1016/j.artint.2023.103963">Discovering Agents</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Koller2003Multiagent" class="csl-entry">
Koller, and Milch. 2003. <span>“<a href="https://doi.org/10.1016/S0899-8256(02)00544-4">Multi-Agent Influence Diagrams for Representing and Solving Games</a>.”</span> <em>Games and Economic Behavior</em>, First World Congress of the Game Theory Society,.
</div>
<div id="ref-MacDermott2023Characterising" class="csl-entry">
MacDermott, Everitt, and Belardinelli. 2023. <span>“<a href="http://arxiv.org/abs/2307.10987">Characterising Decision Theories with Mechanised Causal Graphs</a>.”</span>
</div>
<div id="ref-MacDermott2024Measuring" class="csl-entry">
MacDermott, Fox, Belardinelli, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2412.04758">Measuring Goal-Directedness</a>.”</span>
</div>
<div id="ref-Ward2024Reasons" class="csl-entry">
Ward, MacDermott, Belardinelli, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2402.07221">The Reasons That Agents Act: Intention and Instrumental Goals</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>adaptive</category>
  <category>agents</category>
  <category>causality</category>
  <category>cooperation</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>graphical models</category>
  <category>incentive mechanisms</category>
  <category>learning</category>
  <category>mind</category>
  <category>networks</category>
  <category>utility</category>
  <guid>https://danmackinlay.name/notebook/causality_mechanization_decisions.html</guid>
  <pubDate>Tue, 05 May 2026 03:52:15 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/horse_mechanism_RP-T-1953-1-4-12.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Intergenerational game theory</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/game_theory_intergenerational.html</link>
  <description><![CDATA[ 



<div class="content-warning">
    <h3 class="notification-title anchored">Content warning:</h3>
    <p>AI Slop awaiting human comprehension and review.</p>
</div>

<div class="quarto-title-meta">

    </div>





<div id="fig-skeletal-2x2" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-skeletal-2x2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/skeletal_2x2_RP-P-1896-A-19368-193.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/skeletal_2x2_RP-P-1896-A-19368-193.avif" id="fig-skeletal-2x2" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-skeletal-2x2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Longtermists wonder what we should give the people of the remote future because of their intrinsic worth. This notebook is not about that. <em>This</em> is about what the future can extract from the present, with what little bargaining leverage it can muster.</p>
<p>Most game theory assumes the players exist at the same time and can interact: observe each other’s moves, retaliate against defections, form coalitions, bargain over outcomes. Intergenerational games break all of this, in favour of the actually-existing.</p>
<p>Future generations cannot retaliate against past ones; they do not exist when the moves are made in the intergenerational game. They cannot voluntarily enter coalitions; they have no preferences yet to voice. They cannot reject offers; the bargain arrives as a <em>fait accompli</em>.</p>
<p>As such, the standard equilibrium prediction of self-interested rational play promises nothing good for the unborn. Indeed, it delivers expropriation: environmental depletion, sovereign debt, exhausted commons, accumulated tail risk. These are not coordination failures <em>between</em> present-day parties; they are first-mover advantages against people who cannot yet object <span class="citation" data-cites="Kotlikoff1993Inefficiency">(Kotlikoff and Rosenthal 1993)</span>.</p>
<p>This is the <em>inter-personal</em> counterpart of <a href="../notebook/intertemporal_decisions.html">intertemporal decisions</a>, where I treat the <em>intra-personal</em> version <span class="citation" data-cites="Strotz1955Myopia Pollak1968Consistent McClennen1990Rationality">(Strotz 1955; Pollak 1968; McClennen 1990)</span>. This seems to me to have important structural commonalities — present moves bind future agents who could not participate — but here the future agents are entirely different people, not longitudinal selves in the same individual. Maybe these cash out into the same problem under <a href="../notebook/multi_scale_agency.html">multi-level agency</a>?</p>
<section id="oh-damn-iterated-game-theory-does-not-save-us" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="oh-damn-iterated-game-theory-does-not-save-us"><span class="header-section-number">1</span> Oh damn, iterated game theory does not save us</h2>
<p>There is a stylized insight in <a href="../notebook/game_theory_iterated.html">iterated games</a> that tells us that populations can attain a prosperous <em>tit-for-tat</em> equilibrium, i.e.&nbsp;mutual cooperation sustained by the threat of retaliation in repeated play. This depends on retaliation being available and credible, which it is not, against the arrow of time. The shadow of the future, in Axelrod’s phrase, has to fall on a player who exists. When the players never meet, the shadow falls on no one.</p>
<p>The same goes for the bargaining-set / class-emergence dynamics from <span class="citation" data-cites="Axtell2001Emergence">Axtell, Epstein, and Young (2001)</span>’s multi-agent bargaining model: those require iterated negotiation between the two parties to the bargain. With future generations, only one party is present in any given negotiation.</p>
</section>
<section id="overlapping-generations" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="overlapping-generations"><span class="header-section-number">2</span> Overlapping generations</h2>
<p>The standard formal device for getting <em>some</em> purchase on the problem is the <em>Overlapping Generations</em> (OLG) model <span class="citation" data-cites="Samuelson1958Exact Diamond1965National">(Samuelson 1958; Diamond 1965)</span>. The basic insight is that generations are not point-events but cohorts with finite lifespans, and at any moment several cohorts coexist. A grandparent, parent, and child can interact directly. They have overlapping interests with neighbouring cohorts, who have overlapping interests with <em>theirs</em> in turn. This gives a transitive chain of reciprocal interactions linking the present to the distant future, without requiring any single agent to bargain with someone unborn.</p>
<p>OLG is enough to recover <em>some</em> intergenerational cooperation under the right preference structures — altruism toward children, social-norm enforcement, public-pension institutions, family-firm continuity. But it does not give a fairness theorem for distant generations. The cooperation chain attenuates with each link: future-future-future agents are weighted only as much as their immediate ancestors cared about their children’s children’s children, which is generally not very much.</p>
</section>
<section id="confucius-saw-this-one-coming" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="confucius-saw-this-one-coming"><span class="header-section-number">3</span> Confucius saw this one coming</h2>
<p><a href="https://en.wikipedia.org/wiki/Confucianism?ref=danmackinlay.name">Confucianism</a> seems to have a strong emphasis on filial piety, which prescribes respect and care for one’s parents and ancestors, and by extension, a moral framework for considering the welfare of future generations.</p>
<p>Does it work? Is the problem here a me problem, and appropriately confucian successors would be able to solve this better than filially impious li’l me?</p>
<p>🚧TODO🚧 From here onwards this notebook is slop research notes for a break out session that you were probably not a participant in. Stop reading if you value your eyeballs.</p>
</section>
<section id="rescuing-games-between-cohorts" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="rescuing-games-between-cohorts"><span class="header-section-number">4</span> Rescuing games between cohorts</h2>
<p><span class="citation" data-cites="Phelps1968Second">Phelps and Pollak (1968)</span> turn the OLG setup into an explicit non-cooperative game with one move per cohort. A cohort inherits a stock of capital, chooses how much of it to consume and how much to leave behind, and then exits. Its payoff is its own consumption plus some weight on the welfare of its descendants — a weight strictly less than one, since concern for people we will never meet is imperfect. What makes this a game rather than a plan is that a cohort cannot dictate what its heirs do with the bequest. The heirs face the same problem in turn, against <em>their</em> heirs.</p>
<p>So the solution concept is subgame-perfect equilibrium: every cohort’s saving rate is a best response to the saving rates its successors will in fact choose, given that those are themselves best responses further down the line. The resulting path is generally not the one a planner would pick if it could dictate the whole sequence of saving rates at once, and it can miss by a lot. No cohort does anything irrational, each responds correctly to the situation it is handed, and the sequence still comes out worse for everybody than a path all of them would have preferred.</p>
<p>Formally this is the same object as one person’s saving plan under <a href="../notebook/intertemporal_decisions.html">non-exponential discounting</a> <span class="citation" data-cites="Strotz1955Myopia Pollak1968Consistent McClennen1990Rationality">(Strotz 1955; Pollak 1968; McClennen 1990)</span>: substitute “the self at time <img src="https://latex.codecogs.com/png.latex?t">” for “cohort <img src="https://latex.codecogs.com/png.latex?t">” and the equations do not change. The history runs the opposite way to how it is usually told, though. The <img src="https://latex.codecogs.com/png.latex?%5Cbeta">–<img src="https://latex.codecogs.com/png.latex?%5Cdelta"> quasi-hyperbolic discount function that Laibson later made a staple of behavioural economics is Phelps and Pollak’s, and it started life as a model of generations, not of selves.</p>
<p>McClennen’s taxonomy of <em>naïve</em>, <em>sophisticated</em> and <em>resolute</em> choosers carries over to cohorts, two of the three intact.</p>
<p>A <em>naïve</em> cohort saves as though its descendants will stick to the plan it has in mind for them. They will not, because they inherit the same temptation and re-optimize when their turn comes. Unlike the naïve self, who is repeatedly surprised, the naïve cohort never finds out; it is dead before the evidence arrives.</p>
<p>A <em>sophisticated</em> cohort works out what its successors will actually do and best-responds to that, which is the subgame-perfect path above. Sophistication buys accuracy rather than a better outcome. Correctly anticipating that our heirs will consume most of what we leave them is a reason to leave less, not more.</p>
<p>A <em>resolute</em> chooser, in McClennen’s sense, adopts a plan and then carries it out, treating its successive selves as one agent rather than as opponents to be out-guessed; no external commitment device is needed, because the resoluteness is internal. That is the one with nowhere to live here. Resoluteness requires an agent persisting from the moment a plan is adopted to the moment it must be honoured. Generational turnover is the absence of any such agent. The cohort that adopts the plan is gone before it comes due, and the cohort that has to honour it never agreed to anything.</p>
<p>Intergenerational resoluteness therefore has to be manufactured, by stipulation or by institution — a constitution, a sovereign wealth fund, a central-bank mandate, a treaty with a long ratchet — each of which is an attempt to build a super-agent that outlives every cohort taking part in it. This is the same move as the <a href="../notebook/commitment.html">commitment</a> devices of the intra-personal case, except that here it is not optional, there being no continuous agent to fall back on. It relocates the problem rather than dissolving it: whether the manufactured super-agent stays resolute is a question about <em>its</em> incentives, which returns us to <a href="../notebook/multi_scale_agency.html">multi-level agency</a>.</p>
<p>Which mathematical object we land in depends on the shape of the altruism. Non-exponential weighting of descendants — the <img src="https://latex.codecogs.com/png.latex?%5Cbeta">–<img src="https://latex.codecogs.com/png.latex?%5Cdelta"> form, a discrete drop between us and them followed by gentler discounting thereafter — turns the Ramsey growth problem from a single optimization into a Markov game whose players are cohorts. Exponential weighting does not. Under the dynastic altruism of <span class="citation" data-cites="Barro1974Are">Barro (1974)</span>, where a parent weights the child’s whole utility by a constant factor and the child does the same for the grandchild, the weights compose exponentially down the line; every cohort then ranks future consumption streams identically, and the game collapses back to a single representative-agent planner solving the ordinary Ramsey problem. That collapse is the formal version of “don’t die” below. A dynasty that discounts consistently is, as far as the model can tell, one immortal agent.</p>
</section>
<section id="axiomatic-intergenerational-equity" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="axiomatic-intergenerational-equity"><span class="header-section-number">5</span> Axiomatic intergenerational equity</h2>
<p>Most of the <em>formal</em> work on intergenerational fairness is not game theory; it is axiomatic social choice over infinite streams of well-being. Each generation gets a well-being level <img src="https://latex.codecogs.com/png.latex?x_t">, and the question is which orderings of infinite streams <img src="https://latex.codecogs.com/png.latex?(x_1,%20x_2,%20%5Cdots)"> are consistent with two minimal-seeming axioms:</p>
<ul>
<li><strong>Strong Pareto</strong> (sensitivity): if one generation is strictly better off and none worse, prefer the new stream.</li>
<li><strong>Finite Anonymity</strong> (equal treatment): permuting finitely many generations leaves the ranking unchanged.</li>
</ul>
<p>There are various impossibility results — Basu–Mitra <span class="citation" data-cites="Basu2003Aggregating">(Basu and Mitra 2003)</span>, Lauwers, Zame, and others — blocking us from jointly satisfying these axioms <span class="citation" data-cites="Asheim2010Intergenerational">(Asheim 2010)</span>. There is no numerically representable social welfare function that satisfies both (Diamond–Basu–Mitra), and if we drop numerical representability and just ask for an <em>explicitly definable</em> complete ordering, that fails too (Lauwers–Zame). One of the axioms has to weaken or go.</p>
<p>The literature has taken two paths through this.</p>
<p><em>Keep both axioms, give up completeness.</em> Incomplete orderings like utilitarian or leximin “overtaking” criteria can compare some pairs of infinite streams but not all. Some intergenerational dilemmas come out unrankable, which is awkward for policy advice but at least flagged rather than fudged.</p>
<p><em>Keep completeness, give up equal treatment.</em> Discounted utilitarianism — the standard policy-economics workhorse — applies a discount factor <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20%3C%201"> each period, weighting future generations less. <span class="citation" data-cites="Chichilnisky1996Axiomatic">Chichilnisky (1996)</span> names this a <em>Dictatorship of the Present</em>, since beyond some finite time the future stops mattering for the ranking. The Stern–Nordhaus debate over climate policy is largely a debate over how much of this dictatorship to accept.</p>
<p>A third path tries to weaken the axioms in the right direction rather than throwing one out. Asheim’s <em>Hammond Equity for the Future</em> (HEF) is one such weakening. Standard Hammond equity says any transfer from a better-off generation to a worse-off one improves the social ranking, provided the ordering is preserved. HEF restricts this to one specific case — when the <em>present</em> is better off than an infinite number of <em>future</em> generations, and a present sacrifice would benefit all of them while keeping the present better off than the future. My understanding of the intuition here is that a small present sacrifice is worth it if it benefits an infinite number of unambiguously-worse-off future cohorts, while we should not demand maximal sacrifice from a barely-worse-off present to benefit a barely-worse-off future. Asheim, Mitra, and Tungodden show that HEF combined with weakened Pareto and continuity axioms gives a class of <em>sustainable recursive social welfare functions</em>, orderings that allow present sacrifice when the future is worse off but not when the future is already better off <span class="citation" data-cites="Asheim2010Sustainable">(Asheim, Mitra, and Tungodden 2012)</span>.</p>
<p>In productive technological environments — Asheim, Buchholz, and Tungodden’s “immediate productivity” condition — Strong Pareto and Finite Anonymity together imply that only non-decreasing well-being streams are undominated <span class="citation" data-cites="Asheim2001Justifying">(Asheim, Buchholz, and Tungodden 2001)</span>. So sustainability is what the minimal-equity axioms imply when the technology can in fact produce things, not an additional normative add-on but a <em>consequence</em> of equality + sensitivity once productivity is in the picture.</p>
<p>The methodological frame across all of this is Rawls’s <em>reflective equilibrium</em> <span class="citation" data-cites="Rawls1971Theory">(Rawls 1971)</span>. Criteria for intergenerational equity should be judged on the ethical axioms they build on, and also on their consequences in concrete technological environments. If a criterion says we should sacrifice everything for any-positive-gain-by-the-future, or never sacrifice anything no matter how much the future gains, our intuition that <em>both</em> are wrong is itself evidence to revise the axioms. Picking axioms in the abstract and applying them blindly is bad methodology; we iterate between axioms and downstream implications until both feel right.</p>
<p>I started with Asheim’s <em>Annual Review</em> survey <span class="citation" data-cites="Asheim2010Intergenerational">(Asheim 2010)</span>.</p>
</section>
<section id="veto-power-formalism" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="veto-power-formalism"><span class="header-section-number">6</span> Veto-power formalism</h2>
<p>A bargaining-flavoured strand of the axiomatic literature models the game <em>as if</em> future generations could refuse. Suppose each generation has a Rubinstein-style alternating-offers position <span class="citation" data-cites="Rubinstein1982Perfect">(Rubinstein 1982)</span> with a credible veto over others’ resource decisions <span class="citation" data-cites="Manzini2010Intergenerational">(Manzini 2010)</span>. Even when future cohorts cannot in fact participate, asking “what allocation would they accept under bargaining parity?” gives a well-defined Social Welfare Relation that excludes the worst expropriations.</p>
<p>Such a veto is more a normative device than a positive one. It tells us what fairness <em>would</em> require if the unborn could speak; it does not predict what self-interested play <em>will</em> produce.</p>
</section>
<section id="proxy-mechanisms-in-practice" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="proxy-mechanisms-in-practice"><span class="header-section-number">7</span> Proxy mechanisms in practice</h2>
<p>Closer to political economy: institutions that artificially weight the bargaining power of those representing future interests.</p>
<p><em>Youth quotas</em> in legislative bodies. Younger voters and legislators have longer remaining horizons and internalise more of the long-term consequences; their interests partially overlap with the unborn. The argument is roughly: youths are the next-best proxy for those who cannot yet vote — see <a href="https://www.if.org.uk/wp-content/uploads/2015/12/151201_IGJR_final.pdf?ref=danmackinlay.name">the 2015 <em>Intergenerational Justice Review</em> issue</a> for several treatments.</p>
<p><em>Future-generations tribunals or commissioners</em>, with explicit review or veto powers over policies that bind the long term. Hungary, Wales, Israel, and others have variants; their bite varies enormously by jurisdiction and political will. See <span class="citation" data-cites="Basson2025Commentary">Basson et al. (2025)</span> on the human-rights framing.</p>
<p><em>Constitutional limits</em> on the actions of a present majority — debt ceilings, environmental amendments, supermajority requirements for long-term commitments. The constitutional framing serves as a proxy commitment device — see <a href="../notebook/commitment.html">commitment</a> — that binds future selves of the polity.</p>
<p>These are all imperfect. Youths are not the unborn; commissioners have their own interests; constitutions can be amended. But they are practical institutions trying to do the work the formal models say is needed.</p>
</section>
<section id="discounting-problems" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="discounting-problems"><span class="header-section-number">8</span> Discounting problems</h2>
<p>A deep open question is how <em>I</em> should rationally discount future utility when other people will experience it than me, because I will be dead. A positive discount rate — the empirical norm in cost-benefit analysis — heavily reduces the present-day weight on far-future welfare; this is what allows current generations to rationalise expropriation in policy documents. A zero or near-zero rate produces unbounded numbers (any benefit that compounds forever has infinite present value), forcing modellers to introduce extinction probabilities or other regularizations whose values are themselves contestable.</p>
<p>One area where people have though long and hard about this is <a href="../notebook/climate_change.html">climate change</a>. The Stern–Nordhaus debate on climate-change policy is largely a debate about discounting: the formal cost-benefit models produce wildly different recommendations depending on a parameter that game theory itself does not pin down. There is no equilibrium-style answer. Famous treatments such as DeCanio’s <em>Game Theory and Climate Change</em> <span class="citation" data-cites="DeCanio2013Game">(DeCanio and Fremstad 2013)</span> frame it as multi-objective optimization under deep uncertainty, with the discount rate as a normative input rather than a derivable feature.</p>
</section>
<section id="dont-die" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="dont-die"><span class="header-section-number">9</span> Don’t die</h2>
<p>One way to ameliorate intergenerational games is to not have any generations. If we can achieve indefinite lifespans, then the future is no longer a different generation; it’s just a later self. OTOH, <a href="../notebook/intertemporal_decisions.html">humans can also suck at helping out their future selves</a>.</p>
</section>
<section id="connections" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="connections"><span class="header-section-number">10</span> Connections</h2>
<p>Adjacent to: <a href="../notebook/cooperation.html">cooperation</a> (mechanisms generally), <a href="../notebook/coalition_games.html">coalition games</a> (when “the future generation” is itself heterogeneous), <a href="../notebook/commitment.html">commitment</a> (intertemporal commitment as the dual problem), <a href="../notebook/collective_action.html">collective action</a> (climate and commons specifically), <a href="../notebook/game_complexity.html">game complexity</a> (computing fair allocations in OLG with many cohorts is its own complexity story), and economics of growth (the standard growth models — Solow, Ramsey, Dasgupta–Heal–Solow — are doing intergenerational welfare analysis under a particular axiom set, usually discounted utilitarianism, often without flagging the choice).</p>
<p><strong>Longtermism</strong> (the EA / x-risk strand) sits inside this axiomatic space — typically a near-zero discount rate plus expected-total-utility aggregation, which is one specific resolution of the SP + FA tension. Whether longtermists engage with the impossibility results explicitly, or just smuggle in their preferred resolution via expected utility, varies a lot.`</p>
<p><strong>AI successionism</strong> — that AI systems should or will succeed humanity, and that this is acceptable or even desirable — fits inside the same frame. The problem is sharpened, though, because the “future generations” might have radically different utility functions and need not be biologically continuous with us at all. I am noodling on this elsewhere; the axiomatic frame is one tool for asking which axioms successionism implicitly endorses or rejects.</p>
</section>
<section id="incoming" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="incoming"><span class="header-section-number">11</span> Incoming</h2>
<ul>
<li><p>Samuel Moyn, <a href="https://www.newstatesman.com/ideas/2026/06/the-tyranny-of-the-gerontocracy-2?ref=danmackinlay.name">The dictatorship of the elderly</a></p></li>
<li><p>Louise Perry <a href="https://firstthings.com/modernitys-self-destruct-button/?ref=danmackinlay.name">Modernity’s Self-Destruct Button</a></p>
<blockquote class="blockquote">
<p>The Ponzi scheme of the old age pension is already collapsing, and it will almost certainly have collapsed entirely by the time I reach my sixty-eighth birthday. A ­gerontocratic voter base will cling to the state pension as long as possible—I wouldn’t be surprised if the British government abolished universal education before it abolished the state pension.</p>
</blockquote></li>
</ul>
</section>
<section id="references" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="references"><span class="header-section-number">12</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Asheim2010Intergenerational" class="csl-entry">
Asheim. 2010. <span>“<a href="https://doi.org/10.1146/annurev.economics.102308.124440">Intergenerational Equity</a>.”</span> <em>Annual Review of Economics</em>.
</div>
<div id="ref-Asheim2021How" class="csl-entry">
Asheim, Banerjee, and Mitra. 2021. <span>“<a href="https://doi.org/10.1007/s00199-020-01296-8">How Stationarity Contradicts Intergenerational Equity</a>.”</span> <em>Economic Theory</em>.
</div>
<div id="ref-Asheim2001Justifying" class="csl-entry">
Asheim, Buchholz, and Tungodden. 2001. <span>“<a href="https://doi.org/10.1006/jeem.2000.1136">Justifying Sustainability</a>.”</span> <em>Journal of Environmental Economics and Management</em>.
</div>
<div id="ref-Asheim2010Sustainable" class="csl-entry">
Asheim, Mitra, and Tungodden. 2012. <span>“<a href="https://doi.org/10.1007/s00199-010-0573-7">Sustainable Recursive Social Welfare Functions</a>.”</span> <em>Economic Theory</em>.
</div>
<div id="ref-Auerbach1991Generational" class="csl-entry">
Auerbach, Gokhale, and Kotlikoff. 1991. <span>“Generational Accounts: A Meaningful Alternative to Deficit Accounting.”</span> In <em>Tax Policy and the Economy</em>.
</div>
<div id="ref-Axtell2001Emergence" class="csl-entry">
Axtell, Epstein, and Young. 2001. <span>“<a href="https://doi.org/10.7551/mitpress/6294.003.0009">The Emergence of Classes in a Multi-Agent Bargaining Model</a>.”</span> In <em>Social Dynamics</em>.
</div>
<div id="ref-Barro1974Are" class="csl-entry">
Barro. 1974. <span>“<a href="https://doi.org/10.1086/260266">Are Government Bonds Net Wealth?</a>”</span> <em>Journal of Political Economy</em>.
</div>
<div id="ref-Basson2025Commentary" class="csl-entry">
Basson, Liebenberg, Wewerinke-Singh, et al. 2025. <span>“<a href="https://muse.jhu.edu/pub/1/article/972482">Commentary to the Maastricht Principles on the Human Rights of Future Generations</a>.”</span> <em>Human Rights Quarterly</em>.
</div>
<div id="ref-Basu2003Aggregating" class="csl-entry">
Basu, and Mitra. 2003. <span>“<a href="https://doi.org/10.1111/1468-0262.00458">Aggregating Infinite Utility Streams with Intergenerational Equity: The Impossibility of Being Paretian</a>.”</span> <em>Econometrica</em>.
</div>
<div id="ref-Chander2018Game" class="csl-entry">
Chander. 2018. <em>Game Theory and Climate Change</em>.
</div>
<div id="ref-Chichilnisky1996Axiomatic" class="csl-entry">
Chichilnisky. 1996. <span>“<a href="https://doi.org/10.1007/BF00183353">An Axiomatic Approach to Sustainable Development</a>.”</span> <em>Social Choice and Welfare</em>.
</div>
<div id="ref-DeCanio2013Game" class="csl-entry">
DeCanio, and Fremstad. 2013. <span>“<a href="https://doi.org/10.1016/j.ecolecon.2011.04.016">Game Theory and Climate Diplomacy</a>.”</span> <em>Ecological Economics</em>, New Climate Economics,.
</div>
<div id="ref-Diamond1965National" class="csl-entry">
Diamond. 1965. <span>“National Debt in a Neoclassical Growth Model.”</span> <em>The American Economic Review</em>.
</div>
<div id="ref-Greaves2019Case" class="csl-entry">
Greaves, and MacAskill. 2019. <span>“<a href="https://globalprioritiesinstitute.org/wp-content/uploads/The-Case-for-Strong-Longtermism-GPI-Working-Paper-June-2021-2-2.pdf">The Case for Strong Longtermism</a>.”</span> In <em>Essays on Longtermism: Present Action for the Distant Future</em>.
</div>
<div id="ref-Kotlikoff1992Generational" class="csl-entry">
Kotlikoff. 1992. <em>Generational Accounting: Knowing Who Pays, and When, for What We Spend</em>.
</div>
<div id="ref-Kotlikoff1993Inefficiency" class="csl-entry">
Kotlikoff, and Rosenthal. 1993. <span>“<a href="https://doi.org/10.3386/w3354">Some Inefficiency Implications of Generational Politics and Exchange</a>.”</span> <em>Economics &amp; Politics</em>.
</div>
<div id="ref-MacAskill2023What" class="csl-entry">
MacAskill. 2023. <em>What We Owe The Future</em>.
</div>
<div id="ref-Manzini2010Intergenerational" class="csl-entry">
Manzini. 2010. <span>“<a href="https://doi.org/10.7275/3317861">Intergenerational Justice in the Hobbesian State of Nature</a>.”</span>
</div>
<div id="ref-McClennen1990Rationality" class="csl-entry">
McClennen. 1990. <em>Rationality and Dynamic Choice: Foundational Explorations</em>.
</div>
<div id="ref-Phelps1968Second" class="csl-entry">
Phelps, and Pollak. 1968. <span>“<a href="https://doi.org/10.2307/2296547">On Second-Best National Saving and Game-Equilibrium Growth</a>.”</span> <em>The Review of Economic Studies</em>.
</div>
<div id="ref-Pollak1968Consistent" class="csl-entry">
Pollak. 1968. <span>“<a href="https://doi.org/10.2307/2296548">Consistent Planning</a>.”</span> <em>The Review of Economic Studies</em>.
</div>
<div id="ref-Rawls1971Theory" class="csl-entry">
Rawls. 1971. <em>A Theory of Justice</em>.
</div>
<div id="ref-Rubinstein1982Perfect" class="csl-entry">
Rubinstein. 1982. <span>“<a href="https://doi.org/10.2307/1912531">Perfect Equilibrium in a Bargaining Model</a>.”</span> <em>Econometrica</em>.
</div>
<div id="ref-Samuelson1958Exact" class="csl-entry">
Samuelson. 1958. <span>“<a href="https://doi.org/10.1086/258100">An Exact Consumption-Loan Model of Interest with or Without the Social Contrivance of Money</a>.”</span> <em>Journal of Political Economy</em>.
</div>
<div id="ref-Serrano2005Fifty" class="csl-entry">
Serrano. 2005. <span>“Fifty Years of the Nash Program, 1953–2003.”</span> <em>Investigaciones Económicas</em>.
</div>
<div id="ref-Strotz1955Myopia" class="csl-entry">
Strotz. 1955. <span>“<a href="https://doi.org/10.2307/2295722">Myopia and Inconsistency in Dynamic Utility Maximization</a>.”</span> <em>The Review of Economic Studies</em>.
</div>
</div>


</section>

 ]]></description>
  <category>agents</category>
  <category>cooperation</category>
  <category>economics</category>
  <category>evolution</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>intertemporal</category>
  <category>mind</category>
  <category>utility</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/game_theory_intergenerational.html</guid>
  <pubDate>Mon, 04 May 2026 02:36:57 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/skeletal_2x2_RP-P-1896-A-19368-193.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Vibecoding Apple apps</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/vibecoding_apple.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p>I want my phone to do small useful things for me — tally a thing I notice, nag me when I haven’t stretched, watch a sensor and remind me when something changes, that kind of thing. That kind of automation ought to be a 50-line shell script, except the phone is not a Protestant device, in that it does not permit a personal relationship with the silicon. Instead, my relationship is mediated by the rite of XCode, in the approved forms, the correct vestments, the sacred language.</p>
<p>I have not taken holy orders in Swift, and I am not going to. The iOS catechism looks, from outside, like elaborate liturgy wrapped around what should be a small blessing. What I need is an Electric Monk — a labour-saving device that handles the sacraments on my behalf.</p>
<p>Claude might just be that Monk.</p>
<p>On dogma: the App Store and the various fights about App Review are irrelevant to me, since the apps I’m writing are for me. Traditionally people fulminate at this point about how Apple development is expensive. I am resigned to paying Apple their 99 pieces of silver per year in Apple Developer fees for the privilege of running my own code on my own phone. It seems fine. In fact I don’t <em>technically</em> need to pay just to run code, but it gets tedious fast without signing the binaries, so whatever. It is more expensive than $0, but the time saving dwarfs it so, like, fine. I agree that it is annoying but it is not the worst. When Apple gets too creepy, I will switch to Linux and then I will have another long list of annoyances to complain about with the free toolchain there.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>tl;dr</strong> There exist two MCP servers— I run both, which seems to work.</p>
<ul>
<li><strong>XcodeBuildMCP</strong> — headless wrapper around <code>xcodebuild</code>/<code>simctl</code>/LLDB. Builds, tests, runs, debugs. Works with Xcode closed.</li>
<li><strong><code>xcrun mcpbridge</code></strong> — Apple’s own bridge into the <em>running</em> Xcode IDE. Live diagnostics, Apple docs lookup, SwiftUI Preview capture.</li>
</ul>
<p>They do not AFAICT overlap, or at least Claude seems to know which to use. The first is the build engine; the second is the IDE eyes-and-ears.</p>
</div>
</div>
</div>
<section id="it-works-now" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="it-works-now"><span class="header-section-number">1</span> It works now</h2>
<div id="fig-appley-eve" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-appley-eve-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/appley_eve_RP-P-OB-10.708.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/appley_eve_RP-P-OB-10.708.avif" id="fig-appley-eve" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-appley-eve-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>The Xcode+agent combo <em>kinda</em> worked for ages, but it was pretty noisy and slow. Recent Xcode updates (I think 26.3) made it way better. The agent no longer needs to strain <code>xcodebuild</code> log spew to figure out what went wrong. It calls a tool, gets back JSON with the file, line, column, and message of each error, and acts; apparently this is the standard way to make LLM-friendly dev tools. <code>xcodebuild</code> is famously verbose.<sup>1</sup></p>
</section>
<section id="preliminaries" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="preliminaries"><span class="header-section-number">2</span> Preliminaries</h2>
<p>Run the following to install Xcode or just the command line tools:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xcode-select</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--install</span></span></code></pre></div></div>
<p>Xcode’s command-line tools sometimes break after updates. The current incantations to fix them are:</p>
<ul>
<li><a href="https://forum.sublimetext.com/t/command-line-development-tools/65238/6?ref=danmackinlay.name">Command line development tools</a></li>
<li><a href="https://apple.stackexchange.com/questions/438785/xcode-error-when-launching-terminal/439006?ref=danmackinlay.name#439006">Xcode Error When Launching Terminal</a></li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xcodebuild</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-runFirstLaunch.</span></span>
<span id="cb2-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># or remove old CommandLineTools</span></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># to force upgrade</span></span>
<span id="cb2-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> rm <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-rf</span> /Library/Developer/CommandLineTools</span>
<span id="cb2-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> xcode-select <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--install</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># or maybe</span></span>
<span id="cb2-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> xcode-select <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> /Library/Developer/CommandLineTools</span></code></pre></div></div>
</section>
<section id="xcodebuildmcp" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="xcodebuildmcp"><span class="header-section-number">3</span> XcodeBuildMCP</h2>
<p>An open-source workhorse MCP with ~80 tools.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">claude</span> mcp add xcodebuildmcp <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> npx <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-y</span> xcodebuildmcp@latest</span></code></pre></div></div>
<p>It covers structured builds, test runs with per-method results, simulator boot/shutdown/install, real-device deployment, LLDB-driven debugging, and SwiftPM in addition to <code>.xcodeproj</code>/<code>.xcworkspace</code> and structured parseable return data from each.</p>
<p>The repo is at <a href="https://github.com/cameroncooke/XcodeBuildMCP?ref=danmackinlay.name">cameroncooke/XcodeBuildMCP</a>. Found via <a href="https://blakecrosley.com/blog/xcode-mcp-claude-code?ref=danmackinlay.name">Blake Crosley’s setup post</a>.</p>
<p>Even without opening Xcode-the-IDE, this MCP gets me from “Claude wrote some Swift” to “Claude built it, ran it on the simulator, ran the tests, and saw which ones failed”.</p>
</section>
<section id="apples-mcp-bridge" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="apples-mcp-bridge"><span class="header-section-number">4</span> Apple’s MCP bridge</h2>
<p>Ships with Xcode 26.something — no separate install, only an enable step:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">claude</span> mcp add xcode <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> xcrun mcpbridge</span></code></pre></div></div>
<p>Around 20 tools, all interfacing with a <em>running</em> Xcode GUI IDE — file ops in the editor, real-time diagnostics from the live build context, Apple documentation search, a Swift REPL, and <em>SwiftUI Preview capture</em>. The agent gets to <a href="https://www.anthropic.com/news/apple-xcode-claude-agent-sdk?ref=danmackinlay.name">see the rendered preview of UI</a>, not just the code that produced it, and can iterate visually.</p>
<p>This one only works with Xcode running, so it’s no use for headless CI. For the kind of work I’m doing — sitting at the laptop, iterating on a small app — having Xcode open is not a hardship.</p>
</section>
<section id="division-of-labour" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="division-of-labour"><span class="header-section-number">5</span> Division of labour</h2>
<p>XcodeBuildMCP implements the build–test–debug loop. Apple’s MCP implements all the “GUI stuff” IDE-layer awareness — diagnostics, docs, previews. It seems <a href="https://blakecrosley.com/blog/xcode-mcp-claude-code?ref=danmackinlay.name">we should run both</a>, insofar as I got an LLM to generate a capabilities table for me and that’s what it said:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th>XcodeBuildMCP</th>
<th><code>xcrun mcpbridge</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Install</td>
<td><code>npx xcodebuildmcp@latest</code></td>
<td>bundled with Xcode 26</td>
</tr>
<tr class="even">
<td>Xcode open?</td>
<td>not required</td>
<td>required</td>
</tr>
<tr class="odd">
<td>Build / test / run</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr class="even">
<td>SwiftUI Previews</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr class="odd">
<td>Apple docs</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr class="even">
<td>LLDB</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr class="odd">
<td>CI / headless</td>
<td>✅</td>
<td>❌</td>
</tr>
</tbody>
</table>
</section>
<section id="project-claude.md" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="project-claude.md"><span class="header-section-number">6</span> Project CLAUDE.md</h2>
<p>A short hint at the top of the project’s <code>CLAUDE.md</code> keeps the agent from rediscovering the toolchain on every turn.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource markdown number-lines code-with-copy"><code class="sourceCode markdown"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## XcodeBuildMCP integration</span></span>
<span id="cb5-2"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Build: mcp__xcodebuildmcp__build_sim_name_proj</span>
<span id="cb5-3"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Test:  mcp__xcodebuildmcp__test_sim_name_proj</span>
<span id="cb5-4"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Platform: iOS, Swift 6, SwiftUI, MVVM + @Observable</span></code></pre></div></div>
</section>
<section id="in-ide-claude" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="in-ide-claude"><span class="header-section-number">7</span> In-IDE Claude</h2>
<p>Xcode 26.3+ ships with a Claude Agent panel built into the IDE, using the same Agent SDK and roughly the same ~20 tools as <code>xcrun mcpbridge</code>. Go to Settings → Intelligence, <a href="https://www.youtube.com/watch?v=t8UXKifcLXQ&amp;ref=danmackinlay.name">drop in an Anthropic API key</a>.</p>
<p>I do not use this. I live in the terminal, and Claude Code is where I develop every other bloody thing, and my Apple vibecoding projects are not significant enough to justify a shift. Also I spent about 15 seconds looking for the agent panel as per the instructions and didn’t find it, and I left, bored. YMMV.</p>
</section>
<section id="loose-ends" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="loose-ends"><span class="header-section-number">8</span> Loose ends</h2>
<section id="signing-and-provisioning" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="signing-and-provisioning"><span class="header-section-number">8.1</span> Signing and provisioning</h3>
<p>Even with the ($) developer account, the rituals of personal devices, free/paid teams, provisioning profiles, capabilities, and the keychain are confusing and change in every release. MCPs do not seem to abstract this away, at least not for me. Perhaps there is no abstraction over it because Apple would rather have a human somewhere in the loop to sign off on their nonsense.</p>
</section>
<section id="putting-my-custom-apps-on-my-friends-devices" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="putting-my-custom-apps-on-my-friends-devices"><span class="header-section-number">8.2</span> Putting my custom apps on my friend’s devices</h3>
<p>For my own apps on my own phone, the dev-account install with a 1-year cert is fine. For apps I might want to give to one or two friends, the answer is less clear. AltStore? TestFlight? I haven’t committed. Probably TestFlight.</p>
</section>
<section id="swiftui-preview-is-it-good" class="level3" data-number="8.3">
<h3 data-number="8.3" class="anchored" data-anchor-id="swiftui-preview-is-it-good"><span class="header-section-number">8.3</span> SwiftUI Preview, is it good?</h3>
<p>Does the automated UI capture actually close the loop? In principle, the agent sees the preview and iterates. In practice I’m not yet sure how well this scales past a single isolated view. View hierarchies, state, mock data, navigation contexts — all of these complicate the preview. Currently I plan to avoid ever finding this out by just not building anything complicated enough to need it.</p>
</section>
<section id="all-the-system-apis" class="level3" data-number="8.4">
<h3 data-number="8.4" class="anchored" data-anchor-id="all-the-system-apis"><span class="header-section-number">8.4</span> All the system APIs</h3>
<p>Core Data, HealthKit, Shortcuts intents, WidgetKit — these are not as well attested on the internet as Python, and the API keeps shifting. I note that Claude frequently resorts to defunct APIs and deprecated patterns. How annoying is this in practice?</p>
</section>
<section id="disk-hungriness" class="level3" data-number="8.5">
<h3 data-number="8.5" class="anchored" data-anchor-id="disk-hungriness"><span class="header-section-number">8.5</span> Disk hungriness</h3>
<p>Oh noes, Xcode is gigantic and it fills up my backup drive! What to do?</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> /Applications/Xcode.app</span>
<span id="cb6-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> ~/Library/Developer/Xcode/DerivedData</span>
<span id="cb6-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> ~/Library/Developer/CoreSimulator</span>
<span id="cb6-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> ~/Library/Developer/Xcode/iOS\ DeviceSupport</span>
<span id="cb6-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> ~/Library/Developer/Xcode/watchOS\ DeviceSupport</span>
<span id="cb6-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> tmutil addexclusion <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> ~/Library/Developer/Xcode/visionOS\ DeviceSupport</span></code></pre></div></div>
<p>🚧TODO🚧: should I also exclude these from Spotlight?</p>
</section>
</section>
<section id="shortcuts" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="shortcuts"><span class="header-section-number">9</span> Shortcuts</h2>
<ul>
<li><p><a href="https://github.com/viticci/shortcuts-playground-plugin?ref=danmackinlay.name">viticci/shortcuts-playground-plugin</a></p>
<blockquote class="blockquote">
<p>Shortcuts Playground: A Claude Code and Codex plugin for building, validating, signing, and remixing macOS/iOS Shortcuts with natural language.</p>
</blockquote></li>
</ul>
</section>
<section id="usable-scripting" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="usable-scripting"><span class="header-section-number">10</span> Usable scripting</h2>
<p>Apple has shipped a slow, incomprehensible scripting language for as long as I can remember. It’s called <em>AppleScript</em>. After spending a long time trying to persuade it to do things, I’m comfortable saying it will never save me time if I write it by hand, although I do occasionally run a one-liner via <a href="https://ss64.com/osx/osascript.html?ref=danmackinlay.name">osascript</a>. There are attempts to, in principle, open up macOS’s scripting APIs to, e.g., JavaScript via <a href="http://jstalk.org/?ref=danmackinlay.name">jstalk</a> or Python via <a href="https://pythonhosted.org/pyobjc/?ref=danmackinlay.name">PyObjC</a>, but that’s brutally low level unless we’re app developers, and the documentation is even worse. If we’re writing apps, why not just use Swift?</p>
<p>Some people have converged on the <a href="https://developer.apple.com/documentation/uikit/accessibility/?ref=danmackinlay.name">UIKit accessibility API</a> as a reasonable scripting interface.</p>
<p>On this front, try <a href="http://www.hammerspoon.org/go/?ref=danmackinlay.name">Hammerspoon</a>/Phoenix. <a href="https://pypi.org/project/pyatomac/?ref=danmackinlay.name">pyatomac</a> does the same for Python but seems designed for UI testers, not UI users. You can <a href="https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/AutomatetheUserInterface.html?ref=danmackinlay.name">do it with AppleScript</a> of course, if your heart overflows with bituminous hate.</p>
<p>Or: vibe-code a Swift app instead? See above.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>I am told the <code>xcodebuild</code> log volume on a clean build of a non-trivial app is measured in megabytes. Certainly if I leave debug logging on, my iPhone storage goes to 0 fast.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>apple</category>
  <category>code</category>
  <category>DIY</category>
  <category>faster pussycat</category>
  <category>making things</category>
  <category>workflow</category>
  <guid>https://danmackinlay.name/notebook/vibecoding_apple.html</guid>
  <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/appley_eve_RP-P-OB-10.708.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Agency under bounded compute and information</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/agency_bounded_compute.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Adjacent run-ups
</div>
</div>
<div class="callout-body-container callout-body">
<p>One of several notebooks I started on the same underlying problem. The others are <a href="../notebook/homunculi.html">homunculi</a> (compute split across self, other, and reflective sub-models) and <a href="../notebook/ai_economics_of_cognition.html">economics of cognition</a> (compute as a “substitutable factor of production of cognition” or something like that).</p>
</div>
</div>
<div id="fig-giant-factotum" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-giant-factotum-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/giant_factotum_RP-P-1985-65.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/giant_factotum_RP-P-1985-65.avif" id="fig-giant-factotum" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-giant-factotum-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>One thing that’s especially baffling about economists, <a href="../notebook/aixi.html">AIXI</a>, and <a href="../notebook/bayes_is_not_enough.html">classical Bayesian agent theory</a> is that they often treat the “agent” as a Platonic ideal, with infinite compute and perfect information, or at least as something to be criticized for deviating from that asymptotic ideal. We all know this is not quite right. We have known this since the original Von Neumann-Morgenstern axioms, and we periodically try to patch it with ad hoc <a href="../notebook/bounded_rationality.html">bounded rationality</a> fixes. None of these fixes are quite as theoretically satisfying, so even as we make the disclaimers, the unbounded agent seeps into our reasoning in all kinds of ways.</p>
<p>Maybe there is no “right” way to do it except getting elbows-deep in the empirics of what humans do. That is one potential state of the world.</p>
<p>Nonetheless, I think a better (more contiguous, more compact) theory of bounded agency might help. Maybe not something that “explains everything” about humans in every detail. But surely something that explains <em>better</em> what regularities we might find in finite reasoners in general? I do not think we can avoid at least trying to devise one, because it turns out that we are beginning to share our world with <a href="../notebook/ai_agency.html">new types of reasoning agents</a>, and a theory of what we might share in common feels existentially important. One thing we for sure share is <a href="../notebook/ai_economics_of_cognition.html">boundedness</a>.</p>
<p>Here is yet another attempt (this blog is racking them up) to understand wtf is going on with achievable, bounded agents. I walk through the salient concepts and then get snarled in the details of each of them.</p>
<section id="full-information-unlimited-compute" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="full-information-unlimited-compute"><span class="header-section-number">1</span> Full-information, unlimited compute</h2>
<p>If we have complete information about the initial conditions of the universe, and unlimited compute, then it does not seem natural to classify anything as possessing “agency”; we can see the whole of the universe, up to quantum fluctuations, by stepping our perfect simulator forward in time. There are no decisions to be made, merely consequences to be observed.</p>
<p>But we might also find that thinking of ourselves as horologists to the clockwork universe is unsatisfactory as a model for us doing the modelling. I, for example, will never be this omniscient being. To know the whole of this universe, I would need another, much larger, universe to store the information in, to build the computer to do the simulation, and so on. I mean <a href="../notebook/this_is_a_simulation.html">maybe that happened and I am in the simulation of some omniscient agent</a>? Even then, that omniscient agent is not a great model for me.</p>
<p><a href="../notebook/aixi.html">AIXI</a> relaxes the first assumption (we don’t know where the universe started, we just arrived in it with an effectively infinite computer and need to work out what it is computing). That still doesn’t feel like enough of a relaxation. More generally, “agency” seems like it might accrue to entities which need to learn to act with both bounded information and bounded compute.</p>
<p>It sounds like it might be time for a quadrant map! That sometimes helps. The two axes are <em>information about the world</em> (complete vs bounded) and <em>compute available</em> (unlimited vs bounded).</p>
<ul>
<li><em>Complete information, unlimited compute.</em> A Laplacean demon. Step the perfect simulator forward in time. There is nothing here we would want to call agency; the universe just runs.</li>
<li><em>Bounded information, unlimited compute.</em> <a href="../notebook/aixi.html">AIXI</a> and friends. We grant ourselves an environment hypothesis class weighted by Kolmogorov complexity and let the agent “solve” it in closed form. Computationally troubling, but a background against which much <a href="../notebook/agent_foundations.html">agent foundations</a> theory plays out.</li>
<li><em>Complete information, bounded compute.</em> An oracle that computes too slowly to act in time. This seems maybe relevant to weather forecasting or replay buffers, but not to agency proper, so I won’t talk about it much.</li>
<li><em>Bounded information, bounded compute.</em> Where we and large language models and bacteria and politicians can be found. The thesis of this notebook is that this combination is the <em>primary</em> one we need to understand to “know about agents like ourselves”, and that the other three are idealizations that probably do not have much useful to say about people.</li>
</ul>
</section>
<section id="two-senses-of-agent" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="two-senses-of-agent"><span class="header-section-number">2</span> Two senses of agent</h2>
<p>“Agent” is doing two jobs in this notebook, and I have been sloppy about which. Sometimes it means a bounded <em>observer</em>: a system embedded in a world it must make inferences about from inside. Sometimes it means a bounded <em>actor</em>: a system that also changes the world it is sampling. The observer’s problem is “estimation under openness”. The actor’s problem — what “acting well” can even mean from inside — is here.</p>
<p>The two problems do not factor cleanly. Control theory calls the clean case the <em>separation principle</em>: in LQG the optimal controller is an optimal filter composed with an optimal actor that trusts the estimate. Much of what is interesting below is where that factorization fails — dual control (acting partly in order to learn), the good regulator theorem (acting well requires containing a model), <a href="../notebook/performative_prediction.html">performative prediction</a> (being read is already acting). POMDP-as-agent below attempts to give structural form to the separation principle by choosing a nice low-complexity variant</p>
<p>The quadrant map axes classify <em>agents</em> by their endowments, but the criteria that end up doing the work — the admissibility conditions end up looking different. Specifically, I didn’t handle the problem of self-containment, which in hindsight is where the Cantor trap gets in.</p>
</section>
<section id="the-good-regulator-in-the-big-world" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-good-regulator-in-the-big-world"><span class="header-section-number">3</span> The good regulator in the big world</h2>
<p>The <a href="../notebook/internal_models.html">internal model principle</a>, AKA the “good regulator theorem”, says, roughly: <em>any agent that does well in an environment must contain a model of that environment</em>. <span class="citation" data-cites="Conant1970Every">Conant and Ashby (1970)</span> is the original cybernetics variant, which is to say, mathematical but a little woo; Francis &amp; Wonham <span class="citation" data-cites="Francis1976Internal">(Francis and Wonham 1976)</span> give the control-theoretic version; <span class="citation" data-cites="Huang2018Internal">Huang et al. (2018)</span> is a recent tutorial. We start with the hypothesis that if it is doing ok, an “agent” “contains” a “model” of its “world”, for some definition of those terms.</p>
<p>The big-world hypothesis says: the environment is strictly bigger than the agent — there is no point at which the agent can ever have “solved” the world.</p>
<p>Together, these suggest that when thinking about agents, we need to think about <em>lossy compression</em> of the world. What kind of compression is the agent’s internal model? Which features might it store? What is it sufficient for? When does the residual look like noise, and when does it look like the world ambushing us? What <em>can</em> we afford that still works?</p>
<p>Somewhere in this porridge of formalisms we might find floating a soggy mass corresponding to <a href="../notebook/bounded_rationality.html">bounded rationality</a>, which reminds us that we cannot afford the naïve model, the territory-as-map.</p>
</section>
<section id="prior-art" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="prior-art"><span class="header-section-number">4</span> Prior art</h2>
<p>Short notes on each. I got an LLM to fill in the columns of the table for my edification. Future-me, do not be alarmed by the count — these are mostly variations on a single optimization problem, viewed from different traditions. I suspect some of these collapse into each other under some trivial translation of terminology, but I know not which.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Entry</th>
<th>Operates on</th>
<th>Where bound enters</th>
<th>C</th>
<th>St</th>
<th>Sc</th>
<th>Side</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Resource-rational analysis</td>
<td>cognitive strategies</td>
<td>soft cost on cognitive operations</td>
<td>✓</td>
<td>~</td>
<td>✗</td>
<td>actor</td>
</tr>
<tr class="even">
<td>Bounded optimality</td>
<td>programs for architecture <img src="https://latex.codecogs.com/png.latex?M"></td>
<td>hard feasibility</td>
<td>✓</td>
<td>✗</td>
<td>✗</td>
<td>actor</td>
</tr>
<tr class="odd">
<td>Rational metareasoning</td>
<td>computations, online</td>
<td>per-step VOC cost</td>
<td>✓</td>
<td>✓</td>
<td>~</td>
<td>actor</td>
</tr>
<tr class="even">
<td>Information-theoretic bounded rationality</td>
<td>policies</td>
<td>KL budget vs prior</td>
<td>~</td>
<td>✓</td>
<td>✗</td>
<td>actor</td>
</tr>
<tr class="odd">
<td>Bounded inductive rationality</td>
<td>logical beliefs over time</td>
<td>no-arbitrage in the limit</td>
<td>✓</td>
<td>✓</td>
<td>~</td>
<td>hinge</td>
</tr>
<tr class="even">
<td>Computationally tractable choice</td>
<td>decision rules</td>
<td>polynomial-time axiom</td>
<td>✓</td>
<td>—</td>
<td>✗</td>
<td>actor</td>
</tr>
<tr class="odd">
<td>POMDP-as-agent</td>
<td>system interpretations</td>
<td>state-space capacity</td>
<td>✓</td>
<td>✗</td>
<td>~</td>
<td>hinge</td>
</tr>
<tr class="even">
<td>Computationally-embedded big world</td>
<td>agent automata in computable worlds</td>
<td>agent fits inside the world</td>
<td>✓</td>
<td>✓</td>
<td>✓</td>
<td>observer</td>
</tr>
</tbody>
</table>
<p>The C / St / Sc columns are verdicts under the admissibility conditions: is the framework’s criterion computable within the agent’s bounds, a statistic of observable data, self-contained in a hypothesis class containing the agent? The verdicts apply to each framework’s <em>criterion</em>, not to the agent it describes; “~” marks a hedged verdict and “—” means the question is ill-posed for that row. The Side column tracks the two senses of agent: estimation, action, or the factorization between them. I dropped the old “what it does” column to make room; it said “picks the optimum” six different ways.</p>
<p>NARS seems philosophically close to these but is not formalized tightly enough to fit a row.</p>
<section id="resource-rational-analysis" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="resource-rational-analysis"><span class="header-section-number">4.1</span> Resource-rational analysis</h3>
<p><span class="citation" data-cites="Lieder2020Resourcerational">Lieder and Griffiths (2020)</span> frames human cognition as the optimal use of finite computational resources. The procedure: define the agent’s task, the cognitive operations available, and their costs; derive the heuristic that maximizes expected utility under the budget. We have succeeded if observed cognitive biases match those predicted under some constrained problem.</p>
<p>The phrase pops up in many papers <span class="citation" data-cites="Bhui2021Resourcerational Collins2024Building Hahn2022Resourcerational Ho2022People Levine2024Resourcerational">(Bhui, Lai, and Gershman 2021; Collins et al. 2024; Hahn et al. 2022; Ho et al. 2022; Levine et al. 2024)</span>.</p>
<p>LLM summary follows:</p>
<p>The optimization has the same shape as the bounded-rationality entries above but at a different grain:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi%5E*%20=%20%5Carg%5Cmax_%7B%5Cpi%20%5C,%5Cin%5C,%20%5Ctext%7Bstrategies%7D%7D%20%5Cmathbb%7BE%7D%5Cbigl%5B%5C,U(%5Cpi)%20-%20%5Cmathrm%7Bcost%7D(%5Cpi)%5C,%5Cbigr%5D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cpi"> is a <em>cognitive strategy</em> — an algorithm parameterized by how much compute it spends — and <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Bcost%7D(%5Cpi)"> is a cost on cognitive operations (search expansions, posterior samples, working-memory loads). This lies between BO and RM in granularity: BO optimizes whole programs offline, RM optimizes individual computations online, and RRA optimizes the heuristic itself — a strategy with a configurable compute budget — treating it as a unit.</p>
<p>RRA’s distinctive play is reading observed biases as <img src="https://latex.codecogs.com/png.latex?%5Cpi%5E*"> under some plausible cognitive cost model. We have derived anchoring-style adjustment and availability-based estimation in this framework as resource-rational under specific costs on sample count and inference depth. The inferential arrow runs in the unusual direction: from observed heuristic, we infer the brain’s implicit cost structure.</p>
<p>What this gives us: machinery for a “best affordable model”. If the world is bigger than the agent, this is the framework that tells us <em>which</em> compression to keep.</p>
</section>
<section id="bounded-optimality" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="bounded-optimality"><span class="header-section-number">4.2</span> Bounded optimality</h3>
<p>Russell &amp; Subramanian <span class="citation" data-cites="Russell1995Provably">(S. J. Russell and Subramanian 1995)</span> define a program as bounded-optimal for a given architecture if no other program for that architecture does better in the environment. The optimization moves from “best action” to “best algorithm-on-the-hardware”. Resource-rational analysis is its descendant in the cognitive-science direction. <em>Computational rationality</em> <span class="citation" data-cites="Gershman2015Computational">(Gershman, Horvitz, and Tenenbaum 2015)</span> seems to be the umbrella term.</p>
<p>LLM summary follows:</p>
<p>Fix an architecture <img src="https://latex.codecogs.com/png.latex?M"> — a machine model with bounded memory and per-step compute, plus a clock. Bounded optimality is the program <img src="https://latex.codecogs.com/png.latex?%5Cpi%5E*"> that maximizes expected utility <em>subject to running on <img src="https://latex.codecogs.com/png.latex?M"></em>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi%5E*%20=%20%5Carg%5Cmax_%7B%5Cpi%20%5C,%5Cin%5C,%20%5Cmathrm%7Bprograms%7D(M)%7D%20%5Cmathbb%7BE%7D%5Cbigl%5BU%20%5Cmid%20%5Cpi,%5C,%5Ctext%7Benvironment%7D%5Cbigr%5D.%0A"></p>
<p>So the agent is no longer choosing actions; it is choosing the algorithm that will, in turn, choose actions on its hardware (like resource-constrained Functional Decision Theory?). This is a meta-level optimization over policies-as-programs, with the architecture’s resource constraints baked into the feasible set rather than tacked on as a soft penalty — which is what distinguishes BO from the KL-regularized, free-energy framing above.</p>
<p>Russell &amp; Subramanian also propose <em>asymptotic</em> bounded optimality (ABO), a weakening that requires the program to approach optimality only as the architecture is scaled up, because strict per-architecture optimality is itself intractable to analyse. cf <a href="../notebook/large_sample_theory.html">large sample theory</a> — infinite asymptotics are more tractable to analyse. ABO is a softer cousin of AIXI’s compute-to-infinity move — when “best on <img src="https://latex.codecogs.com/png.latex?M">” is too hard to characterize, the analysis retreats to “best as <img src="https://latex.codecogs.com/png.latex?M"> grows”. Sticking to bounded compute seems to be really hard, judging by what smart people actually do when they try.</p>
<p>What it gives us: an insistence that the budget is a property of the <em>implementation</em>.</p>
<p>Future me, if I am tempted to talk about “the agent’s budget” without specifying the architecture, see above.</p>
</section>
<section id="rational-metareasoning" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="rational-metareasoning"><span class="header-section-number">4.3</span> Rational metareasoning</h3>
<p><span class="citation" data-cites="Russell1991Principles">S. Russell and Wefald (1991)</span> model each unit of computation as a decision: expected value of running the computation minus its cost.<sup>1</sup> The agent decides which inferences to run as a planning problem one level above object-level decision-making.</p>
<p>LLM summary follows:</p>
<p>Two questions distinguish RM from the Bounded Optimality (BO) programme (also a Russell programme): <em>what</em> is being optimized and <em>when</em>. BO optimizes <em>programs</em>, offline — a designer picks the best program for a fixed architecture, once, and the program then just runs. RM optimizes <em>computation choices</em>, online — at runtime the agent itself decides, step by step, which inference to run next. The two framings are complementary: RM is the technique a bounded-optimal program might use internally to allocate its compute, and BO is the criterion under which the program-plus-RM-policy bundle should be assessed.</p>
<p>The decision-theoretic content is the <em>value of computation</em> (VOC). Let <img src="https://latex.codecogs.com/png.latex?b"> be the agent’s current belief state and <img src="https://latex.codecogs.com/png.latex?c"> a candidate computation — expanding a node in a search tree, sampling another rollout, refining a posterior. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7BVOC%7D(c%20%5Cmid%20b)%20=%20%5Cmathbb%7BE%7D%5Cbigl%5B%5C,U%20%5C,%5Cbig%7C%5C,%20%5Ctext%7Bact%20after%20%7D%20c,%5C,%20b%5C,%5Cbigr%5D%20-%20%5Cmathbb%7BE%7D%5Cbigl%5B%5C,U%20%5C,%5Cbig%7C%5C,%20%5Ctext%7Bact%20now%7D,%5C,%20b%5C,%5Cbigr%5D%20-%20%5Cmathrm%7Bcost%7D(c).%0A"></p>
<p>Run <img src="https://latex.codecogs.com/png.latex?c"> if its VOC is positive; act otherwise. The Hay-Russell-Tolpin-Shimony meta-MDP turns this into a proper Markov decision process whose states are <em>beliefs over the underlying MDP</em> and whose actions include both external actions and computation actions, with optimal policies given by Bellman equations on the meta level.</p>
<p>Notice the regress: estimating <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BVOC%7D(c)"> before running <img src="https://latex.codecogs.com/png.latex?c"> is itself a computation, and deciding whether <em>that</em> is worth running is another. Practical RM truncates the recursion — usually at one level (myopic VOC, “is this single next step worth it?”), occasionally deeper. The truncation is what lets the framework run; whether the truncated VOC is itself bounded-optimal in any tight sense is the question that makes RM and BO need each other.</p>
<p>What it gives us: a decision-theoretically explicit story about <em>when to compute</em>, which is the question posed by the good-regulator-meets-big-world tension. The agent cannot run the full simulation; the metareasoner picks the slice.</p>
</section>
<section id="information-theoretic-bounded-rationality" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="information-theoretic-bounded-rationality"><span class="header-section-number">4.4</span> Information-theoretic bounded rationality</h3>
<p>Ortega &amp; Braun <span class="citation" data-cites="Ortega2011Information Ortega2013Thermodynamics">(Pedro Alejandro Ortega and Braun 2011; Pedro A. Ortega and Braun 2013)</span> formalize a bounded-rational policy as the KL-divergence-constrained optimum of expected utility. I have qualms about using KL as a model of compute cost, but it is not crazy in that sometimes minimizing a KLD is practically computationally achievable even where estimating the KLD itself is not, which gets us part-way there. Still, I am generally skeptical that there is anything practical in there.</p>
<p>LLM summary follows:</p>
<p>Pick a <em>reference</em> or <em>prior</em> policy <img src="https://latex.codecogs.com/png.latex?%5Cpi_0"> — what the agent does cheaply, without deliberating — and constrain the deliberated policy <img src="https://latex.codecogs.com/png.latex?%5Cpi"> to stay close to it in KL. So the budget is on the KL divergence, not on FLOPs, and the optimization is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi%5E*%20=%20%5Carg%5Cmax_%5Cpi%20%5C;%5Cmathbb%7BE%7D_%5Cpi%5BU(a)%5D%0A%5Cquad%5Ctext%7Bs.t.%7D%5Cquad%0A%5Cmathrm%7BKL%7D(%5Cpi%20%5C,%5C%7C%5C,%20%5Cpi_0)%20%5Cle%20B,%0A"></p>
<p>equivalently, with <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> the Lagrange multiplier on the constraint,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi%5E*%20=%20%5Carg%5Cmax_%5Cpi%20%5C;%5Cmathbb%7BE%7D_%5Cpi%5BU(a)%5D%20-%20%5Ctfrac%7B1%7D%7B%5Cbeta%7D%5C,%5Cmathrm%7BKL%7D(%5Cpi%20%5C,%5C%7C%5C,%20%5Cpi_0).%0A"></p>
<p>The thermodynamic flavour comes from reading <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BKL%7D(%5Cpi%20%5C,%5C%7C%5C,%20%5Cpi_0)"> as the <em>work done against the prior</em>: every nat of divergence from <img src="https://latex.codecogs.com/png.latex?%5Cpi_0"> has to be extracted from somewhere, by the same Landauer-style accounting that prices erasure. That is the argument that this functional counts as a model of compute cost and not just a regularizer.</p>
<p>The functional that drops out is a free energy, <img src="https://latex.codecogs.com/png.latex?F%5B%5Cpi%5D%20=%20%5Cmathbb%7BE%7D_%5Cpi%5BU%5D%20-%20%5Ctfrac%7B1%7D%7B%5Cbeta%7D%5C,%5Cmathrm%7BKL%7D(%5Cpi%20%5C,%5C%7C%5C,%20%5Cpi_0)">, whose maximizer is the Boltzmann-tilted policy <img src="https://latex.codecogs.com/png.latex?%5Cpi%5E*(a)%20%5Cpropto%20%5Cpi_0(a)%5C,%5Cexp(%5Cbeta%5C,U(a))">. The convenient consequence is that we can sample from <img src="https://latex.codecogs.com/png.latex?%5Cpi%5E*"> by importance-weighting draws from <img src="https://latex.codecogs.com/png.latex?%5Cpi_0"> against <img src="https://latex.codecogs.com/png.latex?%5Cexp(%5Cbeta%20U)"> — no need to enumerate actions to find the argmax, because the optimum is an integral over <img src="https://latex.codecogs.com/png.latex?%5Cpi_0"> that a Monte Carlo planner approximates without bias. <span class="citation" data-cites="Hafner2022Action">Hafner et al. (2022)</span> cast action and perception as joint divergence minimization in this setting.</p>
<p>What it gives us: a formal stand-in for “compute budget” inside decision theory. It is also a bridge to thermodynamics, since the same functional reappears in the substrate principle.</p>
</section>
<section id="bounded-inductive-rationality" class="level3" data-number="4.5">
<h3 data-number="4.5" class="anchored" data-anchor-id="bounded-inductive-rationality"><span class="header-section-number">4.5</span> Bounded inductive rationality</h3>
<p>Oesterheld, Demski &amp; Conitzer <span class="citation" data-cites="Oesterheld2023Theory">(Oesterheld, Demski, and Conitzer 2023)</span> drop logical omniscience.</p>
<p>LLM summary follows:</p>
<p><em>Logical omniscience</em> is the standard Bayesian assumption that an agent assigns probability 1 to every theorem and probability 0 to every contradiction the moment those are stated. Classical decision theory needs that assumption to make the consequences of beliefs cash out: if a finite reasoner believes Peano arithmetic and PA proves <img src="https://latex.codecogs.com/png.latex?%5Cvarphi">, then classically the reasoner <em>already</em> believes <img src="https://latex.codecogs.com/png.latex?%5Cvarphi">. In any practical sense it does not, because nobody has run the proof. Until the agent spends compute to derive <img src="https://latex.codecogs.com/png.latex?%5Cvarphi">, the sentence’s truth-value is just a probability strictly between 0 and 1 — and the agent needs a coherent way to reason with that probability while the proof is still in flight.</p>
<p>The machinery is the logical-induction stuff: a market of <em>traders</em> who bet on whether sentences will be proved, refuted, or remain undecided by a given deductive horizon, with the agent’s beliefs as the market prices. Coherence is not a constraint at any single moment but a no-arbitrage criterion <em>over time</em> — no efficiently-computable trader should be able to extract unbounded money from the agent’s prices as the deductive horizon expands. The agent ends up with calibrated logical beliefs in the limit without ever having been classically coherent on any particular day. ODC’s contribution over the precursor logical-induction work is a clean axiomatization: the trader-market construction was the existence proof; ODC characterize the property class.</p>
<p>A consequence we will need later: the agent never has to <em>commit</em> to what it will prove. It has prices on its own future actions and inferences, which is the move that dissolves the Löbian / 5-and-10 / spurious-counterfactual traps in <a href="../notebook/agency_embedded.html">embedded agency</a>. A coherent agent that is required to know now what it will do later runs into diagonal arguments; an inductively-rational agent answers “I will give you a probability, and update it as the proof comes in”.</p>
<p>This gives us a story close to the <a href="../notebook/agency_embedded.html">embedded agency</a> problem, but still with bounded compute. This might be the most direct response to the Löbian-paradox stuff without shooting off to AIXI’s infinity.</p>
</section>
<section id="computationally-tractable-choice" class="level3" data-number="4.6">
<h3 data-number="4.6" class="anchored" data-anchor-id="computationally-tractable-choice"><span class="header-section-number">4.6</span> Computationally tractable choice</h3>
<p>Camara <span class="citation" data-cites="Camara2021Computationally">(Camara 2021)</span> adds an axiom of computational tractability to decision theory: rule out behaviours that are too computationally hard.</p>
<p>LLM summary follows:</p>
<p>Take whatever set of decision-theory axioms we like — rationalizability, transitivity, independence — and add one more, that the agent’s choice function admits a polynomial-time implementation. Choice rules that fail this axiom are not admissible models of choosing agents.</p>
<p>CTC rules out <em>theories</em> of choice. Camara identifies particular preference structures whose induced choice rules cannot be computed in polynomial time, and concludes that those structures cannot describe how any tractable computer chooses. If observed behaviour can only be rationalized by a super-polynomial decision rule, the verdict is that the rationalization is the wrong account — the right move is to find a tractable rule that also fits the data, not to credit the agent with running an intractable algorithm in its head.</p>
<p>So whereas bounded optimality picks the best program <em>for a given architecture</em>, CTC rules decision <em>theories</em> out <em>for any architecture at all</em>. We could do both — BO tells us how to optimize inside the admissible class of theories; CTC tells us what the admissible class is.</p>
<p>What it gives us: a decision-theoretic analogue of bounded optimality, more axiomatic in flavour, maybe easier to plug into <a href="../notebook/ai_economics_of_cognition.html">economics-of-cognition</a> arguments.</p>
</section>
<section id="pomdp-as-agent" class="level3" data-number="4.7">
<h3 data-number="4.7" class="anchored" data-anchor-id="pomdp-as-agent"><span class="header-section-number">4.7</span> POMDP-as-agent</h3>
<p><span class="citation" data-cites="Biehl2023Interpreting">Biehl and Virgo (2023)</span> interpret a white-box system as a <a href="../notebook/pomdp.html">POMDP</a> solver: the internal state of the system maps to a belief state about the outside world, and the system’s actions are optimal given that belief.</p>
<p>LLM summary follows:</p>
<p>A system <img src="https://latex.codecogs.com/png.latex?S"> is <em>interpretable</em> as a POMDP solver if its internal states map to belief states and its outputs map to policy actions under some POMDP — i.e.&nbsp;its dynamics factor as Bayesian belief-update composed with a value-optimizing policy. The interpretation need not be unique: a single physical system can admit multiple POMDP readings, each with its own implied utility, observation model, and hidden-world structure. The criterion is structural — does the system’s behaviour factor in a POMDP shape — without committing to <em>which</em> POMDP it solves.</p>
<p>This is the formal version of the <a href="../notebook/internal_models.html">good-regulator claim</a> that a well-performing agent contains a model. Biehl &amp; Virgo make “contains a model” a checkable property, and they do it without committing to a utility function — the system is generically agenty, not necessarily a particular agent with particular goals. Bounded compute enters as a restriction on which POMDPs admit interpretations: a finite system can only realize belief-updates and policies whose state spaces fit its capacity.</p>
<p>What it gives us: a candidate computational substrate. The good regulator theorem and bounded rationality both translate into statements about which POMDP solutions a finite agent can realize.</p>
</section>
<section id="computationally-embedded-big-world" class="level3" data-number="4.8">
<h3 data-number="4.8" class="anchored" data-anchor-id="computationally-embedded-big-world"><span class="header-section-number">4.8</span> Computationally-embedded big world</h3>
<p>Some kind of intellectual cousin to the POMDP-as-agent. <span class="citation" data-cites="Lewandowski2025World">Lewandowski et al. (2025)</span> note that an automaton inside a universal computer is <em>implicitly</em> constrained; the bound arises from the fact that it has fewer internal states than its environment does. Their proposed objective, <em>interactivity</em>, measures the agent’s continuing ability to adapt and learn useful predictions despite this.</p>
<p>Somehow they test NN architectures in this setting, I guess in an implicitly <a href="../notebook/continual_learning.html">continuous learning</a> context, and get some empirical results.</p>
<p>LLM summary follows:</p>
<p>The agent is a finite-state automaton with bounded memory and per-step compute. The world is the state of a universal computer evolving under some computable rule. They exchange symbols across an interface. The agent’s boundedness is a consequence of its automaton fitting inside the world’s automaton, not a separately-imposed budget — we are invited to consider this “agent inside a bigger computer” framing, maybe relevant to <a href="../notebook/boundaries_blankets.html">boundaries</a> and <a href="../notebook/multi_scale_agency.html">multi-level agency</a> and all that.</p>
<p><em>Interactivity</em>, their proposed objective, measures the agent’s continuing capacity to acquire useful new predictions about the world’s dynamics as time goes on. This is the Big-World twist: there is no terminal “I have learned the environment” state, and the right objective is <em>sustained</em> learnability rather than convergence to a stationary optimum. RL’s usual maximize-and-stop framing is the wrong shape for an agent in this regime.</p>
<p>They even have an empirical finding! Expressive nonlinear networks, despite their nominal capacity advantage, lose interactivity as they scale — their representations collapse onto fixed features and they stop generating new predictions. Linear networks scale more gracefully on the same metric. Architectural choices that look like “more capability” under maximize-and-stop may be exactly the wrong choices for an agent that has to keep learning. Whatever bounded agency ends up requiring, “scale up the most expressive network you can afford” is not it.</p>
</section>
<section id="nars-assumption-of-insufficient-knowledge-and-resources" class="level3" data-number="4.9">
<h3 data-number="4.9" class="anchored" data-anchor-id="nars-assumption-of-insufficient-knowledge-and-resources"><span class="header-section-number">4.9</span> NARS / Assumption of Insufficient Knowledge and Resources</h3>
<p>Pei Wang’s NARS programme defines intelligence as adaptation under insufficient knowledge and resources. Already covered in <a href="../notebook/ai_economics_of_cognition.html">the economics of cognition notebook</a>; flagging here because it is philosophically close to the “intelligence <em>is</em> boundedness” argument. It is also vague enough that I cannot work out exactly what they mean.</p>
</section>
<section id="infra-bayesianism" class="level3" data-number="4.10">
<h3 data-number="4.10" class="anchored" data-anchor-id="infra-bayesianism"><span class="header-section-number">4.10</span> Infra-Bayesianism</h3>
<p><a href="https://www.alignmentforum.org/posts/gHgs2e2J5azvGFatb/infra-bayesian-physicalism-a-formal-theory-of-naturalized?ref=danmackinlay.name">Kosoy and Appel</a> <a href="../notebook/bayes_imprecise.html">replace the Bayesian prior</a> — a single measure over environments — with a convex set of sub-probability measures (an <em>infradistribution</em>, or in the imprecise-probability vocabulary, a credal set of sa-measures). The agent decides by minimax: evaluate each policy against the worst-case measure in the set.</p>
<p><em>Prima facie</em> this gives us nothing particularly bounded, but I am told that I might find such things if I explore <a href="../notebook/bayes_imprecise.html">Infra-Bayesian physicalism</a>.</p>
</section>
<section id="god-help-us-predictive-coding" class="level3" data-number="4.11">
<h3 data-number="4.11" class="anchored" data-anchor-id="god-help-us-predictive-coding"><span class="header-section-number">4.11</span> God help us, predictive coding</h3>
<p>The Friston-style <a href="../notebook/predictive_coding.html">free-energy / predictive-coding</a> tradition is in this neighbourhood, AFAICT. Active inference is, viewed from outside, a particular package of internal-model + KL-constrained policy + thermodynamic substrate. From the outside it looks like a natural approach, but I think something must be wrong with the definitions or framing, because empirically no one can agree on what this theory is “about”. I am hopeful that I will accidentally rediscover the “good bits” of the free-energy principle by proceeding without it.</p>
</section>
<section id="free-energy-and-active-inference-in-multi-agent-models" class="level3" data-number="4.12">
<h3 data-number="4.12" class="anchored" data-anchor-id="free-energy-and-active-inference-in-multi-agent-models"><span class="header-section-number">4.12</span> Free energy and active inference in multi-agent models</h3>
<p><span class="citation" data-cites="Hyland2024FreeEnergy">Hyland et al. (2024)</span> proposes modelling interactions between boundedly-rational agents as free-energy equilibria. <span class="citation" data-cites="Walters2025Free">Walters et al. (2025)</span> applies free-energy risk metrics to multi-agent systems.The active-inference framing says each agent minimizes its own free energy (surprise), and the question becomes: under what conditions does a collection of surprise-minimizing agents produce an aggregate that is itself low-surprise in a useful sense? <span class="citation" data-cites="Crutchfield2025Agentic">Crutchfield and Jurgens (2025)</span> on “agentic information theory” — intrinsic semantics of information processes approaches the same question. 🚧TODO🚧</p>
</section>
</section>
<section id="embedded-bounded-optimality" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="embedded-bounded-optimality"><span class="header-section-number">5</span> Embedded bounded optimality</h2>
<p>A colleague points out that saying “bounded space/time” alone doesn’t rule out all the ways to cheat. Here are some things that really should not pass the admissibility conditions.</p>
<p>The first impostor is an <em>oracle criterion</em>. A framework can bound its agent’s compute and then judge the agent against a quantity no such agent could compute from its own data. “True risk on the open-world distribution” is an archetype: a finite scalar, cheap to evaluate given the true measure, so no resource bound excludes it — but it is not a function of the <em>sample</em>, but of the <em>world</em> and no embedded agent can evaluate it at any budget. The agent is bounded; the standard it is judged by is not. The Good–Turing estimator <span class="citation" data-cites="Good1953Population">(Good 1953)</span>, by contrast, passes easily because its formula (<img src="https://latex.codecogs.com/png.latex?N_1/N">) is nothing but the sample. The condition that bars this impostor — the criterion must be a <em>statistic of observable data</em> — gets its worked development in inference in an open world.</p>
<p>The second impostor is the <em>outside standpoint</em>. A framework can bound its agent’s compute, feed it nothing but statistics, and still quantify over a hypothesis class that could not contain the agent doing the reasoning. <a href="../notebook/aixi.html">AIXI</a> demonstrates an egregiously infinite version of this: being uncomputable, it is not a member of the class of computable environments it sums over, so it cannot model an agent like itself, including itself. This problem can occur at finite agent “size” also — a bounded agent whose environment class is capped below its own size smuggles in the same standpoint. This is the concern that <em>space-time embedded intelligence</em> <span class="citation" data-cites="Orseau2012SpaceTime">(Orseau and Ring 2012)</span>, <em>reflective-oracle</em> <span class="citation" data-cites="Fallenstein2015Reflective">(Fallenstein, Taylor, and Christiano 2015)</span> and <em>logical-induction</em> <span class="citation" data-cites="Garrabrant2020Logical">(Garrabrant et al. 2020)</span> lines of work chase. The criterion must be <em>self-contained in its own hypothesis class</em> — this is discussed in the reactive notebook, and the Cantor trap below rants about it at length.</p>
<p>So the full criterion is a conjunction of three admissibility conditions: <em>resource-bounded computable</em> ∧ <em>a statistic of observable data</em> ∧ <em>self-contained in its own hypothesis class</em>. The first conjunct is “bounded space/time” itself: the quantity a framework’s criterion evaluates must be computable within the agent’s resource bounds — ideally anytime/online, at bounded per-step cost — and feasibly so, not merely finitely. Computable-but-intractable does not seem enough; that is the verdict on AIXI<em>tl</em> below. A procedure that needs many copies of the world held in memory — posterior sampling via <a href="../notebook/mcmc.html">MCMC</a> over all possible worlds — is out, and infra-Bayesianism is a borderline case, because reasoning over the envelope set may not be feasibly bounded any more than posterior samples. This conjunct is not a scruple; there are theorems about what it costs. The statistical–computational-gap literature studies estimation problems in which the statistically optimal estimator is intractable, so every feasible estimator pays a provable premium in error or sample size — the resource bound changes what is achievable, not merely how long it takes. Algorithmic robust statistics <span class="citation" data-cites="Diakonikolas2023Algorithmic">(Diakonikolas and Kane 2023)</span> is a worked instance: under Huber’s contamination model the classical optimal estimators, such as the Tukey median, are NP-hard in high dimension, and the field charts what error a polynomial-time estimator can attain instead.</p>
<p>The three conditions are independent; named systems fail different subsets. AIXI passes the statistic condition and fails the other two; AIXI<em>tl</em> is computable, hence a possible inhabitant of a computable world — self-containment restored while feasibility still fails. Schmidhuber’s Speed Prior <span class="citation" data-cites="Schmidhuber2002Speed">(Schmidhuber 2002)</span> repairs the uncomputability without reaching feasibility, and turns the self-containment failure into at least an open question; the audit of that family is in <a href="../notebook/aixi.html">the AIXI notebook</a>.</p>
<p>If we want one name for the conjunction, the literature already has one: <em>bounded optimality</em> in Russell and Subramanian’s sense <span class="citation" data-cites="Russell1995Provably Russell1997Rationality">(S. J. Russell and Subramanian 1995; Stuart J. Russell 1997)</span> — optimality relativized to programs actually runnable on the agent’s own machine — with the observability and self-containment conditions making it <em>embedded</em> bounded optimality rather than merely resource-rational. The surrounding normative literature — Simon’s bounded rationality <span class="citation" data-cites="Simon1955Behavioral Simon1956Rational">(Simon 1955, 1956)</span>, computational rationality <span class="citation" data-cites="Gershman2015Computational">(Gershman, Horvitz, and Tenenbaum 2015)</span>, resource-rational analysis <span class="citation" data-cites="Lieder2020Resourcerational">(Lieder and Griffiths 2020)</span>, logical induction for the special case of uncertainty about computations we cannot afford to run — is the frame that turns “bounded space/time” from a lament into a criterion.</p>
</section>
<section id="cantor-trap" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="cantor-trap"><span class="header-section-number">6</span> Cantor trap</h2>
<p>The thought experiment in the opening — <em>give the demon complete information and unlimited compute and let it simulate the universe</em> — articulates a weird bias in the literature that I had not found words for. We systematically ignore that our cognitive demon needs <em>somewhere to put the simulation.</em></p>
<p>Wolpert’s stochastic thermodynamics of computation <span class="citation" data-cites="Wolpert2019Stochastic Wolpert2026What">(David H. Wolpert 2019; David H. Wolpert and Korbel 2026)</span> is one attempt to price the compute. Any logical operation has a thermodynamic cost; any physical computer is itself a dynamical system whose computation we identify by mapping its dynamics onto an abstract machine. A simulator of the entire universe is, at minimum, a physical system as complex as the universe being simulated — the exact lower bound depends on the dynamics, but it is not zero. So a Laplacian demon needs a <em>bigger</em> universe to live in. There is no infinite-compute thought experiment that is internally coherent without dragging in another, larger, world.</p>
<p>Any agent — including the one we use to motivate the unbounded-compute limit — is a physical system competing for the same resources as the world it models. Bounded information, bounded compute is not one option of four; it is the only combination that obtains in the world. The other three are interesting idealizations — <em>projections</em> of bounded agents in which we let things grow to infinity that can never do so. They are not realizable, and moreover it is not clear to me that we ever get close enough to any of them that they are even a good approximation.</p>
<p>The bias in the <a href="../notebook/agency_embedded.html">embedded agency</a> and broader <a href="../notebook/agent_foundations.html">agent foundations</a> literature is what I like to call a <em>Cantor trap</em>: treating an unrealizable combination of compute or data as a place where one can stand and reason, and then “approximating down” from it. The trap looks like progress because Cantorian objects — countably-infinite hypothesis classes, ω-limits, transfinite hierarchies of agents — are mathematically clean and aesthetically seductive (Hilbert’s paradise from which no one shall expel us!).</p>
<p>In practice, the infinite limit seems to me more like a vanishing point. The diagonalization puzzles flagged in bounded inductive rationality — Löb, 5-and-10, spurious counterfactuals — are indicators of the same trap: this is what happens when one tries to make an agent’s reasoning-in-its-entirety the topic of its reasoning. In the vocabulary of the admissibility conditions, this whole section is the self-containment condition at essay length — the Cantor trap is what standing outside our own hypothesis class looks like from inside. Infra-Bayesianism seems to escape some of the diagonalization weirdness by introducing a new type of imprecision — although it still needs unbounded compute, which is still impractical. Infinities are cool, ngl, but we need to know how informative they are about finite agents before we use ’em.</p>
<p>A particular annoyance: my <a href="../notebook/agency_embedded.html">embedded agency</a> notebook was my attempt to understand careful, technically polished work on reflective stability, logical counterfactuals over the agent’s own actions, and value-stability under self-modification — exactly the problems the Cantor trap generates. From the bounded-agents perspective, the same questions look different: some dissolve, others survive but in milder versions, about <em>capacity</em> and <em>prices on uncertainty</em> rather than impossible self-knowledge. AFAICT a tradition with this much technical care has produced surprisingly little to help us reason about agents that are “like us” in this important way.</p>
<p><span class="citation" data-cites="Sole2024Fundamental">Solé et al. (2024)</span> line up adjacent constraints from the biology side — what living systems can and cannot be, given their thermodynamic and informational budgets. Useful as a sanity check that the substrate principle shows up empirically in the systems we already call agents.</p>
<p>Future-me, have you read all these papers yet? One tempting next step is a <em>specific</em> lower bound on a model’s complexity in terms of the agent’s thermodynamic budget. Is that done?</p>
</section>
<section id="where-next" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="where-next"><span class="header-section-number">7</span> Where next</h2>
<p>Chatbot suggests:</p>
<ol type="1">
<li>Read <span class="citation" data-cites="Lewandowski2025World">Lewandowski et al. (2025)</span> and <span class="citation" data-cites="Baltieri2025Bayesian">Baltieri et al. (2025)</span> carefully and write the diff. Both are about embedded agents containing models; one is information-theoretic and asymptotic, the other categorical and structural. There may be a tidy correspondence.</li>
<li>Pick one toy problem — a satisficing LQR, or a tree-search agent with anytime stopping — and run the resource-rational, bounded-optimal, and KL-constrained derivations side-by-side. Verify that they tell the same story, or notice where they diverge.</li>
<li>Promote the substrate principle (and the Cantor trap with it) to a standalone post; the ingredients above are enough for one.</li>
<li>Sketch what a “good regulator theorem under a thermodynamic budget” would look like, even at one page. Surely this must be done? I have not read All the Things yet.</li>
</ol>
<div class="callout callout-style-simple callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Credit
</div>
</div>
<div class="callout-body-container callout-body">
<p>This notebook didn’t come from nowhere; I have been writing this to process ideas that I have been discussing with my colleagues. I am especially indebted to Martin Biehl, <a href="https://artemyk.github.io?ref=danmackinlay.name">Artemy Kolchinsky</a>, <a href="https://aclevername.substack.com?ref=danmackinlay.name">Chris Pang</a>, <a href="https://boundedlyrational.substack.com?ref=danmackinlay.name">Jan Kulveit</a> and <a href="https://www.tomeveritt.se?ref=danmackinlay.name">Tom Everitt</a> who said helpful things about these concepts.</p>
</div>
</div>
</section>
<section id="incoming" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="incoming"><span class="header-section-number">8</span> Incoming</h2>
<ul>
<li>Tishby &amp; Polani’s information theory of the perception–action cycle <span class="citation" data-cites="Tishby2011Information">(Tishby and Polani 2011)</span> — close to Ortega-Braun, but starts from the cycle. Is this just totally <a href="../notebook/informational_empowerment.html">informational empowerment though</a>?</li>
<li>Ho et al.&nbsp;on simplified mental representations for planning <span class="citation" data-cites="Ho2022People">(Ho et al. 2022)</span> — empirical evidence that humans plan with a deliberately impoverished model.</li>
<li>Gigerenzer &amp; Goldstein on fast-and-frugal heuristics <span class="citation" data-cites="Gigerenzer1996Reasoning">(Gigerenzer and Goldstein 1996)</span> — anchor for the descriptive <a href="../notebook/bounded_rationality.html">bounded-rationality literature based on humans.</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Abel2025Agencya" class="csl-entry">
Abel, Barreto, Bowling, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2502.04403">Agency Is Frame-Dependent</a>.”</span>
</div>
<div id="ref-Armstrong2016Racing" class="csl-entry">
Armstrong, Bostrom, and Shulman. 2016. <span>“<a href="https://doi.org/10.1007/s00146-015-0590-y">Racing to the Precipice: A Model of Artificial Intelligence Development</a>.”</span> <em>AI &amp; Society</em>.
</div>
<div id="ref-Arumugam2024Bayesianb" class="csl-entry">
Arumugam, Ho, Goodman, et al. 2024. <span>“<a href="https://doi.org/10.1162/opmi_a_00132">Bayesian Reinforcement Learning With Limited Cognitive Load</a>.”</span> <em>Open Mind : Discoveries in Cognitive Science</em>.
</div>
<div id="ref-Arumugam2021Deciding" class="csl-entry">
Arumugam, and Roy. 2021. <span>“<a href="http://arxiv.org/abs/2101.06197">Deciding What to Learn: A Rate-Distortion Approach</a>.”</span> In <em>Proceedings of the 38th International Conference on Machine Learning</em>.
</div>
<div id="ref-Arumugam2021Value" class="csl-entry">
Arumugam, and Van Roy. 2021. <span>“<a href="https://proceedings.neurips.cc/paper_files/paper/2021/hash/517da335fd0ec2f4a25ea139d5494163-Abstract.html">The Value of Information When Deciding What to Learn</a>.”</span> In <em>Advances in Neural Information Processing Systems</em>.
</div>
<div id="ref-Arumugam2022Decidinga" class="csl-entry">
———. 2022. <span>“<a href="https://doi.org/10.52202/068431-0656">Deciding What to Model: Value-Equivalent Sampling for Reinforcement Learning</a>.”</span> In <em>Advances in Neural Information Processing Systems</em>.
</div>
<div id="ref-Avenhaus2002Inspection" class="csl-entry">
Avenhaus, von Stengel, and Zamir. 2002. <span>“<a href="https://doi.org/10.1016/S1574-0005(02)03014-X">Inspection Games</a>.”</span> In <em>Handbook of Game Theory with Economic Applications</em>.
</div>
<div id="ref-Ay2008Information" class="csl-entry">
Ay, and Polani. 2008. <span>“<a href="https://doi.org/10.1142/S0219525908001465">Information Flows in Causal Networks</a>.”</span> <em>Advances in Complex Systems</em>.
</div>
<div id="ref-Balestriero2025LeJEPA" class="csl-entry">
Balestriero, and LeCun. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.08544">LeJEPA: Provable and Scalable Self-Supervised Learning Without the Heuristics</a>.”</span>
</div>
<div id="ref-Baltieri2025Bayesian" class="csl-entry">
Baltieri, Biehl, Capucci, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2503.00511">A Bayesian Interpretation of the Internal Model Principle</a>.”</span>
</div>
<div id="ref-Baltieri2025Mathematicala" class="csl-entry">
Baltieri, and Suzuki. 2025. <span>“<a href="https://doi.org/10.31234/osf.io/rqu7s_v2">Mathematical Approaches to the Study of Agents</a>.”</span>
</div>
<div id="ref-Beckers2019Abstracting" class="csl-entry">
Beckers, and Halpern. 2019. <span>“<a href="https://doi.org/10.1609/aaai.v33i01.33012678">Abstracting Causal Models</a>.”</span> <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Bhui2021Resourcerational" class="csl-entry">
Bhui, Lai, and Gershman. 2021. <span>“<a href="https://hdl.handle.net/1721.1/144282">Resource-Rational Decision Making</a>.”</span>
</div>
<div id="ref-Biehl2023Interpreting" class="csl-entry">
Biehl, and Virgo. 2023. <span>“<a href="https://doi.org/10.1007/978-3-031-28719-0_2">Interpreting Systems as Solving POMDPs: A Step Towards a Formal Understanding of Agency</a>.”</span> In.
</div>
<div id="ref-Bigelow2024Forking" class="csl-entry">
Bigelow, Holtzman, Tanaka, et al. 2024. <span>“Forking Paths in Neural Text Generation.”</span>
</div>
<div id="ref-Bigelow2026Forking" class="csl-entry">
Bigelow, Zur, Grant, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2608.19611">Forking Fast: Efficiently Estimating Uncertainty Dynamics in Text Generation</a>.”</span>
</div>
<div id="ref-Camara2021Computationally" class="csl-entry">
Camara. 2021. <span>“Computationally Tractable Choice.”</span>
</div>
<div id="ref-CesaBianchi2006Prediction" class="csl-entry">
Cesa-Bianchi, and Lugosi. 2006. <em><a href="https://doi.org/10.1017/CBO9780511546921">Prediction, Learning, and Games</a></em>.
</div>
<div id="ref-Collet2013Quasistationary" class="csl-entry">
Collet, Martínez, and San Martín. 2013. <em><a href="https://doi.org/10.1007/978-3-642-33131-2">Quasi-Stationary Distributions: Markov Chains, Diffusions and Dynamical Systems</a></em>. Probability and Its Applications.
</div>
<div id="ref-Collins2024Building" class="csl-entry">
Collins, Sucholutsky, Bhatt, et al. 2024. <span>“<a href="https://doi.org/10.1038/s41562-024-01991-9">Building Machines That Learn and Think with People</a>.”</span> <em>Nature Human Behaviour</em>.
</div>
<div id="ref-Conant1970Every" class="csl-entry">
Conant, and Ashby. 1970. <span>“<a href="https://doi.org/10.1080/00207727008920220">Every Good Regulator of a System Must Be a Model of That System</a>.”</span> <em>International Journal of Systems Science</em>.
</div>
<div id="ref-Crutchfield2025Agentic" class="csl-entry">
Crutchfield, and Jurgens. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2505.19275">Agentic Information Theory: Ergodicity and Intrinsic Semantics of Information Processes</a>.”</span>
</div>
<div id="ref-Dakos2012Methods" class="csl-entry">
Dakos, Carpenter, Brock, et al. 2012. <span>“<a href="https://doi.org/10.1371/journal.pone.0041010">Methods for Detecting Early Warnings of Critical Transitions in Time Series Illustrated Using Simulated Ecological Data</a>.”</span> <em>PLoS ONE</em>.
</div>
<div id="ref-Darroch1965Quasistationary" class="csl-entry">
Darroch, and Seneta. 1965. <span>“<a href="https://doi.org/10.2307/3211876">On Quasi-Stationary Distributions in Absorbing Discrete-Time Finite Markov Chains</a>.”</span> <em>Journal of Applied Probability</em>.
</div>
<div id="ref-Diakonikolas2016Robust" class="csl-entry">
Diakonikolas, Kamath, Kane, et al. 2016. <span>“<a href="http://arxiv.org/abs/1604.06443">Robust Estimators in High Dimensions Without the Computational Intractability</a>.”</span> <em>arXiv:1604.06443 [Cs, Math, Stat]</em>.
</div>
<div id="ref-Diakonikolas2017Being" class="csl-entry">
Diakonikolas, Kamath, Kane, et al. 2017. <span>“<a href="http://arxiv.org/abs/1703.00893">Being Robust (in High Dimensions) Can Be Practical</a>.”</span> <em>arXiv:1703.00893 [Cs, Math, Stat]</em>.
</div>
<div id="ref-Diakonikolas2023Algorithmic" class="csl-entry">
Diakonikolas, and Kane. 2023. <em><a href="https://doi.org/10.1017/9781108943161">Algorithmic High-Dimensional Robust Statistics</a></em>.
</div>
<div id="ref-EleutherAI2026Dynamical" class="csl-entry">
EleutherAI. 2026. <span>“<a href="https://blog.eleuther.ai/dynamical-models-of-ai-governability/">A Dynamical Model of AI Governability</a>.”</span>
</div>
<div id="ref-England2015Dissipative" class="csl-entry">
England. 2015. <span>“<a href="https://doi.org/10.1038/nnano.2015.250">Dissipative Adaptation in Driven Self-Assembly</a>.”</span> <em>Nature Nanotechnology</em>.
</div>
<div id="ref-E2006Towards" class="csl-entry">
E, and Vanden-Eijnden. 2006. <span>“<a href="https://doi.org/10.1007/s10955-005-9003-9">Towards a Theory of Transition Paths</a>.”</span> <em>Journal of Statistical Physics</em>.
</div>
<div id="ref-Fallenstein2015Reflective" class="csl-entry">
Fallenstein, Taylor, and Christiano. 2015. <span>“<a href="https://doi.org/10.48550/arXiv.1508.04145">Reflective Oracles: A Foundation for Classical Game Theory</a>.”</span>
</div>
<div id="ref-Fearon1995Rationalist" class="csl-entry">
Fearon. 1995. <span>“<a href="https://doi.org/10.1017/S0020818300033324">Rationalist Explanations for War</a>.”</span> <em>International Organization</em>.
</div>
<div id="ref-Finzi2026Entropy" class="csl-entry">
Finzi, Qiu, Jiang, et al. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2601.03220">From Entropy to Epiplexity: Rethinking Information for Computationally Bounded Intelligence</a>.”</span>
</div>
<div id="ref-Fish2026Generative" class="csl-entry">
Fish, Gölz, Parkes, et al. 2026. <span>“<a href="https://doi.org/10.1145/3799709">Generative Social Choice</a>.”</span> <em>Journal of the ACM</em>.
</div>
<div id="ref-Francis1976Internal" class="csl-entry">
Francis, and Wonham. 1976. <span>“<a href="https://doi.org/10.1016/0005-1098(76)90006-6">The Internal Model Principle of Control Theory</a>.”</span> <em>Automatica</em>.
</div>
<div id="ref-Garrabrant2020Logical" class="csl-entry">
Garrabrant, Benson-Tilsen, Critch, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.1609.03543">Logical Induction</a>.”</span>
</div>
<div id="ref-Gershman2015Computational" class="csl-entry">
Gershman, Horvitz, and Tenenbaum. 2015. <span>“<a href="https://doi.org/10.1126/science.aac6076">Computational Rationality: A Converging Paradigm for Intelligence in Brains, Minds, and Machines</a>.”</span> <em>Science</em>.
</div>
<div id="ref-Gigerenzer1996Reasoning" class="csl-entry">
Gigerenzer, and Goldstein. 1996. <span>“<a href="https://doi.org/10.1037/0033-295X.103.4.650">Reasoning the Fast and Frugal Way: Models of Bounded Rationality</a>.”</span> <em>Psychological Review</em>.
</div>
<div id="ref-Good1953Population" class="csl-entry">
Good. 1953. <span>“<a href="https://doi.org/10.2307/2333344">The Population Frequencies of Species and the Estimation of Population Parameters</a>.”</span> <em>Biometrika</em>.
</div>
<div id="ref-Greenblatt2024AI" class="csl-entry">
Greenblatt, Shlegeris, Sachan, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2312.06942">AI Control: Improving Safety Despite Intentional Subversion</a>.”</span>
</div>
<div id="ref-HadfieldMenell2017Offswitch" class="csl-entry">
Hadfield-Menell, Dragan, Abbeel, et al. 2017. <span>“<a href="https://doi.org/10.24963/ijcai.2017/32">The Off-Switch Game</a>.”</span> In <em>Proceedings of the Twenty-Sixth International Joint Conference on Artificial Intelligence (IJCAI)</em>.
</div>
<div id="ref-Hafner2022Action" class="csl-entry">
Hafner, Ortega, Ba, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2009.01791">Action and Perception as Divergence Minimization</a>.”</span>
</div>
<div id="ref-Hahn2022Resourcerational" class="csl-entry">
Hahn, Futrell, Levy, et al. 2022. <span>“<a href="https://doi.org/10.1073/pnas.2122602119">A Resource-Rational Model of Human Processing of Recursive Linguistic Structure</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Hay2012Selecting" class="csl-entry">
Hay, Russell, Tolpin, et al. 2012. <span>“<a href="https://doi.org/10.48550/arXiv.1408.2048">Selecting Computations: Theory and Applications</a>.”</span> In <em>Proceedings of the Twenty-Eighth Conference on Uncertainty in Artificial Intelligence</em>.
</div>
<div id="ref-Hirshleifer1971Private" class="csl-entry">
Hirshleifer. 1971. <span>“The Private and Social Value of Information and the Reward to Inventive Activity.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Ho2022People" class="csl-entry">
Ho, Abel, Correa, et al. 2022. <span>“<a href="https://doi.org/10.1038/s41586-022-04743-9">People Construct Simplified Mental Representations to Plan</a>.”</span> <em>Nature</em>.
</div>
<div id="ref-Huang2018Internal" class="csl-entry">
Huang, Isidori, Marconi, et al. 2018. <span>“<a href="https://doi.org/10.1109/CDC.2018.8619624">Internal Models in Control, Biology and Neuroscience</a>.”</span> In <em>2018 IEEE Conference on Decision and Control (CDC)</em>.
</div>
<div id="ref-Hu2026Dissociative" class="csl-entry">
Hu, Rong, and Van Kleek. 2026. <span>“<a href="https://doi.org/10.1145/3805689.3806748">Dissociative Identity: Language Model Agents Lack Grounding for Reputation Mechanisms</a>.”</span> In <em>Proceedings of the ACM Conference on Fairness, Accountability, and Transparency (FAccT)</em>.
</div>
<div id="ref-Hyland2025Variational" class="csl-entry">
Hyland, and Albarracin. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2509.17957">On the Variational Costs of Changing Our Minds</a>.”</span>
</div>
<div id="ref-Hyland2024FreeEnergy" class="csl-entry">
Hyland, Gavenčiak, Costa, et al. 2024. <span>“<a href="https://openreview.net/forum?id=4Ft7DcrjdO">Free-Energy Equilibria: Toward a Theory of Interactions Between Boundedly-Rational Agents</a>.”</span> In.
</div>
<div id="ref-Jorgensen2026Causal" class="csl-entry">
Jørgensen, Weichwald, and Hammond. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2605.00248">Causal Foundations of Collective Agency</a>.”</span>
</div>
<div id="ref-Kolchinsky2018Semantic" class="csl-entry">
Kolchinsky, and Wolpert. 2018. <span>“<a href="https://doi.org/10.1098/rsfs.2018.0041">Semantic Information, Autonomous Agency and Non-Equilibrium Statistical Physics</a>.”</span> <em>Interface Focus</em>.
</div>
<div id="ref-Ko2026Cliff" class="csl-entry">
Ko, Lee, Kang, et al. 2026. <span>“Cliff Tokens: Analyzing Failure Trigger Tokens in LLM Mathematical Reasoning.”</span>
</div>
<div id="ref-Koutsougeras1993Incentive" class="csl-entry">
Koutsougeras, and Yannelis. 1993. <span>“<a href="https://doi.org/10.1007/BF01212914">Incentive Compatibility and Information Superiority of the Core of an Economy with Differential Information</a>.”</span> <em>Economic Theory</em>.
</div>
<div id="ref-Kreps1982Rational" class="csl-entry">
Kreps, Milgrom, Roberts, et al. 1982. <span>“<a href="https://doi.org/10.1016/0022-0531(82)90029-1">Rational Cooperation in the Finitely Repeated Prisoners’ Dilemma</a>.”</span> <em>Journal of Economic Theory</em>.
</div>
<div id="ref-Kreps1982Reputation" class="csl-entry">
Kreps, and Wilson. 1982. <span>“<a href="https://doi.org/10.1016/0022-0531(82)90030-8">Reputation and Imperfect Information</a>.”</span> <em>Journal of Economic Theory</em>.
</div>
<div id="ref-Kulveit2025Gradual" class="csl-entry">
Kulveit, Douglas, Ammann, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2501.16946">Gradual Disempowerment: Systemic Existential Risks from Incremental AI Development</a>.”</span>
</div>
<div id="ref-Leike2016Formal" class="csl-entry">
Leike, Taylor, and Fallenstein. 2016. <span>“<a href="https://doi.org/10.48550/arXiv.1609.05058">A Formal Solution to the Grain of Truth Problem</a>.”</span>
</div>
<div id="ref-Levin1973Universal" class="csl-entry">
Levin. 1973. <span>“<a href="https://lance.fortnow.com/papers/files/Levin%20Universal%20with%20Proofs.pdf">Universal Sequential Search Problems</a>.”</span> <em>Problemy Peredachi Informatsii</em>.
</div>
<div id="ref-Levine2024Resourcerational" class="csl-entry">
Levine, Chater, Tenenbaum, et al. 2024. <span>“<a href="https://doi.org/10.1017/S0140525X24001067">Resource-Rational Contractualism: A Triple Theory of Moral Cognition</a>.”</span> <em>Behavioral and Brain Sciences</em>.
</div>
<div id="ref-Lewandowski2025World" class="csl-entry">
Lewandowski, Ramesh, Meyer, et al. 2025. <span>“<a href="https://openreview.net/forum?id=gJclyLFSdU">The World Is Bigger: A Computationally-Embedded Perspective on the Big World Hypothesis</a>.”</span> In.
</div>
<div id="ref-Lieder2020Resourcerational" class="csl-entry">
Lieder, and Griffiths. 2020. <span>“<a href="https://doi.org/10.1017/S0140525X1900061X">Resource-Rational Analysis: Understanding Human Cognition as the Optimal Use of Limited Computational Resources</a>.”</span> <em>Behavioral and Brain Sciences</em>.
</div>
<div id="ref-Lombrozo2024Learning" class="csl-entry">
Lombrozo. 2024. <span>“<a href="https://doi.org/10.1016/j.tics.2024.07.007">Learning by Thinking in Natural and Artificial Minds</a>.”</span> <em>Trends in Cognitive Sciences</em>.
</div>
<div id="ref-Macar2025Thought" class="csl-entry">
Macar, Bogdan, Rajamanoharan, et al. 2025. <span>“<a href="https://openreview.net/forum?id=dkwVlQ0s7b">Thought Branches: Interpreting LLM Reasoning Requires Resampling</a>.”</span> In.
</div>
<div id="ref-Meleard2012Quasistationary" class="csl-entry">
Méléard, and Villemonais. 2012. <span>“<a href="https://doi.org/10.1214/11-PS191">Quasi-Stationary Distributions and Population Processes</a>.”</span> <em>Probability Surveys</em>.
</div>
<div id="ref-Metzner2009Transition" class="csl-entry">
Metzner, Schütte, and Vanden-Eijnden. 2009. <span>“<a href="https://doi.org/10.1137/070699500">Transition Path Theory for Markov Jump Processes</a>.”</span> <em>Multiscale Modeling &amp; Simulation</em>.
</div>
<div id="ref-Oesterheld2023Theory" class="csl-entry">
Oesterheld, Demski, and Conitzer. 2023. <span>“<a href="https://doi.org/10.4204/EPTCS.379.33">A Theory of Bounded Inductive Rationality</a>.”</span> <em>Electronic Proceedings in Theoretical Computer Science</em>.
</div>
<div id="ref-Orseau2018Agents" class="csl-entry">
Orseau, McGill, and Legg. 2018. <span>“<a href="https://doi.org/10.48550/arXiv.1805.12387">Agents and Devices: A Relative Definition of Agency</a>.”</span>
</div>
<div id="ref-Orseau2011SelfModification" class="csl-entry">
Orseau, and Ring. 2011. <span>“<a href="https://doi.org/10.1007/978-3-642-22887-2_1">Self-Modification and Mortality in Artificial Agents</a>.”</span> In <em>Artificial General Intelligence</em>. Lecture Notes in Computer Science.
</div>
<div id="ref-Orseau2012SpaceTime" class="csl-entry">
———. 2012. <span>“<a href="https://doi.org/10.1007/978-3-642-35506-6_22">Space-Time Embedded Intelligence</a>.”</span> In <em>Artificial General Intelligence</em>. Lecture Notes in Computer Science.
</div>
<div id="ref-Ortega2011Information" class="csl-entry">
Ortega, Pedro Alejandro, and Braun. 2011. <span>“<a href="https://doi.org/10.1007/978-3-642-22887-2_28">Information, Utility and Bounded Rationality</a>.”</span> In <em>Proceedings of the 4th International Conference on Artificial General Intelligence</em>. AGI’11.
</div>
<div id="ref-Ortega2013Thermodynamics" class="csl-entry">
Ortega, Pedro A., and Braun. 2013. <span>“<a href="https://doi.org/10.1098/rspa.2012.0683">Thermodynamics as a Theory of Decision-Making with Information-Processing Costs</a>.”</span> <em>Proceedings of the Royal Society A: Mathematical, Physical and Engineering Sciences</em>.
</div>
<div id="ref-Oulasvirta2022Computational" class="csl-entry">
Oulasvirta, Jokinen, and Howes. 2022. <span>“<a href="https://doi.org/10.1145/3491102.3517739">Computational Rationality as a Theory of Interaction</a>.”</span> In <em>CHI Conference on Human Factors in Computing Systems</em>.
</div>
<div id="ref-Overman2026Oversight" class="csl-entry">
Overman, and Bayati. 2026. <span>“The Oversight Game: Learning to Cooperatively Balance an AI Agent’s Safety and Autonomy.”</span> In <em>Proceedings of the 43rd International Conference on Machine Learning (ICML)</em>. Proceedings of Machine Learning Research.
</div>
<div id="ref-Plaut2025Safe" class="csl-entry">
Plaut, Liévano-Karim, Zhu, et al. 2025. <span>“Safe Learning Under Irreversible Dynamics via Asking for Help.”</span>
</div>
<div id="ref-Poor2008Quickest" class="csl-entry">
Poor, and Hadjiliadis. 2008. <em><a href="https://doi.org/10.1017/CBO9780511754678">Quickest Detection</a></em>.
</div>
<div id="ref-Powell2006War" class="csl-entry">
Powell. 2006. <span>“<a href="https://doi.org/10.1017/S0020818306060061">War as a Commitment Problem</a>.”</span> <em>International Organization</em>.
</div>
<div id="ref-Rubenstein2017Causal" class="csl-entry">
Rubenstein, Weichwald, Bongers, et al. 2017. <span>“<a href="http://arxiv.org/abs/1707.00819">Causal Consistency of Structural Equation Models</a>.”</span> In <em>Uncertainty in Artificial Intelligence</em>.
</div>
<div id="ref-Russell1997Rationality" class="csl-entry">
Russell, Stuart J. 1997. <span>“<a href="https://doi.org/10.1016/S0004-3702(97)00026-X">Rationality and Intelligence</a>.”</span> <em>Artificial Intelligence</em>, Economic Principles of Multi-Agent Systems,.
</div>
<div id="ref-Russell1995Provably" class="csl-entry">
Russell, S. J., and Subramanian. 1995. <span>“<a href="https://doi.org/10.1613/jair.133">Provably Bounded-Optimal Agents</a>.”</span> <em>Journal of Artificial Intelligence Research</em>.
</div>
<div id="ref-Russell1991Principles" class="csl-entry">
Russell, Stuart, and Wefald. 1991. <span>“<a href="https://doi.org/10.1016/0004-3702(91)90015-C">Principles of Metareasoning</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Schlee2001Value" class="csl-entry">
Schlee. 2001. <span>“<a href="https://doi.org/10.1257/aer.91.3.509">The Value of Information in Efficient Risk-Sharing Arrangements</a>.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Schmidhuber2002Speed" class="csl-entry">
Schmidhuber. 2002. <span>“<a href="https://doi.org/10.1007/3-540-45435-7_15">The Speed Prior: A New Simplicity Measure Yielding Near-Optimal Computable Predictions</a>.”</span> In <em>Proceedings of the 15th Annual Conference on Computational Learning Theory</em>.
</div>
<div id="ref-Sheta2026Formal" class="csl-entry">
Sheta. 2026. <span>“A Formal Framework for Critical-Mass Collapse in Online Multiplayer Games.”</span>
</div>
<div id="ref-Shiryaev1963Optimum" class="csl-entry">
Shiryaev. 1963. <span>“<a href="https://doi.org/10.1137/1108002">On Optimum Methods in Quickest Detection Problems</a>.”</span> <em>Theory of Probability &amp; Its Applications</em>.
</div>
<div id="ref-Simon1955Behavioral" class="csl-entry">
Simon. 1955. <span>“<a href="https://doi.org/10.2307/1884852">A Behavioral Model of Rational Choice</a>.”</span> <em>The Quarterly Journal of Economics</em>.
</div>
<div id="ref-Simon1956Rational" class="csl-entry">
———. 1956. <span>“<a href="https://doi.org/10.1037/h0042769">Rational Choice and the Structure of the Environment</a>.”</span> <em>Psychological Review</em>.
</div>
<div id="ref-Soares2015Corrigibility" class="csl-entry">
Soares, Fallenstein, Yudkowsky, et al. 2015. <span>“Corrigibility.”</span> In <em>AAAI Workshop on AI and Ethics</em>.
</div>
<div id="ref-Sole2024Fundamental" class="csl-entry">
Solé, Kempes, Corominas-Murtra, et al. 2024. <span>“<a href="https://doi.org/10.1098/rsfs.2024.0010">Fundamental Constraints to the Logic of Living Systems</a>.”</span> <em>Interface Focus</em>.
</div>
<div id="ref-Still2012Thermodynamics" class="csl-entry">
Still, Sivak, Bell, et al. 2012. <span>“<a href="https://doi.org/10.1103/PhysRevLett.109.120604">Thermodynamics of Prediction</a>.”</span> <em>Physical Review Letters</em>.
</div>
<div id="ref-Taylor2025Auditing" class="csl-entry">
Taylor, Black, Bowen, et al. 2025. <span>“Auditing Games for Sandbagging.”</span>
</div>
<div id="ref-Tishby2011Information" class="csl-entry">
Tishby, and Polani. 2011. <span>“Information Theory of Decisions and Actions.”</span> In <em>PERCEPTION-ACTION CYCLE</em>.
</div>
<div id="ref-vanderWeij2024AI" class="csl-entry">
van der Weij, Hofstätter, Jaffe, et al. 2024. <span>“AI Sandbagging: Language Models Can Strategically Underperform on Evaluations.”</span>
</div>
<div id="ref-Walters2025Free" class="csl-entry">
Walters, Kaufmann, Sefas, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2502.04249">Free Energy Risk Metrics for Systemically Safe AI: Gatekeeping Multi-Agent Study</a>.”</span>
</div>
<div id="ref-Wolpert2019Stochastic" class="csl-entry">
Wolpert, David H. 2019. <span>“<a href="https://doi.org/10.1088/1751-8121/ab0850">Stochastic Thermodynamics of Computation</a>.”</span>
</div>
<div id="ref-Wolpert2026What" class="csl-entry">
Wolpert, David H, and Korbel. 2026. <span>“<a href="https://doi.org/10.1088/2632-072X/ae3af8">What Does It Mean for a System to Compute?</a>”</span> <em>Journal of Physics: Complexity</em>.
</div>
<div id="ref-Wu2016Conservative" class="csl-entry">
Wu, Shariff, Lattimore, et al. 2016. <span>“Conservative Bandits.”</span> In <em>Proceedings of the 33rd International Conference on Machine Learning (ICML)</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Briefly summarized in <a href="../notebook/ai_economics_of_cognition.html">the economics of cognition notebook</a> under “rational metareasoning”. The <span class="citation" data-cites="Hay2012Selecting">Hay et al. (2012)</span> meta-MDP is an operationalization.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>adaptive</category>
  <category>agents</category>
  <category>AI safety</category>
  <category>bounded compute</category>
  <category>compsci</category>
  <category>economics</category>
  <category>extended self</category>
  <category>machine learning</category>
  <category>mind</category>
  <category>statmech</category>
  <category>when to compute</category>
  <guid>https://danmackinlay.name/notebook/agency_bounded_compute.html</guid>
  <pubDate>Wed, 29 Apr 2026 10:15:24 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/giant_factotum_RP-P-1985-65.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Production functions</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/production_functions.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-popery-man" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-popery-man-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/popery_man_RP-P-OB-82.077.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/popery_man_RP-P-OB-82.077.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-popery-man-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>I’m annoyed that people use dumb production functions when speculating about AI.</p>
<p>I’m not going to name any names here—you know who you are. But, before you next write about <a href="../notebook/nn_scaling_laws.html">neural scaling</a>, make sure that relations other than <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Boutput%7D%20=%20%5Ctext%7Binput%201%7D%5Ctimes%20%5Ctext%7Binput%202%7D"> can exist between inputs and outputs, or I will be not so much angry as disappointed.</p>
<p>A production function <img src="https://latex.codecogs.com/png.latex?F"> maps inputs to output: <img src="https://latex.codecogs.com/png.latex?Y%20=%20F(K,%20L,%20%5Cdots)">. In practice, we pick one for its analytical convenience, which is dangerous because it smuggles in assumptions. It’s worth checking those before going all-in on something. That said, “choosing not to choose” a production function and defaulting to <img src="https://latex.codecogs.com/png.latex?Y=KL"> is still making a choice; <em>ceteris paribus</em>, a stupid one.</p>
<p>An <em>isoquant</em> is the set of input combinations that produce the same output, <img src="https://latex.codecogs.com/png.latex?%5C%7B(K,%20L)%20:%20F(K,%20L)%20=%20%5Cbar%20Y%5C%7D">, i.e.&nbsp;a contour line of <img src="https://latex.codecogs.com/png.latex?F"> when we plot <img src="https://latex.codecogs.com/png.latex?L"> against <img src="https://latex.codecogs.com/png.latex?K">.</p>
<p><em>Returns to scale</em> describes what happens to output when we scale every input by the same factor <img src="https://latex.codecogs.com/png.latex?t">: <img src="https://latex.codecogs.com/png.latex?F(tK,%20tL)%20=%20t%5Ek%20F(K,%20L)">, with constant returns at <img src="https://latex.codecogs.com/png.latex?k%20=%201"> (double every input, double the output), increasing returns at <img src="https://latex.codecogs.com/png.latex?k%20%3E%201">, and decreasing returns at <img src="https://latex.codecogs.com/png.latex?k%20%3C%201">.</p>
<p>The <em>cost function</em> gives the cheapest way to produce output <img src="https://latex.codecogs.com/png.latex?Y"> when the inputs <img src="https://latex.codecogs.com/png.latex?x%20=%20(x_1,%20%5Cdots,%20x_n)"> have prices <img src="https://latex.codecogs.com/png.latex?w%20=%20(w_1,%20%5Cdots,%20w_n)">:</p>
<p><img src="https://latex.codecogs.com/png.latex?C(w,%20Y)%20=%20%5Cmin_%7Bx%7D%5C,%5C%7B%5C,%20w%20%5Ccdot%20x%20%5C;:%5C;%20F(x)%20%5Cge%20Y%20%5C,%5C%7D."></p>
<p>If <img src="https://latex.codecogs.com/png.latex?F"> is well-behaved (more of any input never hurts, and isoquants are convex), we can recover <img src="https://latex.codecogs.com/png.latex?F"> from <img src="https://latex.codecogs.com/png.latex?C">. The two carry the same information, so we may work with whichever is easier; this is <em>duality</em>. The cost function also determines the input demands: by Shephard’s lemma, <img src="https://latex.codecogs.com/png.latex?%5Cpartial%20C/%5Cpartial%20w_i%20=%20x_i%5E*(w,%20Y)"> is the cost-minimizing quantity of input <img src="https://latex.codecogs.com/png.latex?i">. That makes <img src="https://latex.codecogs.com/png.latex?C"> the usual choice for direct estimation. A firm takes its input prices as given but chooses its input quantities, so a regression on prices suffers less from the endogeneity that hampers a regression on quantities.</p>
<section id="let-us-care-about-elasticity-of-substitution" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="let-us-care-about-elasticity-of-substitution"><span class="header-section-number">1</span> Let us care about elasticity of substitution</h2>
<p>We usually want a production function that allows us to consider the impact of the <em>elasticity of substitution</em> <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%20%5Cdfrac%7Bd%5Clog(K/L)%7D%7Bd%5Clog(%5Ctext%7BMRTS%7D)%7D">.</p>
<p>Here MRTS is the <em>marginal rate of technical substitution</em>, <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BMRTS%7D%20=%20%5Cdfrac%7B%5Cpartial%20F/%5Cpartial%20L%7D%7B%5Cpartial%20F/%5Cpartial%20K%7D">: how many units of capital one more unit of labour can replace at fixed output, which is the slope of the isoquant. A cost-minimising firm sets the MRTS equal to the wage–rental ratio <img src="https://latex.codecogs.com/png.latex?w/r">, so <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is the percentage change in the input mix <img src="https://latex.codecogs.com/png.latex?K/L"> for a 1% change in relative factor prices.</p>
<p>Capital and labour are a textbook pair because of labour economics. However, we can generalize to any kind of substitution between inputs. With inputs <img src="https://latex.codecogs.com/png.latex?Y%20=%20F(x_1,%20%5Cdots,%20x_n)"> there is an MRTS for every pair, <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BMRTS%7D_%7Bij%7D%20=%20%5Cdfrac%7B%5Cpartial%20F/%5Cpartial%20x_i%7D%7B%5Cpartial%20F/%5Cpartial%20x_j%7D">, holding output and all the other inputs fixed. The MRTS generalization is still well defined there. <img src="https://latex.codecogs.com/png.latex?%5Csigma"> ceases to be unique, as we must decide what the other inputs do while <img src="https://latex.codecogs.com/png.latex?i"> and <img src="https://latex.codecogs.com/png.latex?j"> trade off. Various choices give varying specialty elasticities, which are out of scope for the moment.</p>
</section>
<section id="the-constant-elasticity-of-substitution-family" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="the-constant-elasticity-of-substitution-family"><span class="header-section-number">2</span> The Constant Elasticity of Substitution Family</h2>
<p>A.k.a. the CES family. Includes many classics.</p>
<p><img src="https://latex.codecogs.com/png.latex?Y%20=%20A%5Cleft%5B%5Calpha%20K%5E%7B%5Crho%7D%20+%20(1-%5Calpha)L%5E%7B%5Crho%7D%5Cright%5D%5E%7B1/%5Crho%7D,%20%5Cqquad%20%5Csigma%20=%20%5Cfrac%7B1%7D%7B1-%5Crho%7D"></p>
<p>Three famous special cases fall out as <img src="https://latex.codecogs.com/png.latex?%5Crho%20%5Cto%201,%200,%20-%5Cinfty">:</p>
<section id="linear-perfect-substitutes" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="linear-perfect-substitutes"><span class="header-section-number">2.1</span> Linear / perfect substitutes</h3>
<p>(<img src="https://latex.codecogs.com/png.latex?%5Crho%20=%201">, <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%20%5Cinfty">): <img src="https://latex.codecogs.com/png.latex?Y%20=%20aK%20+%20bL">. One input can replace the other at a fixed rate, so a cost-minimizing buyer checks which input is cheaper per unit of output and uses only that one. That fits electricity, where a kilowatt-hour from a wind farm does the same job as one from a gas plant, but most interesting production functions are not perfectly substitutable.</p>
</section>
<section id="cobb-douglas" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="cobb-douglas"><span class="header-section-number">2.2</span> Cobb-Douglas</h3>
<p>(<img src="https://latex.codecogs.com/png.latex?%5Crho%20%5Cto%200">, <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%201">): <img src="https://latex.codecogs.com/png.latex?Y%20=%20A%20K%5E%7B%5Calpha%7D%20L%5E%7B%5Cbeta%7D">. The default workhorse. It’s everywhere because it is realistic if you squint right and also still pretty tractable:</p>
<ul>
<li>Log-linear, so estimation is OLS on <img src="https://latex.codecogs.com/png.latex?%5Clog%20Y%20=%20%5Clog%20A%20+%20%5Calpha%20%5Clog%20K%20+%20%5Cbeta%20%5Clog%20L">.</li>
<li>“Constant income shares”: if each input (let us say, labour and capital again) is paid according to its marginal product, owners of capital always receive the fraction <img src="https://latex.codecogs.com/png.latex?%5Calpha"> of output and workers the fraction <img src="https://latex.codecogs.com/png.latex?%5Cbeta">. For much of the twentieth century labour’s share of national income did hold roughly steady, which bolstered the credibility of Cobb-Douglas.</li>
<li>Self-dual — the cost function is also Cobb-Douglas, so demand systems come out in closed form.</li>
<li>Returns to scale is just <img src="https://latex.codecogs.com/png.latex?%5Calpha%20+%20%5Cbeta">; set <img src="https://latex.codecogs.com/png.latex?=1"> and we get constant returns for free.</li>
<li>Nice interior solutions, everything differentiable, no inputs ever go to zero (each input is <em>essential</em>: <img src="https://latex.codecogs.com/png.latex?Y%20=%200"> if any input is 0).</li>
</ul>
<p>That said, setting <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%201"> is dangerous. If we can estimate the other parameters from data, we should estimate this one too. It’s clearly important. Whether income shares rise or fall as capital deepens depends on whether <img src="https://latex.codecogs.com/png.latex?%5Csigma%20%5Cgtrless%201">. Piketty has famously noted that labour share has been declining.</p>
</section>
<section id="leontief" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="leontief"><span class="header-section-number">2.3</span> Leontief</h3>
<p>(<img src="https://latex.codecogs.com/png.latex?%5Crho%20%5Cto%20-%5Cinfty">, <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%200">): <img src="https://latex.codecogs.com/png.latex?Y%20=%20%5Cmin%5Cleft(%5Cfrac%7BK%7D%7Ba%7D,%20%5Cfrac%7BL%7D%7Bb%7D%5Cright)">. Fixed proportions, zero substitution. Think “one pilot per plane”, “two wheels per bicycle”.</p>
<ul>
<li>It’s the basis of <em>input–output analysis</em> (what Leontief’s renown stems from) and of activity-analysis / linear programming models of production.</li>
<li>It’s the right short-run model under <em>putty-clay</em>: substitutable ex ante when designing the plant, rigid ex post once the concrete is poured.</li>
<li>Isoquants have kinks and it’s non-differentiable, so calculus-based comparative statics fail — but LP duality replaces them.</li>
</ul>
</section>
<section id="general-ces" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="general-ces"><span class="header-section-number">2.4</span> General CES</h3>
<p>(<img src="https://latex.codecogs.com/png.latex?%5Csigma"> free): what we use when <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is a variable of interest. Famously a battleground in environmental economics — whether manufactured capital can substitute for natural capital (<img src="https://latex.codecogs.com/png.latex?%5Csigma%20%3E%201"> vs <img src="https://latex.codecogs.com/png.latex?%5Csigma%20%3C%201">) is deep in the Solow-vs-Daly sustainability argument.</p>
</section>
</section>
<section id="non-constant-elasticity-of-substitution" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="non-constant-elasticity-of-substitution"><span class="header-section-number">3</span> Non-constant elasticity of substitution</h2>
<section id="nested-ces-klem" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="nested-ces-klem"><span class="header-section-number">3.1</span> Nested CES / KLEM</h3>
<p><img src="https://latex.codecogs.com/png.latex?Y%20=%20F%5Cbig(E,%20G(K,%20L)%5Cbig)"> with different <img src="https://latex.codecogs.com/png.latex?%5Csigma"> at each level. This represents a tree hierarchy of inputs funneling into a single output of interest. Standard in CGE and climate-economy models, because the claim there is usually “capital and skilled labour are complements but both substitute for energy” — which no single-level CES can express.</p>
</section>
<section id="translog-christensenjorgensonlau" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="translog-christensenjorgensonlau"><span class="header-section-number">3.2</span> Translog (Christensen–Jorgenson–Lau)</h3>
<p><img src="https://latex.codecogs.com/png.latex?%5Clog%20Y%20=%20a_0%20+%20%5Csum_i%20a_i%20%5Clog%20x_i%20+%20%5Ctfrac%7B1%7D%7B2%7D%5Csum_i%5Csum_j%20b_%7Bij%7D%5Clog%20x_i%20%5Clog%20x_j"> This is a quadratic in the log inputs, i.e., a second-order Taylor expansion in the log domain. Around any chosen input mix, it can match how output responds to each input and how easily every pair of inputs substitutes for each other; so, unlike CES, it lets <img src="https://latex.codecogs.com/png.latex?%5Csigma"> differ between pairs and change as the mix changes. Setting every <img src="https://latex.codecogs.com/png.latex?b_%7Bij%7D%20=%200"> recovers Cobb-Douglas, so we can check whether Cobb-Douglas is sufficient by testing whether those terms are zero, insofar as estimation is actually feasible, although some introspection will reveal that here be dragons. For one, the parameter count grows with the square of the number of inputs, and, moreover, we need to fit it in a tiny, localized range, which is hard. In practice, most translog work fits the cost function <img src="https://latex.codecogs.com/png.latex?C"> in this form rather than <img src="https://latex.codecogs.com/png.latex?F">, for the identification reason above.</p>
</section>
<section id="others-i-have-not-heard-of" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="others-i-have-not-heard-of"><span class="header-section-number">3.3</span> Others I have not heard of</h3>
<p>Surely many</p>
</section>
</section>
<section id="practical-advice" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="practical-advice"><span class="header-section-number">4</span> Practical advice</h2>
<p>Rule of thumb from a non-economist:</p>
<p>If I find myself using something dumber than Cobb-Douglas, I should switch to Cobb-Douglas, which is typically the minimum viable complexity that still captures substitution between inputs.</p>
<p>Then, if the problem proves more complex, I can get fancier.</p>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<div id="refs">

</div>


</section>

 ]]></description>
  <category>cooperation</category>
  <category>economics</category>
  <category>hand wringing</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/production_functions.html</guid>
  <pubDate>Mon, 27 Apr 2026 07:27:48 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/popery_man_RP-P-OB-82.077.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>So you’ve joined a union</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/post/so_youve_joined_a_union.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>tl;dr</strong> Australian unions are generally worth joining. Being a workplace delegate is high-leverage: it opens up points of intervention that are hard to come by otherwise (inside gossip, colleague ties, negotiating room, a widened range of sayable things at work). They are flawed — affiliated to the political parties, biased toward incumbents, institutionally tired. Still: I recommend you do it, and do it <em>now</em> while you still have leverage.</p>
</div>
</div>
</div>
<div id="fig-opstand" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-opstand-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/opstand_RP-P-OB-78.391.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/opstand_RP-P-OB-78.391.avif" id="fig-opstand" class="full figure illustration img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-opstand-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Context: I was a workplace delegate for the CSIRO Staff Association — which is a section of the <a href="https://www.cpsu.org.au/?ref=danmackinlay.name">CPSU</a>, the public-service mega-union — at <a href="https://www.data61.csiro.au/?ref=danmackinlay.name">Data61</a>, CSIRO’s digital research division, during a stretch when Data61 was being systematically under-resourced. This is a guide to being a member or delegate in a professional-staff union in Australia, not a guide to unionism in the abstract. People should write guides for meatpackers or wharvies, and for other professional unions too. And they have.</p>
<p>After some struggle I realised that write-ups of the ins and outs of unionising seemed to be kinda sparse in my cohort (i.e.&nbsp;nerds, professionals) and I figured it would be good to speak up about it.</p>
<p>My union experience was actually kinda great. This is a pleasing realisation, because if I had reasoned from the ambient opinions about unions, I might have thought that a union membership card was something I would pick up in an unlabelled brown paper bag from behind the counter of a vendor of ill repute.</p>
<p>Instead: free merch, deep intel on what is going on, a chance to speak truth to power, and the satisfaction of having helped to make things better for colleagues.</p>
<p>There are downsides too, ofc. Read on for the dirty.</p>
<section id="australian-context" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="australian-context"><span class="header-section-number">1</span> Australian context</h2>
<p>Union density in Australia is about <a href="https://www.abs.gov.au/statistics/labour/earnings-and-working-conditions/trade-union-membership/latest-release?ref=danmackinlay.name">13.1% of employees as of August 2024</a> — up from 12.5% in 2022, the first recorded increase since 2011. Over the longer term the trend is down: in 1992, density was 40%. That is to say, the movement has been dying for three decades, and recently it, uh, stopped dying. Something interesting is happening at the margin, starting from a low base.</p>
<p>Inside those averages, professionals are overrepresented (around 20% density) and education-and-training workers more so (27%). So if you are a white-collar knowledge worker reading this, you experimenting with unionism is not unusual for your industry. There are plenty of us already, just not looking prominently Bolshie means we are just invisible.</p>
<p>The Australian union system is unusual in a few ways.</p>
<p><em>Enterprise bargaining</em> is the central mechanism for negotiating conditions. Pay and conditions for most workplaces are set by an <em>enterprise agreement</em> negotiated between the employer and a bargaining representative — usually the union — at the level of the single workplace or company. Since the <a href="https://www.legislation.gov.au/C2009A00028/latest/text?ref=danmackinlay.name">Fair Work Act 2009</a>, this is how a modern Australian pay rise gets made. Our union’s main job, most of the time, is to negotiate this instrument.</p>
<p><em>Protected industrial action</em> is only legal during bargaining for a new agreement, and only after a secret ballot via the <a href="https://www.fwc.gov.au/?ref=danmackinlay.name">Fair Work Commission</a>, and only with three working days’ notice (for most workplaces). Striking in solidarity with another workplace is not protected, which is to say, it is effectively illegal. So, don’t be bringing to mind militant historical or European unionism when you think about the modern Australian version.<sup>1</sup></p>
<p><em>Most big unions affiliate to the ALP.</em> “Affiliate” means something specific: the union’s President and Secretary sign a pledge to the Labor platform, pay a per-member affiliation fee, and the union’s members are thereby entitled to 50% of delegate positions at Labor state and national conferences. The <a href="https://www.actu.org.au/?ref=danmackinlay.name">ACTU</a> itself does not affiliate — it is a peak body — but most of its largest member unions do. In practice, this means some fraction of our dues is helping to elect a particular political party, and that the union’s political stances will be calibrated against not upsetting Labor. We can think this is fine or not fine. I am personally not a fan, since I think it sets up bad institutional incentives.</p>
</section>
<section id="flaws" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="flaws"><span class="header-section-number">2</span> Flaws</h2>
<section id="incumbency-bias" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="incumbency-bias"><span class="header-section-number">2.1</span> Incumbency bias</h3>
<p>This one is obvious when you think about it. Unions are disproportionately composed of, and run by, indefinite-tenure staff. The consequences cascade.</p>
<p>In enterprise bargaining, the people who turn up to the member meetings, write submissions, and stress-test the employer’s offer are overwhelmingly indefinite staff. They know how it works because they have been through a round before. The fixed-term, casual, and labour-hire workforce — which in research is often the majority — frequently doesn’t know bargaining is happening, or doesn’t know they can participate, or rationally does not invest in a process whose outputs they may not be around to enjoy. So the agreement gets tuned for the people in the room.</p>
<p>A large fraction of delegate time goes into redundancy negotiation. That is not a criticism of delegates — redundancies are harrowing and it is hard to argue that being present is not helping people in a real way. It also sketches the contours of the service the union tends to provide: a union that spends most of its crisis capacity defending tenured jobs is, implicitly, a service for people who have tenured jobs to defend.</p>
<p>For young workers, on a fixed-term contract, or otherwise outside the incumbent class, the union can feel irrelevant by default. Still, I reckon, worth joining. Just go in expecting to need to push for your own interests.</p>
</section>
<section id="operational-conservatism" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="operational-conservatism"><span class="header-section-number">2.2</span> Operational conservatism</h3>
<p>Unions per default do not take the operational needs of the employer seriously, or at least make ambit claims that imply they don’t. “I know the budget was cut by 15% but we want a 15% pay rise with no redundancies” will … never work? Nor will “always favour incumbent staff and also hire all the new skills we need.”</p>
<p>Maybe this is just me running into someone else’s negotiation style, but for me it would be more credible if we could go into negotiations with a more flexible, problem-solving mindset, instead of “they start cruelly low, we start impossibly high and then meet in the middle.” Or maybe this is how organizations have to negotiate because complicated platforms are hard to vote on.</p>
<p>This is not what they are for, I suppose, but the consequences show up in the agreement. In bargaining I have watched clauses land which would, I could see, make some internal workflow I care about more cumbersome. A classic example: by making it hard to sack people and hard to hire on short-term contracts, the union starves the organisation of the ability to experiment with short-term contracts for new skills and capabilities.</p>
<p>OTOH, by permitting short-term contracts, the organization would have license to recruit transient staff who would never join the union (bad for the union) and not negotiate for better conditions (bad for the union’s members).</p>
<p>A related failure: unions rarely favour creative or radical approaches. “We got this last round, let’s get a bit more of it this round” is the default. Arguments for novel structures — more flexible project-based teams, different modes of career progression, profit-sharing arrangements for commercialisable work — tend to fall outside the union’s Overton window. Part of this is presumably institutional memory (those structures have been used as thin ends of anti-worker wedges before). Part of it is organisational inertia.</p>
<p>For the kind of person who likes working on hard interesting problems inside a functional organisation, the union’s default policy stance can feel like being in a 1950s time tunnel, aimed at a very different, less dynamic world of work. Again, this is not what the union is for, but it is a consequence of the institutional setup.</p>
<p>How to solve for this? I reckon it’s worthwhile at least turning up and having a chance to push for good outcomes, without hoping for a complete solution from this alone.</p>
<p>Complete solutions would be worker co-ops, or a more radical political economy, or a different industrial-relations framework. Work towards those too.</p>
</section>
<section id="the-professional-organiser-problem" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="the-professional-organiser-problem"><span class="header-section-number">2.3</span> The professional-organiser problem</h3>
<p>Big unions employ professional organisers and officials. These are often good, dedicated people. They are also incumbents in their own sense — they have career paths, personal influence, factional alliances, and a durable stake in the union’s continuing to be run the way it has been run. In the worst cases this produces performative, communications-hoarding, process-heavy unionism that does not scale with the membership’s engagement.</p>
<p>The tell I watch for is the communications architecture. If the union does not host a chat channel where members can talk to <em>each other</em> — if all communication is union-to-member broadcast from the officials, with no lateral member-to-member channel — then that is a union whose professionals are more comfortable talking <em>to</em> members than enabling members to talk to one another. We can draw our own conclusions about why. My guess: an engaged, talkative membership is harder to steer.</p>
<p>I don’t think this is malice. I think it’s an equilibrium that is comfortable for professional staff who are only shallowly engaged with any given workplace’s problems, and that persists because nobody with the power to change it has much incentive to.</p>
</section>
<section id="the-enervating-fair-work-settlement" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="the-enervating-fair-work-settlement"><span class="header-section-number">2.4</span> The enervating Fair Work settlement</h3>
<p>The Fair Work Act gives Australian workers a floor of rights (minimum wages, leave, unfair dismissal protection, an enterprise-level bargaining mechanism) that is better than nothing. It also gives Australian unions a narrow, procedural, Commission-mediated menu of actions they can choose from. Protected action is protected only within tightly drawn conditions. Pattern bargaining across an industry is largely forbidden. Sympathy strikes are forbidden. The whole apparatus assumes that industrial conflict should be rare, procedural, and bounded.</p>
<p>This produces an enervated unionism. The union rarely needs to mobilise its membership at scale — the Commission is <em>right there</em>, the processes are what they are, and the union’s comparative advantage is procedural expertise rather than mass pressure. Compared to what one reads of unionism in, say, the US or continental Europe, the Australian version can feel strangely administrative. I find this both a relief (nobody is getting arrested) and a limitation (nobody is winning very much, either).</p>
</section>
</section>
<section id="why-its-worth-it-anyway" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="why-its-worth-it-anyway"><span class="header-section-number">3</span> Why it’s worth it anyway</h2>
<p>Given all the above, why did I sign up as a delegate, and why would I do it again? Because the leverage is huge. Being a delegate got me:</p>
<p><em>Inside gossip.</em> The delegates’ meetings are where one learns what management is telling the union, which is often a truer signal than what management is telling staff. One sees drafts of redundancy proposals before they become announcements. One hears which divisions are being quietly wound down. Going the other way, the delegate carries back useful information about the workforce that the management chain on its own would not have heard.</p>
<p>The more underrated form of this, IMO, is <em>lateral</em> gossip — what peers know about each other that a hierarchical organisation aggressively filters out. Our formal view of colleagues in other teams is mediated almost entirely by those teams’ managers, who have career incentives to present their units as competent, well-run, and indispensable. The union is one of the few structures in a big organisation where frontline staff from different teams compare notes, and discover: which empire-building manager is winning headcount they don’t have work for, which team is quietly drowning, which senior technical lead is the bottleneck nobody can work around, which department’s nominal stars are coasting, which quiet group does the good work that never shows up in the monthly report. This stuff matters for a lot of mid-career decisions — internal transfers, whether a cross-team dependency is safe to rely on, which up-chain promises are worth trusting — and in an excessively hierarchical organisation almost none of it is available through any other channel. People seriously underestimate how much of their picture of their workplace has been composed for them by interested parties. The union cuts across that.</p>
<p><em>Strong relationships with colleagues across silos.</em> Research organisations partition into teams that rarely talk to each other, and that compete for resources. The union is one of very few structures where a data scientist talks to a comms officer talks to a facilities manager as peers with a shared interest. This defangs a lot of internal resource competition simply by putting faces on the “other” side of it. The employer benefits from this too, even if they rarely notice: a less siloed workforce wastes less energy on internal turf wars.</p>
<p><em>Esprit de corps.</em> Not nothing. Knowing that one has a dozen colleagues whose numbers are in one’s phone, and who will pick up, is a psychic resource in a way that is hard to describe until one has had it.</p>
<p><em>A widened range of sayable things at work.</em> Delegates, by convention and by the adverse-action provisions of the Fair Work Act, have more protection for speaking publicly about workplace issues than ordinary staff do.<sup>2</sup> We can say things a rank-and-file employee would be nervous to say, because we are saying them in our delegate capacity. This is not absolute — we can still be sacked for many things — but it widens the range of sayable things considerably. I used this a lot.</p>
<p><em>A direct line to powerful people when it mattered.</em> At bargaining time, at redundancy time, at restructure time, delegates are in the room with the CEO, or the DG, or their direct deputies. One gets to negotiate with the people actually making the decisions. For a mid-career professional this is an unusual amount of face time with senior leadership, and the skills one builds — reading the room, finding the pressure points, knowing when to escalate — are portable.</p>
<p><em>Friends.</em> I left the delegate role with more friends at CSIRO than I had before I took it. I don’t know a better return on my time.</p>
</section>
<section id="a-second-centre-of-power" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="a-second-centre-of-power"><span class="header-section-number">4</span> A second centre of power</h2>
<p>Most of the above is about what the delegate gets. The less obvious side is what the <em>employer</em> gets from having a functioning union on the premises. IMO a sensible leadership team treats the union as a cheap source of information and a cheap source of accountability that would otherwise be unavailable to it, which is the core of what having a second centre of power inside an organisation buys you.</p>
<p>Every formal complaints mechanism I have heard of routes upward through the chain of command. Which fails at the kind of case that matters most: when the problem <em>is</em> the chain of command. A staff member being ground down by their manager has almost nowhere to go internally — the next rung up is the bully’s mate, or someone institutionally invested in not seeing the bully as a problem. The union is a parallel chain that does not route through the bully. At least, not <em>that</em> bully. A well-run HR function treats this as a useful signal, because it surfaces problems the formal channel would have swallowed. A badly run one resents it. The ones that resent it tend, in my experience, to be the ones whose formal channel is broken in ways they would rather not admit.</p>
<p><em>Risk identification.</em> Frontline staff know things that never make it onto a dashboard: project-failure signals, mis-scoped deliverables, customer-abuse patterns, strategies that will not survive contact with reality. A lot of this information is uncomfortable, which is precisely why it gets filtered out as it moves upward through layers of people whose performance reviews depend on appearing to have things in hand. The union is one channel by which those signals can bypass the incentive structures that suppress bad news going up the chain. I think that organization <em>could</em> benefit from this? But what I have noticed is that they treat it as a threat.</p>
<p><em>A commitment device.</em> An enterprise agreement, together with an organised workforce that will notice breaches of it, is a way for senior leadership to credibly commit to things. Promises management would like to make but cannot otherwise guarantee — about workload, about career paths, about fair treatment of a category of worker — become more durable when backed by a bargaining process and a Union that will kick up a stink if the promises are broken. In principle, the employer gets a more motivated, lower-turnover workforce in exchange for being bound to its own stated intentions.</p>
<p>This is not to pretend away structural conflict. Interests do diverge — about wages, about job security, and above all, who carries the risk when things fail. The bargaining apparatus is built around those divergences. But most decisions, at least in my workplaces, most of the time, do not need to be zero-sum wars. Some zero-sum fights I was involved in were made to be zero-sum to keep people busy and distracted, and to stop them from collaboratively solving the real problem. When a bad manager stokes conflict to distract attention from their own failings, this is a power play, not an inevitable state of affairs or rule of nature.</p>
<p>Neither “side” is a monolith. “Workers” and “employers” are each coalitions — junior and senior staff, contractors and indefinite, technical and administrative; shareholders, executives, line managers, finance, operations — with partially overlapping and partially competing interests. Cross-line alliances — this technical team and the CTO, this cohort of staff and the CFO’s cost-containment agenda — are common and often stable. The two-team framing is convenient shorthand at best.</p>
<p>Leaning on the adversarial frame as the default identity of unionism — worker-vs-boss as our core brand — is great for recruiting — people love a fight with the Other. But it has costs: it alienates the moderates on both sides, and leaves win-wins on the table that would otherwise have been obvious.</p>
<p>It seems most executive teams <em>don’t</em> want union pushback. IMO they should want all the union engagement they can get. A second centre of power inside a dysfunctional organization can be one of the few ways management gets the information and accountability needed to stop making predictable, avoidable mistakes — which is a case of the <a href="../notebook/institutional_alignment.html">institutional alignment</a> problem. The union, or at least many unions I have been involved with, can be a flawed exemplar of that second centre: too procedural, too political, too disengaged, for all the reasons I listed above. But those flaws are not, mostly, irreducible features of unionism. They are habits that unions can fall into, and which we can change by joining the damn union and making it work better.</p>
<p>Ultimately, the union is us. If we want a second centre of power in our organisation that is not captured, not incumbency-biased, and not hoarding communications, the mechanism by which it comes to exist is: we join, we show up, we pay 0.7% of our salary, we invest our time and sweat equity in making everything else better. The price of admission is unusually low.</p>
</section>
<section id="advice-i-wish-i-had-on-day-one" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="advice-i-wish-i-had-on-day-one"><span class="header-section-number">5</span> Advice I wish I had on day one</h2>
<p>A few things I had to work out the hard way.</p>
<p><em>Turn up.</em> A startling fraction of the influence in the delegate body accrues simply to the people who turn up to every meeting. This is unglamorous and correct. Showing up is 80% of the job. If there are literally too many meetings to attend, ramp it back. If the union insists on stupid held-over structures (e.g.&nbsp;place-based organising for an online organisation with distributed teams, looking at you CSIRO Staff Association) we could make it a project to try to modernise.</p>
<p><em>Ask for the bargaining log of claims early.</em> Every round of enterprise bargaining starts with a list of things the union is asking for. If you are not on the bargaining committee you may not see it until it is near-final. Ask for it. If there are claims we care about, we have to fight for them early in the process, not at the end.</p>
<p><em>Cultivate union communications the union staff cannot control.</em> Ideally you trust your organiser (for the record, I always trusted mine). But baking too much control into the union staff’s roles sets up bad incentives for them to hoard power just like the bosses. If the union doesn’t run a members’ chat, start a group chat among the delegates you trust.</p>
<p><em>Learn the instrument.</em> So easy these days that there is no excuse. Load the current enterprise agreement into an LLM. The last one too. Also, Part 2-4 of the Fair Work Act. Ask questions. Read specific passages. Learn the stupid terms of art. We become much more useful to our colleagues the moment we can answer basic questions about the instrument instead of referring them upward.</p>
<p><em>Practise <a href="../notebook/how_to_communicate.html">communication skills</a>.</em> Delegate work is the best-calibrated training environment I have found for the workplace-communication skills that white-collar work otherwise hides from us until we are mid-career and already expected to have them: writing a clear update for members, chairing a meeting where people disagree and we need a decision at the end of it, negotiating with a senior executive who has lawyers (we don’t), delivering hard news to a colleague, pushing back on a bad idea without burning the relationship. The stakes are high enough that sloppiness has consequences, and low enough that we get to try again next meeting. Take the opportunity.</p>
<p><em>Do not let this become your whole life, or even your whole job.</em> Union work is high-leverage precisely because the baseline engagement of everybody else is so low. Which means there is infinite demand for your time. Decide in advance what fraction of your working week you will spend on it, and stick to it. Extension projects are good, but don’t pick too many.</p>
<p><em>Recruit more delegates.</em> The more delegates we have, the more influence we have, and the less work each delegate has to do. Don’t solve everything yourself; solve one thing and use that as a recruiting pitch for the next delegate. Showing people they can make life better is hugely encouraging.</p>
<p><em>We get so much love for doing this.</em> People, mostly, value and appreciate the work of delegates. I got so much respect from my colleagues when I’d shown up for a tough time, but they would make sure the line manager was not around first. Then the line manager would also thank me, when their direct reports were not around. Double thanks!</p>
</section>
<section id="unfinished-business" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="unfinished-business"><span class="header-section-number">6</span> Unfinished business</h2>
<p>Things I wanted to get to and didn’t, in case some other delegate is reading this and looking for a project.</p>
<p><em>Modernising workplace democracy with the <a href="../notebook/civic_tech.html">civic-tech stack</a>.</em> The CSIRO Staff Association still runs on place-based organising for a workforce that is distributed and largely online. The mechanisms surveyed in that notebook — bridging-based aggregation (Polis, Community Notes), AI-mediated deliberation, participatory budgeting — are exactly the tools a union ought to be piloting on itself. A union is one of the few institutions inside a firm with the standing to run them, and one of the few with a membership big and engaged enough to generate the signal. Nobody is doing this seriously in the Australian professional-union space, as far as I can tell.</p>
<p><em>Bringing the fixed-term, casual, and labour-hire workforce into bargaining.</em> See the incumbency-bias section. I had ideas, I ran out of road.</p>
<p><em>Member-to-member communications infrastructure that the officials don’t control.</em> I started this and didn’t finish it. It is harder than it looks, because the people most willing to spin up a chat are also the people most likely to make it weird.</p>
<p><em>Working out whether labour unions have any leverage left as the <a href="../notebook/ai_economics_automation.html">economics of cognitive automation</a> shifts under us.</em> The whole bargaining apparatus assumes that the workforce is hard to replace at short notice. If a meaningful slice of professional-staff tasks becomes a thing the employer can buy on an inference budget, the strike threat — already attenuated by the Fair Work Act — gets thinner. My guess is that unions <em>do</em> still have leverage <em>for now</em>, partly because the substitution is patchier than the headlines suggest and partly because the political-and-legitimacy costs of replacing organised workers with bots are still real. But that window is now, plus not much runway: anything we want enshrined in an enterprise agreement — clauses about AI tooling, retraining commitments, displacement protections, transparency on automation pilots — has to land in the next round or two, while the threat of collective action is still credibly priced into management’s calculations.</p>
</section>
<section id="if-we-were-starting-from-scratch" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="if-we-were-starting-from-scratch"><span class="header-section-number">7</span> If we were starting from scratch</h2>
<p>A sceptical reader will have noticed that almost everything in this post is a second-best argument. The union is a patch on a firm shape that has employees and shareholders on opposing sides of an ownership boundary, with managers in between. A lot of what the union does — surfacing risks, piercing silos, keeping managers accountable, sometimes even committing the enterprise to its stated intentions — is only necessary because the firm is designed the way it is.</p>
<p>Other shapes are possible. Let me get galaxy-brained for a second: Worker-owned firms, consumer cooperatives, and various flavours of co-determination redistribute the ownership boundary and fold the “second centre of power” into the first. I have <a href="../notebook/founders_workers.html">written separately</a> about how one might divide equity in a firm being built along those lines. More broadly, this sits inside the frame of <a href="../notebook/institutional_alignment.html">institutional alignment</a>: any institution we build is, in effect, a trained system whose incentives and reporting structures have to be tuned for the work we want out of it, and most organisations have simply never been tuned for the work of <a href="../notebook/collective_care.html">collectively caring for the people inside them</a>.</p>
<p>A ground-up redesign is not on the menu for most employees of most organisations. But the frame is useful for explaining <em>why</em> even a flawed union is high-leverage: unions are one of the viable ways to retrofit decent communications into an ossified organization. If we could start over, we might build that in at the foundation. We mostly can’t, so we patch.</p>
</section>
<section id="resources" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="resources"><span class="header-section-number">8</span> Resources</h2>
<p>Background and statistics</p>
<ul>
<li><a href="https://www.abs.gov.au/statistics/labour/earnings-and-working-conditions/trade-union-membership/latest-release?ref=danmackinlay.name">ABS Trade Union Membership, August 2024</a> — primary source for density figures.</li>
<li><a href="https://www.rba.gov.au/publications/rdp/2019/2019-02/full.html?ref=danmackinlay.name">RBA RDP 2019-02: Is Declining Union Membership Contributing to Low Wages Growth?</a> — careful empirical work on the wage effects of union decline.</li>
</ul>
<p>The legal instruments</p>
<ul>
<li><a href="https://www.fairwork.gov.au/employment-conditions/agreements/bargaining-for-an-agreement?ref=danmackinlay.name">Fair Work Ombudsman: Enterprise agreements and bargaining</a></li>
<li><a href="https://www.fwc.gov.au/workplace-disputes/other-workplace-disputes/industrial-action?ref=danmackinlay.name">Fair Work Commission: Industrial action</a></li>
<li><a href="https://www.legislation.gov.au/C2009A00028/latest/text?ref=danmackinlay.name">Fair Work Act 2009 (text)</a></li>
</ul>
<p>Peak bodies and some relevant unions</p>
<ul>
<li><a href="https://www.actu.org.au/?ref=danmackinlay.name">Australian Council of Trade Unions (ACTU)</a></li>
<li><a href="https://www.cpsu.org.au/?ref=danmackinlay.name">CPSU (PSU Group)</a> — federal public sector</li>
<li><a href="https://csirostaff.org.au/?ref=danmackinlay.name">CSIRO Staff Association</a> — a section of the CPSU; my own alma mater</li>
<li><a href="https://www.nteu.org.au/?ref=danmackinlay.name">NTEU</a> — tertiary education</li>
<li><a href="https://www.professionalsaustralia.org.au/?ref=danmackinlay.name">Professionals Australia</a> — engineers, scientists, managers</li>
</ul>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p><a href="https://en.wikipedia.org/wiki/Australian_labour_movement?ref=danmackinlay.name">The history</a> is pretty cool though, esp.&nbsp;<a href="https://en.wikipedia.org/wiki/Paddy_Troy?ref=danmackinlay.name">Paddy Troy</a>.↩︎</p></li>
<li id="fn2"><p>Strictly, the Fair Work Act’s general protections (Part 3-1) are what actually do the legal work; the constitutional “implied freedom of political communication” is a much narrower doctrine that rarely maps cleanly onto workplace speech. Conflating the two is sloppy but a useful shorthand for the lived experience of being a delegate.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>economics</category>
  <category>ethics</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>politics</category>
  <category>work</category>
  <guid>https://danmackinlay.name/post/so_youve_joined_a_union.html</guid>
  <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/opstand_RP-P-OB-78.391.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Returns to scale in technological society</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/returns_to_leviathan.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-wall-scaling-sm" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-wall-scaling-sm-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/wall_scaling_sm_RP-P-1909-6331.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/wall_scaling_sm_RP-P-1909-6331.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-wall-scaling-sm-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Consider the scaling laws across <a href="../notebook/urbanism.html">cities</a>, economies, <a href="../notebook/nn_scaling_laws.html">neural nets</a>… Does it follow that in a competitive selection environment, ultimately, the world converges upon one leviathan? One giant system? Is the attracting state a single, gigantic economy? In AI safety people wonder about the singleton, the ultimate mega agent. Should we think about the singleton economy, the singleton nation state?</p>
<p>Is there any leverage to being small and idiosyncratic where there is such obvious gain in being strong, powerful, and all-encompassing? Does the world converge to unipolarity? Do we grind out all the small states, the tiny coalitions, the middle powers? Or is there some kind of stable polycentricity, where there are niches for small and medium-sized entities to thrive? I would like to know.</p>
<section id="urban-scaling" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="urban-scaling"><span class="header-section-number">1</span> Urban scaling laws</h2>
<p>The urban scaling results are a empirical anchor for all of this. <span class="citation" data-cites="Bettencourt2013Origins">Bettencourt (2013)</span> provides the theoretical derivation: cities are social reactors, and the superlinear scaling emerges from the increasing number of social interactions per capita as density grows. <span class="citation" data-cites="Bettencourt2007Growth">Bettencourt et al. (2007)</span> established the empirical regularities — wealth creation and innovation scale with exponent <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Capprox%201.2">, infrastructure with <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Capprox%200.8">.</p>
<p>Methodological critique of the naive approach may be found in <span class="citation" data-cites="Cottineau2017Diverse">Cottineau et al. (2017)</span> — the measured exponent is sensitive to how we define city boundaries, which indicators we use, and which countries we look at. <span class="citation" data-cites="Arcaute2015Constructing">Arcaute et al. (2015)</span> show that for England and Wales, many scaling results vanish or change sign when city boundaries are defined differently. This matters: if the exponent is an artefact of the boundary definition, the whole edifice wobbles.</p>
<p>There is much more <span class="citation" data-cites="Arcaute2015Constructing Balland2020Complex Bettencourt2016Urban Cottineau2017Diverse Kuhnert2006Scaling Leishman2021Relationships PrietoCuriel2022Scaling Rybski2019Urban">(Arcaute et al. 2015; Balland et al. 2020; Bettencourt and Lobo 2016; Cottineau et al. 2017; Kühnert, Helbing, and West 2006; Leishman et al. 2021; Prieto Curiel, Cabrera-Arnau, and Bishop 2022; Rybski, Arcaute, and Batty 2019)</span></p>
</section>
<section id="economic-scaling-laws" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="economic-scaling-laws"><span class="header-section-number">2</span> Economic scaling laws</h2>
<p>The literature on returns to scale is enormous and old, but I want to slice it by the <em>unit of analysis</em>: firms, nations, cities. The scaling exponents turn out to be different in each case, and the reasons why are the interesting part.</p>
<section id="firms" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="firms"><span class="header-section-number">2.1</span> Firms</h3>
<p>Geoffrey West’s Santa Fe Institute group found that companies scale <em>sublinearly</em> — more like biological organisms than cities <span class="citation" data-cites="West2017Scale">(West 2017)</span>. Sales scale roughly linearly with employee count (exponent <img src="https://latex.codecogs.com/png.latex?%5Capprox%201">), but profitability and innovation per capita do not increase with firm size the way they do in cities. The implication is that firms, like organisms, are bounded growers: they slow down, stop growing, and die. The average lifespan of a publicly traded company is about 10 years.</p>
<p>Why? Coase proposed a reason in 1937 <span class="citation" data-cites="Coase1937Nature">(Coase 1937)</span>: firms exist because they reduce transaction costs below what the market would charge, but they expand only until the cost of organising one more internal transaction equals the cost of doing it on the market. There are “decreasing returns to the entrepreneur function” — management overhead, communication pathways (<img src="https://latex.codecogs.com/png.latex?O(n%5E2)"> in the number of interacting units), errors of coordination. Which is to say, firms hit diseconomies of scale because internal coordination is expensive.</p>
<p>This is the Coasean boundary, and it’s the reason we don’t see a single firm absorbing the whole economy — or didn’t, before at least. This might change.</p>
<p>There is a suggestive parallel with the declining labour share of income <span class="citation" data-cites="Grossman2022Elusive">(Grossman and Oberfield 2022)</span>. If capital’s share of output is rising and labour’s is falling, that is consistent with the returns accruing to the <em>infrastructure</em> side of the scaling relation (sublinear, capital-intensive) rather than the <em>social interaction</em> side (superlinear, labour-intensive). Firms might be drifting further into the organism-like regime — more capital, less interaction, lower <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> — which would make them even more bounded and mortal. Or it might mean that the Coasean boundary is shifting in ways that decouple the firm from its workforce without decoupling it from scale economies in capital. I am not sure which.</p>
</section>
<section id="nations" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="nations"><span class="header-section-number">2.2</span> Nations</h3>
<p><span class="citation" data-cites="Alesina2003Size">Alesina and Spolaore (2003)</span> model national boundaries as an equilibrium between economies of scale in public goods provision and the costs of preference heterogeneity. Bigger nations amortize the cost of armies, courts, infrastructure, and monetary systems over more taxpayers. But larger populations mean more diverse preferences, languages, cultures — harder to govern democratically, harder to produce public goods that satisfy everyone.</p>
<p>Their framework predicts that the optimal size of nations depends on the international environment. Under free trade, small nations can capture scale economies through markets rather than territory, so the equilibrium shifts toward smaller states — which is roughly what we observe since 1945, with the number of internationally recognised states roughly tripling. Under autarky, size matters more, and empires make economic sense.</p>
<p>Empirically, GDP scales sublinearly with national population — an exponent around 0.8, meaning doubling population gives less than double output <span class="citation" data-cites="PrietoCuriel2022Scaling">(Prieto Curiel, Cabrera-Arnau, and Bishop 2022)</span>. This is the opposite of the urban scaling result (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Capprox%201.15"> for socioeconomic variables in cities). Nations are not cities. The density of social interaction that drives superlinear urban scaling is diluted across a national territory.</p>
<p>Now, the stability of any polycentric equilibrium depends on the security environment. <span class="citation" data-cites="Sekeris2012Tragedy">Sekeris (2012)</span> models a common-pool resource game with violence, and finds that as the resource shrinks, conflict becomes cheap, and the cooperative equilibrium unravels. Small states can coexist under free trade and relative peace, but resource scarcity or a shift to autarky changes the payoff matrix, and the large predatory state becomes viable again. Alesina and Spolaore’s “size depends on openness” result and Sekeris’s “cooperation unravels under scarcity” result are two views of the same mechanism: polycentricity is a <em>conditional</em> equilibrium, not an attractor.</p>
</section>
<section id="some-puzzles" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="some-puzzles"><span class="header-section-number">2.3</span> Some puzzles</h3>
<p>We have three different systems with three different scaling regimes:</p>
<ul>
<li><strong>Cities</strong> scale superlinearly (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Capprox%201.15">) for socioeconomic output — innovation, wealth, crime. This is driven by the density of social interactions <span class="citation" data-cites="Bettencourt2007Growth">(Bettencourt et al. 2007)</span>.</li>
<li><strong>Firms</strong> scale roughly linearly to sublinearly. Internal coordination costs impose a ceiling. They behave more like organisms <span class="citation" data-cites="West2017Scale">(West 2017)</span>.</li>
<li><strong>Nations</strong> scale sublinearly (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Capprox%200.8">). Heterogeneity costs and governance overhead eat the returns to public goods provision <span class="citation" data-cites="Alesina2003Size PrietoCuriel2022Scaling">(Alesina and Spolaore 2003; Prieto Curiel, Cabrera-Arnau, and Bishop 2022)</span>.</li>
</ul>
<p>This gives us a hierarchy: cities are the engine of increasing returns, but both the organisational containers we put around economic activity (firms) and the political containers we put around territory (states) exhibit diminishing returns to scale. The superlinearity of cities is embedded in, and constrained by, sublinear envelopes.</p>
<p>This is a <a href="../notebook/multi_scale_agency.html">multi-level agency</a> problem dressed in scaling exponents. The city, the firm, the nation — these are not just different units of analysis, they are <em>nested levels of organisation</em> with different dynamics at each level. The scaling exponent <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> is, in a sense, a summary statistic for the kind of agent a system is at a given level of coarse-graining. Organisms (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3C%201">): bounded, mortal, efficient. Cities (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201">): open-ended, accelerating, crisis-prone. The question “at what level do we model agency?” and the question “what is the scaling exponent?” turn out to be the same question asked in different vocabularies.</p>
</section>
</section>
<section id="the-cosmic-version" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-cosmic-version"><span class="header-section-number">3</span> The cosmic version</h2>
<p><span class="citation" data-cites="Wong2022Asymptotic">Wong and Bartlett (2022)</span> push this question to its most cosmic limit. They take the urban scaling exponent <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201"> from Bettencourt <em>et al.</em> <span class="citation" data-cites="Bettencourt2007Growth">(Bettencourt et al. 2007)</span> and ask what happens when the entire planetary civilisation becomes, in effect, one city — one densely networked informational superorganism connected through the dataome.<sup>1</sup> The growth equation</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BdN(t)%7D%7Bdt%7D%20=%20%5Cleft(%5Cfrac%7BY_0%7D%7BE%7D%5Cright)%20N(t)%5E%5Cbeta%20-%20%5Cleft(%5Cfrac%7BR%7D%7BE%7D%5Cright)%20N(t)%0A"></p>
<p>predicts singularities — moments when population and energy demand tend to infinity in finite time — which must be averted by innovations that reset the system’s trajectory. But the interval between resets, <img src="https://latex.codecogs.com/png.latex?t_%7B%5Ctext%7Bcycle%7D%7D">, shrinks as the population grows. Eventually <img src="https://latex.codecogs.com/png.latex?t_%7B%5Ctext%7Bcycle%7D%7D"> drops below the timescale on which innovation can actually occur (<img src="https://latex.codecogs.com/png.latex?t_%7B%5Ctext%7Binnovate%7D%7D">), and the system faces what they call <em>asymptotic burnout</em>: collapse driven by the superlinear dynamics that previously drove growth.</p>
<p>Their proposed resolution is <em>homeostatic awakening</em> — the civilization consciously reorienting away from unbounded growth toward persistence and well-being. Which is to say, the civilization invents a new objective function. This is presented as a resolution to the Fermi paradox: either civilizations burn out (short-lived), or they pivot to homeostasis and become quiet (long-lived but difficult to detect). Either way, no galaxy-spanning Type III civilizations.</p>
<p>There is an older version of this argument, without the scaling-law formalism. <span class="citation" data-cites="Tainter2003Resource">Tainter et al. (2003)</span> (and Tainter’s earlier <em>Collapse of Complex Societies</em>) propose that complex societies face diminishing marginal returns to <em>complexity itself</em>: each additional layer of coordination, bureaucracy, or infrastructure costs more and delivers less, until the society can no longer sustain its own overhead and simplifies — voluntarily or otherwise. Wong and Bartlett’s burnout is the superlinear-dynamics version of Tainter’s complexity trap: the system does not merely stagnate (as in Tainter) but actively accelerates into crisis. Both end in simplification; the mechanism differs.</p>
<p>I find this idea interesting as a <em>question organizer</em> even if the quantitative model is schematic. The key variable is <img src="https://latex.codecogs.com/png.latex?%5Cbeta">: whether the scaling exponent for a given system sits above or below 1 determines whether it exhibits increasing returns (and eventual crisis) or diminishing returns (and eventual stagnation). And the value of <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> is not the same for all systems.</p>
</section>
<section id="ai-coase" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="ai-coase"><span class="header-section-number">4</span> AI and the Coasean boundary</h2>
<div id="fig-hungry-leviathan" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-hungry-leviathan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/hungry_leviathan_RP-P-BI-128.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: "><img src="https://danmackinlay.name/images/hungry_leviathan_RP-P-BI-128.avif" id="fig-hungry-leviathan" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-hungry-leviathan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2
</figcaption>
</figure>
</div>
<p>If the Coasean boundary of the firm is set by the cost of internal coordination relative to market transaction costs, and if AI agents radically reduce <em>both</em>, what happens to the equilibrium firm size?</p>
<p>There are at least two competing effects. <span class="citation" data-cites="Korinek2025Concentrating">Korinek and Vipra (2025)</span> argue that AI itself exhibits enormous economies of scale — training frontier models requires billions of dollars in compute, and the resulting capabilities can be deployed at near-zero marginal cost. This pushes toward concentration: a few firms with the best models capture most of the value.</p>
<p>But there is a countervailing force. If AI agents can handle complex contracting, negotiation, and monitoring at scale — <a href="../notebook/ai_institutions.html">Coasean bargaining at scale</a>, as it were — then transaction costs on the <em>market</em> side also collapse. The “Headless Firm” model <span class="citation" data-cites="Harre2025Coasean">(Harré and Ormerod 2025)</span> proposes that agentic AI changes how coordination costs scale: from <img src="https://latex.codecogs.com/png.latex?O(n%5E2)"> in human-managed organisations to <img src="https://latex.codecogs.com/png.latex?O(n)"> in protocol-mediated agent systems. If so, we might see a simultaneous <em>expansion</em> of what large organisations can coordinate and <em>shrinkage</em> of what needs to be inside an organisation at all.<sup>2</sup> Which effect dominates?</p>
<p><span class="citation" data-cites="Wolpert2025Computational">Wolpert and Harper (2025)</span> model societies as computers, where the computational power available depends on how agents are organised. If AI lets us reorganise the <em>topology</em> of economic interaction — not just doing the same coordination faster, but enabling coordination structures that were previously impossible — then the scaling exponent itself might change. We might move the boundary between the firm-like regime (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cleq%201">) and the city-like regime (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201">).</p>
<p>I don’t know which effect wins. Possibly both at once, in different domains. But the stakes are those of <span class="citation" data-cites="Wong2022Asymptotic">Wong and Bartlett (2022)</span>: if we push <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> high enough, we push <img src="https://latex.codecogs.com/png.latex?t_%7B%5Ctext%7Bcycle%7D%7D"> down, and we accelerate the approach toward burnout — or toward the moment we have to decide whether homeostatic awakening is something we can actually do.</p>
</section>
<section id="leviathan" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="leviathan"><span class="header-section-number">5</span> Where does this leave the Singleton?</h2>
<p>The scaling laws give us a tentative answer to the opening question. The world probably does <em>not</em> converge on one system, because the scaling exponents differ across levels of organisation. Cities generate superlinear returns, but the containers that govern cities — firms and states — face diminishing returns to scale. The Leviathan is bounded.</p>
<p>But that conclusion rests on current coordination technology. If AI changes the exponents — if it makes large-scale coordination cheap enough that firms or states start exhibiting city-like superlinear scaling — then the attractor shifts. The singleton becomes more plausible, or at least the threshold for “too big” moves upward.</p>
<p>There is a flavour of <a href="../notebook/technological_determinism.html">technological determinism</a> to excavate here — the idea that coordination technology <em>determines</em> the viable scale of organisation, and therefore the shape of political and economic life. If the printing press enabled the nation state, and if the internet enabled global supply chains and platform monopolies, then AI-mediated coordination might enable — or force? — another jump in the viable scale of collective action. The scaling exponents are not constants of nature; they are artefacts of the coordination technology available. Change the technology, change <img src="https://latex.codecogs.com/png.latex?%5Cbeta">, change the equilibrium size of everything.</p>
<p>This is not quite hard technological determinism — we are not claiming the technology <em>uniquely</em> determines outcomes. <span class="citation" data-cites="Alesina2003Size">Alesina and Spolaore (2003)</span> already argues that the equilibrium depends on the international security environment, trade openness, and democratic norms, not just coordination costs. But the scaling exponents set the <em>envelope</em> of what is possible, and the technology moves the envelope. Soft determinism, maybe. The tools constrain the menu; the polity still orders from it.</p>
<p>Then we are back to Wong and Bartlett’s question: a civilization with a sufficiently high <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> will face accelerating singularities. Is homeostatic awakening something that can be engineered? Or is it the kind of thing a civilization only stumbles into after a sufficiently frightening brush with burnout? And — the <a href="../notebook/multi_scale_agency.html">multi-level agency</a> angle — <em>who</em> does the awakening? The civilization, the nation, the firm? A single God-like uploaded CEO? Roco’s basilisk? If the scaling exponents differ across levels, the incentive to keep growing differs across levels too. The firm-level agents face sublinear returns and might happily plateau; the city-level dynamics keep accelerating regardless of what any individual agent wants. The homeostatic awakening, if it happens, has to happen at the level where <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201"> — which may not be the level at which anyone has decision-making authority.</p>
<p>🚧TODO🚧: dig into <span class="citation" data-cites="Wolpert2025Computational">Wolpert and Harper (2025)</span> — their “society as computer” model feels relevant.</p>
<p>🚧TODO🚧: the <a href="../notebook/thermodynamics_of_life.html">biological scaling literature</a> deserves its own subsection. Kleiber’s law (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%203/4"> for metabolic rate) and the West-Brown-Enquist model are the template that Wong and Bartlett are generalising.</p>
<p>🚧TODO🚧: chase Anderson’s <em>Imagined Communities</em> — there’s a history-of-coordination-technology story here that connects the printing press → nation state → telegraph → empire → internet → platform monopoly → AI → ??? sequence. Each technology changes something — the coordination exponent? — for a different level of organisation.</p>
</section>
<section id="references" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="references"><span class="header-section-number">6</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Alesina2003Size" class="csl-entry">
Alesina, and Spolaore. 2003. <em>The Size of Nations</em>.
</div>
<div id="ref-Arcaute2015Constructing" class="csl-entry">
Arcaute, Hatna, Ferguson, et al. 2015. <span>“<a href="https://doi.org/10.1098/rsif.2014.0745">Constructing Cities, Deconstructing Scaling Laws</a>.”</span> <em>Journal of the Royal Society Interface</em>.
</div>
<div id="ref-Balland2020Complex" class="csl-entry">
Balland, Jara-Figueroa, Petralia, et al. 2020. <span>“<a href="https://doi.org/10.1038/s41562-019-0803-3">Complex Economic Activities Concentrate in Large Cities</a>.”</span> <em>Nature Human Behaviour</em>.
</div>
<div id="ref-Bettencourt2013Origins" class="csl-entry">
Bettencourt. 2013. <span>“<a href="https://doi.org/10.1126/science.1235823">The Origins of Scaling in Cities</a>.”</span> <em>Science</em>.
</div>
<div id="ref-Bettencourt2016Urban" class="csl-entry">
Bettencourt, and Lobo. 2016. <span>“<a href="https://doi.org/10.1098/rsif.2016.0005">Urban Scaling in Europe</a>.”</span> <em>Journal of the Royal Society Interface</em>.
</div>
<div id="ref-Bettencourt2007Growth" class="csl-entry">
Bettencourt, Lobo, Helbing, et al. 2007. <span>“<a href="https://doi.org/10.1073/pnas.0610172104">Growth, Innovation, Scaling, and the Pace of Life in Cities</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Bettencourt2010Urban" class="csl-entry">
Bettencourt, Lobo, Strumsky, et al. 2010. <span>“<a href="https://doi.org/10.1371/journal.pone.0013541">Urban Scaling and Its Deviations: Revealing the Structure of Wealth, Innovation and Crime Across Cities</a>.”</span> <em>PLOS ONE</em>.
</div>
<div id="ref-Bowles2011Liberal" class="csl-entry">
Bowles. 2011. <span>“Is Liberal Society a Parasite on Tradition?”</span> <em>Philosophy and Public Affairs</em>.
</div>
<div id="ref-Bowles1998Efficient" class="csl-entry">
Bowles, and Gintis. 1998. <span>“Efficient Redistribution: New Rules for Markets, States and Communities.”</span> <em>Recasting Egalitarianism: New Rules for Communities, States and Markets</em>.
</div>
<div id="ref-Brill2024Neural" class="csl-entry">
Brill. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2412.07942">Neural Scaling Laws Rooted in the Data Distribution</a>.”</span>
</div>
<div id="ref-Coase1937Nature" class="csl-entry">
Coase. 1937. <span>“<a href="https://doi.org/10.1111/j.1468-0335.1937.tb00002.x">The Nature of the Firm</a>.”</span> <em>Economica</em>.
</div>
<div id="ref-Cottineau2017Diverse" class="csl-entry">
Cottineau, Hatna, Arcaute, et al. 2017. <span>“<a href="https://doi.org/10.1016/j.compenvurbsys.2016.04.006">Diverse Cities or the Systematic Paradox of Urban Scaling Laws</a>.”</span> <em>Computers, Environment and Urban Systems</em>, Spatial analysis with census data: emerging issues and innovative approaches,.
</div>
<div id="ref-Douglas2025Progress" class="csl-entry">
Douglas, and Verstyuk. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2501.17894">Progress in Artificial Intelligence and Its Determinants</a>.”</span>
</div>
<div id="ref-Grace2013Algorithmic" class="csl-entry">
Grace. 2013. <span>“<a href="https://intelligence.org/files/AlgorithmicProgress.pdf">Algorithmic Progress in Six Domains</a>.”</span>
</div>
<div id="ref-Grossman2022Elusive" class="csl-entry">
Grossman, and Oberfield. 2022. <span>“<a href="https://doi.org/10.1146/annurev-economics-080921-103046">The Elusive Explanation for the Declining Labor Share</a>.”</span> <em>Annual Review of Economics</em>.
</div>
<div id="ref-Harre2025Coasean" class="csl-entry">
Harré, and Ormerod. 2025. <span>“<a href="https://doi.org/10.2139/ssrn.6503058">The Coasean Reversal: AI and the Boundaries of the Firm — a Theoretical Framework Synthesising Marshall, Coase, Aoki, and Kauffman</a>.”</span>
</div>
<div id="ref-Hoffmann2022Training" class="csl-entry">
Hoffmann, Borgeaud, Mensch, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2203.15556">Training Compute-Optimal Large Language Models</a>.”</span>
</div>
<div id="ref-Hooker2020Hardware" class="csl-entry">
Hooker. 2020. <span>“<a href="http://arxiv.org/abs/2009.06489">The Hardware Lottery</a>.”</span> <em>arXiv:2009.06489 [Cs]</em>.
</div>
<div id="ref-Jorda2019Rate" class="csl-entry">
Jordà, Knoll, Kuvshinov, et al. 2019. <span>“<a href="https://doi.org/10.1093/qje/qjz012">The Rate of Return on Everything, 1870–2015</a>.”</span> <em>The Quarterly Journal of Economics</em>.
</div>
<div id="ref-Kaplan2020Scaling" class="csl-entry">
Kaplan, McCandlish, Henighan, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.2001.08361">Scaling Laws for Neural Language Models</a>.”</span>
</div>
<div id="ref-Korinek2025Concentrating" class="csl-entry">
Korinek, and Vipra. 2025. <span>“<a href="https://doi.org/10.1093/epolic/eiae057">Concentrating Intelligence: Scaling and Market Structure in Artificial Intelligence</a>.”</span> <em>Economic Policy</em>.
</div>
<div id="ref-Kuhnert2006Scaling" class="csl-entry">
Kühnert, Helbing, and West. 2006. <span>“<a href="https://doi.org/10.1016/j.physa.2006.01.058">Scaling Laws in Urban Supply Networks</a>.”</span> <em>Physica A: Statistical Mechanics and Its Applications</em>, Information and Material Flows in Complex Networks,.
</div>
<div id="ref-Lehner2024Monopsony" class="csl-entry">
Lehner, Parolin, Pignatti, et al. 2024. <span>“<a href="https://doi.org/10.2139/ssrn.4975258">Monopsony Power and Poverty: The Consequences of Walmart Supercenter Openings</a>.”</span> <em>SSRN Electronic Journal</em>.
</div>
<div id="ref-Leishman2021Relationships" class="csl-entry">
Leishman, Bond-Smith, Liang, et al. 2021. <span>“<a href="https://www.ahuri.edu.au/research/final-reports/357">Relationships Between Metropolitan, Satellite and Regional City Size, Spatial Context and Economic Productivity</a>.”</span> <em>AHURI Final Report</em>.
</div>
<div id="ref-MacLeod1999Cassini" class="csl-entry">
MacLeod. 1999. <em>The Cassini Division</em>.
</div>
<div id="ref-Moore1993Social" class="csl-entry">
Moore. 1993. <em>Social Origins of Dictatorship and Democracy: Lord and Peasant in the Making of the Modern World</em>.
</div>
<div id="ref-Pollard2021Rise" class="csl-entry">
Pollard. 2021. <span>“<a href="https://prospect.org/api/content/e8a23c4c-7b8d-11eb-9b74-1244d5f7c7c6/">The Rise of the Private Police</a>.”</span> <em>The American Prospect</em>.
</div>
<div id="ref-PrietoCuriel2022Scaling" class="csl-entry">
Prieto Curiel, Cabrera-Arnau, and Bishop. 2022. <span>“<a href="https://doi.org/10.3389/fphy.2022.858307">Scaling Beyond Cities</a>.”</span> <em>Frontiers in Physics</em>.
</div>
<div id="ref-Rybski2019Urban" class="csl-entry">
Rybski, Arcaute, and Batty. 2019. <span>“<a href="https://doi.org/10.1177/2399808319886125">Urban Scaling Laws</a>.”</span> <em>Environment and Planning B: Urban Analytics and City Science</em>.
</div>
<div id="ref-Scharf2021Ascent" class="csl-entry">
Scharf. 2021. <em>The Ascent of Information: Books, Bits, Genes, Machines, and Life’s Unending Algorithm</em>.
</div>
<div id="ref-Sekeris2012Tragedy" class="csl-entry">
Sekeris. 2012. <span>“<a href="http://petros.sekeris.org/violentcommons.pdf">The Tragedy of the Commons in a Violent World</a>.”</span> Working Paper 1213.
</div>
<div id="ref-Stross2006Accelerando" class="csl-entry">
Stross, Charles. 2006. <em>Accelerando</em>.
</div>
<div id="ref-Stross2014Neptunes" class="csl-entry">
Stross, Charles. 2014. <em>Neptune’s Brood: A Space Opera</em>.
</div>
<div id="ref-Tainter2003Resource" class="csl-entry">
Tainter, Allen, Little, et al. 2003. <span>“<a href="http://www.ecologyandsociety.org/vol7/iss3/art4/">Resource Transitions and Energy Gain: Contexts of Organization</a>.”</span> <em>Conservation Ecology</em>.
</div>
<div id="ref-Watts2006Blindsight" class="csl-entry">
Watts. 2006. <em>Blindsight</em>.
</div>
<div id="ref-West2017Scale" class="csl-entry">
West. 2017. <em>Scale: The Universal Laws of Growth, Innovation, Sustainability, and the Pace of Life in Organisms, Cities, Economies, and Companies</em>.
</div>
<div id="ref-Wolpert2025Computational" class="csl-entry">
Wolpert, and Harper. 2025. <span>“<a href="https://doi.org/10.31219/osf.io/qj83z">The Computational Power of a Human Society: A New Model of Social Evolution</a>.”</span>
</div>
<div id="ref-Wong2022Asymptotic" class="csl-entry">
Wong, and Bartlett. 2022. <span>“<a href="https://doi.org/10.1098/rsif.2022.0029">Asymptotic Burnout and Homeostatic Awakening: A Possible Solution to the Fermi Paradox?</a>”</span> <em>Journal of The Royal Society Interface</em>.
</div>
<div id="ref-Xexeo2024Economic" class="csl-entry">
Xexéo, Braida, Parreiras, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2405.17637">The Economic Implications of Large Language Model Selection on Earnings and Return on Investment: A Decision Theoretic Model</a>.”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The “dataome” being the accumulated information layer outside biological organisms — books, architecture, computers, the internet. A coinage from <span class="citation" data-cites="Scharf2021Ascent">Scharf (2021)</span>.↩︎</p></li>
<li id="fn2"><p>Stross’s <em>Accelerando</em> <span class="citation" data-cites="Stross2006Accelerando">(Charles. Stross 2006)</span> is the science-fictional limit case: articles of incorporation go Turing-complete, firms become autonomous agents recursively spawning subsidiaries, and the distinction between firm and market dissolves into a computational froth. Silly? Maybe. But “AI agents negotiate contracts at scale” is a less colourful description of the same dynamic.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>buzzword</category>
  <category>cooperation</category>
  <category>culture</category>
  <category>design</category>
  <category>DIY</category>
  <category>economics</category>
  <category>housing</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>insurgency</category>
  <category>making things</category>
  <category>policy</category>
  <category>spatial</category>
  <category>straya</category>
  <category>the rather superior sort of city</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/returns_to_leviathan.html</guid>
  <pubDate>Mon, 20 Apr 2026 11:32:16 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/wall_scaling_sm_RP-P-1909-6331.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Wisdom from the madness of crowds</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/wisdom_from_madness_of_crowds.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-monkey-markets" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-monkey-markets-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/monkey_markets_RP-P-OB-9428.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/monkey_markets_RP-P-OB-9428.avif" id="fig-monkey-markets" class="figure illustration full img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-monkey-markets-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Starting questions for better formulations of social belief.</p>
<section id="why-do-i-believe-things" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="why-do-i-believe-things"><span class="header-section-number">1</span> Why do I believe things?</h2>
<p>Look, obviously there is a lot going on with human beliefs in practice. I’m interested in the operationalisation in <span class="citation" data-cites="Hyland2025Variational">Hyland and Albarracin (2025)</span>, which treats beliefs as mechanisms that do work: they take in observations and produce predictions and thus can make decisions. They are also objects in themselves, which can be intrinsically valued for their content, and can be shared and imported from others. As such he operationalizes the program of the pragmatists <span class="citation" data-cites="Dewey1929Quest James1897Will James1907Pragmatism James1909Meaning">(Dewey 1929; James 1897, 1907, 1909)</span> who proposed that we can understand beliefs in terms of their “cash value”— the practical value we get from holding them.<sup>1</sup> Along the way he also operationalizes the “docility” of <span class="citation" data-cites="Simon1990Mechanism">Herbert A. Simon (1990)</span>, which is the idea that bounded agents can rationally import beliefs from others rather than re-deriving them. Not only that, but he makes it <a href="../notebook/variational_inference.html">variational</a><sup>2</sup> which means that we get to make this a principled <a href="../notebook/agency_bounded_compute.html">bounded agent</a> model, and we can reason about the trade-offs between copying beliefs and doing our own reasoning.</p>
<p>This seems like a generally good idea, although I think we could take it farther. We can, for example, treat beliefs formally as <em><a href="../notebook/causality_mechanization_decisions.html">mechanisms in the sense of mechanized graphs</a></em>.</p>
<div id="fig-docile-believer" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-docile-believer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="docile-believer.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: My belief B_{\text{dan}}, depicted as a mechanism. On the left I observe the world, recording two cause-and-effect pairs C_i \to E_i that inform my belief (dashed). On the right, Alice and Bob have beliefs which influence mine along the dash-dotted edges. This is my docility, my propensity to copy a neighbour’s belief rather than formulating it from evidence alone. The belief drives a decision C_3, the intervention \mathrm{do}(C_3), chosen to bring about a desired effect E_3 whose worth to me is the utility U."><img src="https://danmackinlay.name/notebook/wisdom_from_madness_of_crowds_files/mediabag/docile-believer.svg" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-docile-believer-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: My belief <img src="https://latex.codecogs.com/png.latex?B_%7B%5Ctext%7Bdan%7D%7D">, depicted <a href="../notebook/causality_mechanization_decisions.html">as a <em>mechanism</em></a>. On the left I observe the world, recording two cause-and-effect pairs <img src="https://latex.codecogs.com/png.latex?C_i%20%5Cto%20E_i"> that inform my belief (dashed). On the right, Alice and Bob have beliefs which influence mine along the dash-dotted edges. This is my <em>docility</em>, my propensity to copy a neighbour’s belief rather than formulating it from evidence alone. The belief drives a decision <img src="https://latex.codecogs.com/png.latex?C_3">, the intervention <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Bdo%7D(C_3)">, chosen to bring about a desired effect <img src="https://latex.codecogs.com/png.latex?E_3"> whose worth to me is the utility <img src="https://latex.codecogs.com/png.latex?U">.
</figcaption>
</figure>
</div>
<p>I think we can push this further and come to a better model of how to be an effective social learner.</p>
</section>
<section id="certainty" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="certainty"><span class="header-section-number">2</span> Certainty</h2>
<p>I like to claim to be a <a href="../notebook/bayes_is_not_enough.html">Good Bayesian</a> and thus, that I never Believe Things with absolute certainty. No, I assert, my refined intellect is too well schooled in the arts of Optimal Updating, and too honed in the arts of subjective probability, to do any of that sordid <em>absolute belief</em> business. Rather than “facts”, I virtuously hold many hypotheses in mind, weighted by my current prior probability— with the sole and righteous exception of the mechanisms of Bayesian updating itself, of course. Those I do hold to be 100% true.</p>
<p>We will, of course, realise this is bollocks. Obviously <em>some</em> things I treat as if they were simply true. I believe in gravity, and the existence of apples, and so on. I might conceivably be shown, eventually, to be wrong about these things. But I will, in that case, be totally surprised, a little embarrassed, and will have made no long-shot contingency plans against the absence of gravity or apples; not so much as a small amulet with the face of Newton to bless me.</p>
<p>Indeed, <a href="../notebook/bayes_is_not_enough.html">full Bayesianism</a> is impossible (and worse than that, even garden variety Bayes is just plain <a href="../notebook/bayes_complexity.html">difficult</a>), for a <a href="../notebook/agency_bounded_compute.html">bounded agent like me</a>.</p>
</section>
<section id="social-belief-graphs" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="social-belief-graphs"><span class="header-section-number">3</span> Social belief graphs</h2>
</section>
<section id="pathwise-fairness-in-social-belief-graphs" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="pathwise-fairness-in-social-belief-graphs"><span class="header-section-number">4</span> Pathwise fairness in social belief graphs</h2>
</section>
<section id="incoming" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="incoming"><span class="header-section-number">5</span> Incoming</h2>
<ul>
<li><p>Can we do something like the inductive market of <span class="citation" data-cites="Garrabrant2020Logical">Garrabrant et al. (2020)</span>?</p></li>
<li><p><span class="citation" data-cites="Conitzer2013Maximum">Conitzer (2013)</span>: social networks, social choice and statistical estimation unified. Spun out into a notebook on AI Social choice.</p></li>
<li><p><a href="https://eq-network.org/?ref=danmackinlay.name">Equilibria Network - Designing New Forms Of Collective Intelligence</a></p></li>
<li><p><a href="https://www.forethought.org/research/ai-for-ai-for-epistemics?ref=danmackinlay.name">AI for AI for Epistemics</a></p>
<blockquote class="blockquote">
<p>AI-powered tools and services that help people figure out what’s true (“AI for epistemics”) could matter a lot. As R&amp;D is increasingly automated, AI systems will play a larger role in the process of developing such AI-based epistemic tools. This has important implications. Whoever is willing to devote sufficient compute will be able to build strong versions of the tools, quickly. Eventually, the hard part won’t be building useful systems, but making sure people trust the right ones, and making sure that they are truth-tracking even in domains where that’s hard to verify. We can do some things now to prepare. Incumbency effects mean that shaping the early versions for the better could have persistent benefits. Helping build appetite among socially motivated actors with deep pockets could enable the benefits to come online sooner, and in safer hands. And in some cases, we can identify particular things that seem likely to be bottlenecks later, and work on those directly.</p>
</blockquote></li>
</ul>
</section>
<section id="references" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="references"><span class="header-section-number">6</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Acemoglu2006Learning" class="csl-entry">
Acemoglu, Chernozhukov, and Yildiz. 2006. <span>“<a href="https://doi.org/10.3386/w12648">Learning and Disagreement in an Uncertain World</a>.”</span> Working Paper 12648. Working Paper Series.
</div>
<div id="ref-Acemoglu2011Opinion" class="csl-entry">
Acemoglu, and Ozdaglar. 2011. <span>“<a href="https://doi.org/10.1007/s13235-010-0004-1">Opinion Dynamics and Learning in Social Networks</a>.”</span> <em>Dynamic Games and Applications</em>.
</div>
<div id="ref-Aleta2019Dynamics" class="csl-entry">
Aleta, and Moreno. 2019. <span>“<a href="https://doi.org/10.1140/epjds/s13688-019-0200-1">The Dynamics of Collective Social Behavior in a Crowd Controlled Game</a>.”</span> <em>EPJ Data Science</em>.
</div>
<div id="ref-Almaatouq2021When" class="csl-entry">
Almaatouq, Rahimian, Burton, et al. 2021. <span>“<a href="http://arxiv.org/abs/2006.12471">When Social Influence Promotes the Wisdom of Crowds</a>.”</span> <em>arXiv:2006.12471 [Physics, Stat]</em>.
</div>
<div id="ref-Arguedas2022Echo" class="csl-entry">
Arguedas, Robertson, Fletche, et al. 2022. <span>“<a href="https://royalsociety.org/-/media/policy/projects/online-information-environment/oie-echo-chambers.pdf">Echo Chambers, Filter Bubbles, and Polarisation: A Literature Review</a>.”</span>
</div>
<div id="ref-Arpit2017Closer" class="csl-entry">
Arpit, Jastrzębski, Ballas, et al. 2017. <span>“<a href="https://doi.org/10.48550/arXiv.1706.05394">A Closer Look at Memorization in Deep Networks</a>.”</span>
</div>
<div id="ref-Ashurst2022Why" class="csl-entry">
Ashurst, Carey, Chiappa, et al. 2022. <span>“<a href="https://doi.org/10.1609/aaai.v36i9.21182">Why Fair Labels Can Yield Unfair Predictions: Graphical Conditions for Introduced Unfairness</a>.”</span> In <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Atanasov2015Distilling" class="csl-entry">
Atanasov, Rescober, Stone, et al. 2015. <span>“<a href="https://doi.org/10.1287/mnsc.2015.2374">Distilling the Wisdom of Crowds: Prediction Markets Versus Prediction Polls</a>.”</span> <em>Academy of Management Proceedings</em>.
</div>
<div id="ref-Bergemann2003Robust" class="csl-entry">
Bergemann, and Morris. 2003. <span>“<a href="https://ideas.repec.org//p/cla/levrem/666156000000000035.html">Robust Mechanism Design</a>.”</span> <em>Levine’s Bibliography</em>, Levine’s Bibliography,.
</div>
<div id="ref-Borondo2014Each" class="csl-entry">
Borondo, Borondo, Rodriguez-Sickert, et al. 2014. <span>“<a href="https://doi.org/10.1038/srep03784">To Each According to Its Degree: The Meritocracy and Topocracy of Embedded Markets</a>.”</span> <em>Scientific Reports</em>.
</div>
<div id="ref-Boutilier2015Optimal" class="csl-entry">
Boutilier, Caragiannis, Haber, et al. 2015. <span>“<a href="https://doi.org/10.1016/j.artint.2015.06.003">Optimal Social Choice Functions: A Utilitarian View</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Buleshnyi2025PESHAP" class="csl-entry">
Buleshnyi, Loftus, and Hansen. 2025. <span>“<a href="https://openreview.net/forum?id=xp7cgFrlUM">PE-SHAP: Causally Interpretable Path-Wise Shapley Explanations</a>.”</span> In.
</div>
<div id="ref-Carvalho2010Sharing" class="csl-entry">
Carvalho. 2010. <span>“Sharing Rewards Based on Subjective Opinions.”</span>
</div>
<div id="ref-Conitzer2013Maximum" class="csl-entry">
Conitzer. 2013. <span>“<a href="https://doi.org/10.1109/Allerton.2013.6736702">The Maximum Likelihood Approach to Voting on Social Networks</a>.”</span> In <em>2013 51st Annual Allerton Conference on Communication, Control, and Computing (Allerton)</em>.
</div>
<div id="ref-Danan2016Robust" class="csl-entry">
Danan, Gajdos, Hill, et al. 2016. <span>“<a href="https://doi.org/10.1257/aer.20150678">Robust Social Decisions</a>.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Dasgupta2013Endogenous" class="csl-entry">
Dasgupta, and Ghosh. 2013. <span>“Crowdsourced Judgement Elicitation with Endogenous Proficiency.”</span> In <em>Proceedings of the 22nd International World Wide Web Conference (WWW)</em>.
</div>
<div id="ref-Dawid1979Maximum" class="csl-entry">
Dawid, and Skene. 1979. <span>“<a href="https://crowdsourcing-class.org/readings/downloads/ml/EM.pdf">Maximum Likelihood Estimation of Observer Error‐Rates Using the EM Algorithm</a>.”</span> <em>Journal of the Royal Statistical Society Series C</em>.
</div>
<div id="ref-Deletang2024Language" class="csl-entry">
Delétang, Ruoss, Duquenne, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2309.10668">Language Modeling Is Compression</a>.”</span>
</div>
<div id="ref-Dewey1929Quest" class="csl-entry">
Dewey. 1929. <em>The Quest for Certainty: A Study of the Relation of Knowledge and Action</em>.
</div>
<div id="ref-Diakonikolas2023Algorithmic" class="csl-entry">
Diakonikolas, and Kane. 2023. <em><a href="https://doi.org/10.1017/9781108943161">Algorithmic High-Dimensional Robust Statistics</a></em>.
</div>
<div id="ref-Drolsbach2024Community" class="csl-entry">
Drolsbach, Solovev, and Pröllochs. 2024. <span>“<a href="https://doi.org/10.1093/pnasnexus/pgae217">Community Notes Increase Trust in Fact-Checking on Social Media</a>.”</span> Edited by David Rand. <em>PNAS Nexus</em>.
</div>
<div id="ref-Duque2025Advantage" class="csl-entry">
Duque, Aghajohari, Cooijmans, et al. 2025. <span>“<a href="https://openreview.net/forum?id=QFO1asgas2">Advantage Alignment Algorithms</a>.”</span> In.
</div>
<div id="ref-Everitt2021Agent" class="csl-entry">
Everitt, Carey, Langlois, et al. 2021. <span>“<a href="https://doi.org/10.1609/aaai.v35i13.17368">Agent Incentives: A Causal Perspective</a>.”</span> In <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Fluri2024Evaluating" class="csl-entry">
Fluri, Paleka, and Tramèr. 2024. <span>“<a href="http://arxiv.org/abs/2306.09983">Evaluating Superhuman Models with Consistency Checks</a>.”</span> In.
</div>
<div id="ref-Foerster2018Learning" class="csl-entry">
Foerster, Chen, Al-Shedivat, et al. 2018. <span>“<a href="http://arxiv.org/abs/1709.04326">Learning with Opponent-Learning Awareness</a>.”</span> In <em>Proceedings of the 17th International Conference on Autonomous Agents and MultiAgent Systems</em>. AAMAS ’18.
</div>
<div id="ref-Galanis2024Information" class="csl-entry">
Galanis, Ioannou, and Kotronis. 2024. <span>“<a href="https://doi.org/10.1093/restud/rdae009">Information Aggregation Under Ambiguity: Theory and Experimental Evidence</a>.”</span> <em>Review of Economic Studies</em>.
</div>
<div id="ref-GarrabrantLogical" class="csl-entry">
Garrabrant, Benson-Tilsen, Critch, et al. 2016. <span>“Logical Induction (Abridged).”</span>
</div>
<div id="ref-Garrabrant2020Logical" class="csl-entry">
———, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.1609.03543">Logical Induction</a>.”</span>
</div>
<div id="ref-Gneiting2007Strictly" class="csl-entry">
Gneiting, and Raftery. 2007. <span>“<a href="https://doi.org/10.1198/016214506000001437">Strictly Proper Scoring Rules, Prediction, and Estimation</a>.”</span> <em>Journal of the American Statistical Association</em>.
</div>
<div id="ref-Golub2011Network" class="csl-entry">
Golub, and Jackson. 2011. <span>“<a href="https://doi.org/10.2139/ssrn.1784542">Network Structure and the Speed of Learning: Measuring Homophily Based on Its Consequences</a>.”</span> SSRN Scholarly Paper ID 1784542.
</div>
<div id="ref-Golub2012How" class="csl-entry">
———. 2012. <span>“<a href="https://doi.org/10.1093/qje/qjs021">How Homophily Affects the Speed of Learning and Best-Response Dynamics</a>.”</span> <em>The Quarterly Journal of Economics</em>.
</div>
<div id="ref-Huang2024Learning" class="csl-entry">
Huang, Strack, and Tamuz. 2024. <span>“<a href="https://doi.org/10.3982/ECTA20806">Learning in Repeated Interactions on Networks</a>.”</span> In <em>Econometrica</em>.
</div>
<div id="ref-Hyland2025Variational" class="csl-entry">
Hyland, and Albarracin. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2509.17957">On the Variational Costs of Changing Our Minds</a>.”</span>
</div>
<div id="ref-ShahanaIbrahim2023" class="csl-entry">
Ibrahim. 2023. <span>“<a href="https://ir.library.oregonstate.edu/concern/graduate_thesis_or_dissertations/bv73c790q">Learning from Crowdsourced Noisy Annotations: From Dawid-Skene to Deep Neural Networks</a>.”</span>
</div>
<div id="ref-Ince2025Constructing" class="csl-entry">
Ince, Moresco, Peri, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2503.03471">Constructing Elicitable Risk Measures</a>.”</span>
</div>
<div id="ref-James1890Principles" class="csl-entry">
James. 1890. <em>The Principles of Psychology</em>.
</div>
<div id="ref-James1897Will" class="csl-entry">
———. 1897. <em><a href="https://www.gutenberg.org/ebooks/26659">The Will to Believe and Other Essays in Popular Philosophy</a></em>.
</div>
<div id="ref-James1907Pragmatism" class="csl-entry">
———. 1907. <em><a href="https://www.gutenberg.org/ebooks/5116">Pragmatism: A New Name for Some Old Ways of Thinking</a></em>.
</div>
<div id="ref-James1909Meaning" class="csl-entry">
———. 1909. <em><a href="https://www.gutenberg.org/ebooks/5117">The Meaning of Truth: A Sequel to “Pragmatism”</a></em>.
</div>
<div id="ref-Kalai1993Rational" class="csl-entry">
Kalai, and Lehrer. 1993. <span>“<a href="https://doi.org/10.2307/2951492">Rational Learning Leads to Nash Equilibrium</a>.”</span> <em>Econometrica</em>.
</div>
<div id="ref-Kamenica2012Behavioral" class="csl-entry">
Kamenica. 2012. <span>“<a href="https://doi.org/10.1146/annurev-economics-080511-110909">Behavioral Economics and Psychology of Incentives</a>.”</span> <em>Annual Review of Economics</em>.
</div>
<div id="ref-Kamenica2019Bayesian" class="csl-entry">
———. 2019. <span>“<a href="https://doi.org/10.1146/annurev-economics-080218-025739">Bayesian Persuasion and Information Design</a>.”</span> <em>Annual Review of Economics</em>.
</div>
<div id="ref-Kamenica2011Bayesian" class="csl-entry">
Kamenica, and Gentzkow. 2011. <span>“<a href="https://doi.org/10.1257/aer.101.6.2590">Bayesian Persuasion</a>.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Khan2024Scaling" class="csl-entry">
Khan, Willi, Kwan, et al. 2024. <span>“<a href="http://arxiv.org/abs/2312.12568">Scaling Opponent Shaping to High Dimensional Games</a>.”</span> In <em>Proceedings of the 23rd International Conference on Autonomous Agents and Multiagent Systems</em>. AAMAS ’24.
</div>
<div id="ref-Kilbertus2017Avoiding" class="csl-entry">
Kilbertus, Rojas Carulla, Parascandolo, et al. 2017. <span>“<a href="https://proceedings.neurips.cc/paper/2017/hash/f5f8590cd58a54e94377e6ae2eded4d9-Abstract.html">Avoiding Discrimination Through Causal Reasoning</a>.”</span> In <em>Advances in Neural Information Processing Systems 30</em>.
</div>
<div id="ref-Krestnikov2026Truth" class="csl-entry">
Krestnikov. 2026. <span>“<a href="https://doi.org/10.48550/arXiv.2603.11749">Truth as a Compression Artifact in Language Model Training</a>.”</span>
</div>
<div id="ref-Lalitha2014Social" class="csl-entry">
Lalitha, Javidi, and Sarwate. 2014. <span>“<a href="http://arxiv.org/abs/1410.4307">Social Learning and Distributed Hypothesis Testing</a>.”</span> <em>arXiv:1410.4307 [Cs, Math, Stat]</em>.
</div>
<div id="ref-Lanctot2025Soft" class="csl-entry">
Lanctot, Larson, Kaisers, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2411.00119">Soft Condorcet Optimization for Ranking of General Agents</a>.”</span>
</div>
<div id="ref-List2001Epistemic" class="csl-entry">
List, and Goodin. 2001. <span>“<a href="https://doi.org/10.1111/1467-9760.00128">Epistemic Democracy: Generalizing the Condorcet Jury Theorem</a>.”</span> <em>Journal of Political Philosophy</em>.
</div>
<div id="ref-Lowe2019Pitfalls" class="csl-entry">
Lowe, Foerster, Boureau, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1903.05168">On the Pitfalls of Measuring Emergent Communication</a>.”</span>
</div>
<div id="ref-Lu2022ModelFree" class="csl-entry">
Lu, Willi, Witt, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2205.01447">Model-Free Opponent Shaping</a>.”</span> In <em>Proceedings of the 39th International Conference on Machine Learning</em>.
</div>
<div id="ref-Mann2017Optimal" class="csl-entry">
Mann, and Helbing. 2017. <span>“<a href="https://doi.org/10.1073/pnas.1618722114">Optimal Incentives for Collective Intelligence</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Matejka2015Rational" class="csl-entry">
Matějka, and McKay. 2015. <span>“<a href="https://doi.org/10.1257/aer.20130047">Rational Inattention to Discrete Choices: A New Foundation for the Multinomial Logit Model</a>.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-MauraRivero2025Jackpot" class="csl-entry">
Maura-Rivero, Lanctot, Visin, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2501.19266">Jackpot! Alignment as a Maximal Lottery</a>.”</span>
</div>
<div id="ref-MauraRivero2025Utilityinspired" class="csl-entry">
Maura-Rivero, Nagpal, Patel, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2501.06248">Utility-Inspired Reward Transformations Improve Reinforcement Learning Training of Language Models</a>.”</span>
</div>
<div id="ref-Mercier2021Does" class="csl-entry">
Mercier, and Claidière. 2021. <span>“<a href="https://doi.org/10.1016/j.cognition.2021.104912">Does Discussion Make Crowds Any Wiser?</a>”</span> <em>Cognition</em>.
</div>
<div id="ref-Miller2005Eliciting" class="csl-entry">
Miller, Resnick, and Zeckhauser. 2005. <span>“Eliciting Informative Feedback: The Peer-Prediction Method.”</span> <em>Management Science</em>.
</div>
<div id="ref-Munos2024Nash" class="csl-entry">
Munos, Valko, Calandriello, et al. 2024. <span>“<a href="http://arxiv.org/abs/2312.00886">Nash Learning from Human Feedback</a>.”</span> In <em>Proceedings of the 41st International Conference on Machine Learning</em>. ICML’24.
</div>
<div id="ref-Nabi2024Fair" class="csl-entry">
Nabi, and Benkeser. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2408.01630">Fair Risk Minimization Under Causal Path-Specific Effect Constraints</a>.”</span>
</div>
<div id="ref-Nabi2018Fair" class="csl-entry">
Nabi, and Shpitser. 2018. <span>“<a href="https://doi.org/10.1609/aaai.v32i1.11553">Fair Inference on Outcomes</a>.”</span> <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Navajas2018Aggregated" class="csl-entry">
Navajas, Niella, Garbulsky, et al. 2018. <span>“<a href="https://doi.org/10.1038/s41562-017-0273-4">Aggregated Knowledge from a Small Number of Debates Outperforms the Wisdom of Large Crowds</a>.”</span> <em>Nature Human Behaviour</em>.
</div>
<div id="ref-Niemeyer2023How" class="csl-entry">
Niemeyer, Veri, Dryzek, et al. 2023. <span>“<a href="https://doi.org/10.1017/S0003055423000023">How Deliberation Happens: Enabling Deliberative Reason</a>.”</span> <em>American Political Science Review</em>.
</div>
<div id="ref-Olckers2024Manipulation" class="csl-entry">
Olckers, and Walsh. 2024. <span>“<a href="https://doi.org/10.1016/j.artint.2024.104196">Manipulation and Peer Mechanisms: A Survey</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Ren2005Consensus" class="csl-entry">
Ren, and Beard. 2005. <span>“Consensus Seeking in Multiagent Systems Under Dynamically Changing Interaction Topologies.”</span> <em>Automatic Control, IEEE Transactions on</em>.
</div>
<div id="ref-Simon1990Mechanism" class="csl-entry">
Simon, Herbert A. 1990. <span>“<a href="https://doi.org/10.1126/science.2270480">A Mechanism for Social Selection and Successful Altruism</a>.”</span> <em>Science</em>.
</div>
<div id="ref-Simon1997Administrative" class="csl-entry">
Simon, Herbert A. 1997. <em>Administrative Behavior</em>.
</div>
<div id="ref-Sims2003Implications" class="csl-entry">
Sims. 2003. <span>“<a href="https://doi.org/10.1016/S0304-3932(03)00029-1">Implications of Rational Inattention</a>.”</span> <em>Journal of Monetary Economics</em>.
</div>
<div id="ref-Siththaranjan2023Distributional" class="csl-entry">
Siththaranjan, Laidlaw, and Hadfield-Menell. 2023. <span>“<a href="http://arxiv.org/abs/2312.08358">Distributional Preference Learning: Understanding and Accounting for Hidden Context in RLHF</a>.”</span> In.
</div>
<div id="ref-Sudhir2025Marketbased" class="csl-entry">
Sudhir, and Tran-Thanh. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2503.05828">Market-Based Architectures in RL and Beyond</a>.”</span>
</div>
<div id="ref-Sunstein2014Wiser" class="csl-entry">
Sunstein, and Hastie. 2014. <em>Wiser: Getting Beyond Groupthink to Make Groups Smarter</em>.
</div>
<div id="ref-Trouche2014Arguments" class="csl-entry">
Trouche, Sander, and Mercier. 2014. <span>“<a href="http://papers.ssrn.com/abstract=2431710">Arguments, More Than Confidence, Explain the Good Performance of Reasoning Groups</a>.”</span> SSRN Scholarly Paper ID 2431710.
</div>
<div id="ref-Witkowski2012Robust" class="csl-entry">
Witkowski, and Parkes. 2012. <span>“<a href="https://doi.org/10.1609/aaai.v26i1.8261">A Robust Bayesian Truth Serum for Small Populations</a>.”</span> In <em>Proceedings of the Twenty-Sixth AAAI Conference on Artificial Intelligence</em>. AAAI’12.
</div>
<div id="ref-Yu2019MultiAgent" class="csl-entry">
Yu, Song, and Ermon. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1907.13220">Multi-Agent Adversarial Inverse Reinforcement Learning</a>.”</span> In.
</div>
<div id="ref-Zhang2016Spectral" class="csl-entry">
Zhang, Chen, Zhou, et al. 2016. <span>“<a href="http://jmlr.org/papers/v17/14-511.html">Spectral Methods Meet EM: A Provably Optimal Algorithm for Crowdsourcing</a>.”</span> In <em>Journal of Machine Learning Research</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Nice write-up for this in <a href="https://plato.stanford.edu/entries/truth-pragmatic/?ref=danmackinlay.name">Stanford Encyclopedia of Philosophy</a>.↩︎</p></li>
<li id="fn2"><p>In Bayes lingo <a href="../notebook/variational_inference.html"><em>variational</em> means <em>approximate in some principled way</em></a>. I am very sorry about this stupid terminology. It is too late now.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>adaptive</category>
  <category>agents</category>
  <category>bounded compute</category>
  <category>collective knowledge</category>
  <category>communicating</category>
  <category>cooperation</category>
  <category>democracy</category>
  <category>distributed</category>
  <category>economics</category>
  <category>game theory</category>
  <category>how do science</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>mind</category>
  <category>networks</category>
  <category>performative prediction problems</category>
  <category>provenance</category>
  <category>sociology</category>
  <category>standards</category>
  <category>virality</category>
  <guid>https://danmackinlay.name/notebook/wisdom_from_madness_of_crowds.html</guid>
  <pubDate>Sun, 19 Apr 2026 04:10:27 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/monkey_markets_RP-P-OB-9428.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Czechia</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/czechia.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-defenestration" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-defenestration-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/Prager.Fenstersturz.1618.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/Prager.Fenstersturz.1618.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-defenestration-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>I am re-visiting Czechia. I would like to write something worthy of the country, in the form of a slapdash, idiosyncratic, mildly contrarian take on a place I know poorly, while avoiding the hubris of thinking I can craft a <a href="https://www.gleech.org/tallinn?ref=danmackinlay.name">gleech-tier take</a>. I cannot afford to set my standard for quality that high any more than I can set my standards for time-management so low.</p>
<p>So! A slapdash, idiosyncratic, mildly contrarian one for you.</p>
<section id="people-and-things-from-czechia-i-knew-before" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="people-and-things-from-czechia-i-knew-before"><span class="header-section-number">1</span> People and things from Czechia I knew before</h2>
<p>Experimental condition: I primed myself before I arrived, writing down everyone and everything I have heard of that is Czech.</p>
<p>At time of writing I wonder: What will I miss? Which prejudices will I be demonstrating thereby? I am greatly curious.</p>
<p>I am attempting to triangulate the country from its more famous children.</p>
<p>Read down below this section for a post-arrival addendum of whom I missed.</p>
<section id="jan-hus" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="jan-hus"><span class="header-section-number">1.1</span> Jan Hus</h3>
<p>Religious reformer, fifteenth century. The Hussites are history heroes of mine, in that special way that long-dead people can be, where everything they did right was awesome and everything they did wrong was excusable, them being <em>of their time</em>. Jan Hus got the project of redoing Christianity decades before Luther; was rewarded for it by being duplicitously burned at the stake at Constance in 1415. The Hussite Wars that followed managed the impressive feat of inventing a folk-Protestant peasant army that beat back several Holy Roman crusades using armoured war-wagons and early handguns. The Protestant Reformation, and some kind of peasant uprising, ahead of its time, was a Czech thing. If they had waited for the printing press as well as handguns, history might have been different.</p>
<p>Also, <a href="https://english.radio.cz/what-does-jan-hus-have-do-czech-spelling-8754833?ref=danmackinlay.name">Hus is often credited with inventing modern Czech orthography</a>.</p>
</section>
<section id="karel-iv" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="karel-iv"><span class="header-section-number">1.2</span> Karel IV</h3>
<p>The Holy Roman Emperor we Anglophones call Charles IV. Made Prague the imperial capital in the fourteenth century, commissioned various things that make towns serious: the bridge, the cathedral, founded the university (1348) that still bears his name. For who came before and after, see the HRE timeline below.</p>
</section>
<section id="charles-university-univerzita-karlova" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="charles-university-univerzita-karlova"><span class="header-section-number">1.3</span> Charles University / Univerzita Karlova</h3>
<p>Founded by Karel IV in 1348 as the first university in central Europe, and possessed of a bonkers institutional biography ever since. I confess I did actually look this biography up when I was applying to work there, but that was in 2025, so I’ll put it on this page as received knowledge.</p>
<p>Originally the four student “nations” (Bohemian, Bavarian, Saxon, Polish) each had one vote in university affairs. In 1409 Wenceslaus IV decreed that the Bohemian nation should have three votes and the rest one between them; the German masters and students promptly walked out and founded the University of Leipzig, which is also still going. Jan Hus had been rector shortly before he was burned in 1415, after which the Hussites took over and the theology faculty was effectively closed for the next two centuries.</p>
<p>After the Habsburgs won the Battle of White Mountain in 1620, the Jesuits were handed the keys; in 1654 they merged the Carolinum with the Jesuit Clementinum to form the Charles-Ferdinand University, and the place was Catholicised by force.</p>
<p>In 1882 the institution was split into a Czech-language university and a German-language university, both called Charles-Ferdinand, sharing some libraries but not apparently much else. In 1939 the Nazis closed the Czech university and sent over a thousand of its students to concentration camps; in 1945 the German half was abolished in turn. The Communists purged the survivors in 1948. The students of Charles University were on the front line of 1989.</p>
<p>Language/religion of instruction across seven hundred years runs, more or less: Latin, Latin (Hussite), Latin (Jesuit), German, Czech-and-German-in-parallel, Reich-German, Czech. The medieval-university brand has not been done many favours, but it has survived.</p>
</section>
<section id="various-wenceslases" class="level3" data-number="1.4">
<h3 data-number="1.4" class="anchored" data-anchor-id="various-wenceslases"><span class="header-section-number">1.4</span> Various Wenceslases</h3>
<p>Mostly as an English speaker I know Wenceslaus I, Duke of Bohemia (907–935): the “Good King” of the Christmas carol, who was strictly a duke not a king (I confess I did need to look that up) and who was knifed by his brother. Thereafter there was a confusing line-up of Kings Wenceslas (II to IV I guess). The fourth one was Charles IV’s son and an aspirational Holy Roman Emperor (every family has one, amirite). The <a href="https://en.wikipedia.org/wiki/Wenceslas_Square?ref=danmackinlay.name">largest square in Prague</a> is named for the first-mentioned, who became the patron saint of Bohemia.</p>
</section>
<section id="defenestration" class="level3" data-number="1.5">
<h3 data-number="1.5" class="anchored" data-anchor-id="defenestration"><span class="header-section-number">1.5</span> Defenestration</h3>
<p>Throwing political opponents out of windows is a signature Czech contribution to constitutional procedure. The First Defenestration of Prague (1419) launched the Hussite Wars; the Second Defenestration (1618) launched the Thirty Years’ War. A non-canonical mini-defenestration occurred in between. The foreign minister Jan Masaryk fell out of a window in 1948 in a manner that was officially suicide and unofficially a third-to-fourth defenestration. I for one think it speaks to an architecturally progressive streak, that there was such a ready supply of tall buildings to throw people from at a time when the typical peasant <a href="https://janeaustensworld.com/2011/12/05/a-short-history-of-floors/?ref=danmackinlay.name">didn’t have a floor</a>.</p>
</section>
<section id="sudetenland" class="level3" data-number="1.6">
<h3 data-number="1.6" class="anchored" data-anchor-id="sudetenland"><span class="header-section-number">1.6</span> Sudetenland</h3>
<p>The German-speaking border regions of Bohemia, Moravia, and Czech Silesia. Most Anglophones (me included) know the word from WWII history class as the bit Hitler announced in 1938 he would invade in order to “protect” the Germans living there, which Britain and France then let him have at Munich, after which he promptly invaded everything anyway.</p>
</section>
<section id="milan-kundera" class="level3" data-number="1.7">
<h3 data-number="1.7" class="anchored" data-anchor-id="milan-kundera"><span class="header-section-number">1.7</span> Milan Kundera</h3>
<p>Novelist. <em>The Unbearable Lightness of Being</em> is the famous one that I am <a href="../notebook/timeless_works_of_art.html#kundera-on-kitsch">vaguely annoyed how much I like</a>. Kundera defected to France in the 1970s, was stripped of citizenship by the Communist government, declined to come back after 1989, eventually wrote in French, fell out with the Czech literary scene, and got accused of having informed on a fellow citizen as a young man in 1950.</p>
</section>
<section id="václav-havel" class="level3" data-number="1.8">
<h3 data-number="1.8" class="anchored" data-anchor-id="václav-havel"><span class="header-section-number">1.8</span> Václav Havel</h3>
<p>Playwright, dissident, jailed, then president — first of Czechoslovakia, then of the new Czech Republic after the country split with Slovakia in 1993 (the “Velvet Divorce”). The model intellectual-as-statesman that the post-Soviet Eastern European liberal moment offered up. What happened to that moment?</p>
<p>Anyway, you can find Havel plays being performed in Prague right this very minute. How many presidential plays have you watched?</p>
</section>
<section id="viktor-koženy" class="level3" data-number="1.9">
<h3 data-number="1.9" class="anchored" data-anchor-id="viktor-koženy"><span class="header-section-number">1.9</span> Viktor Koženy</h3>
<p><a href="https://www.blanchard-house.com/releases/nwfei4clq9mes1v8n3hacvrt3tkpi0?ref=danmackinlay.name">The Pirate of Prague</a>. Made off with several hundred million dollars of Czech voucher-privatisation money in the 1990s, had disastrous adventures in Colorado and Azerbaijan, fled to the Bahamas, and has since been wanted in several jurisdictions for variations of fraud.</p>
</section>
<section id="václav-smil" class="level3" data-number="1.10">
<h3 data-number="1.10" class="anchored" data-anchor-id="václav-smil"><span class="header-section-number">1.10</span> Václav Smil</h3>
<p>Polymath of energy, materials, agriculture. Czech-born Canadian academic. Bill Gates says he reads everything Smil writes; given the volume of Smil’s output this suggests that his books have cost the world a hundred million dollars in lost Gates time. I have complicated feelings about Smil, whose books were a major part of my undergraduate studies. I recall I did not feel that he really nailed the human dimension of energy transition. Enough time has passed that I cannot recall specific complaints, though.</p>
</section>
<section id="franz-kafka" class="level3" data-number="1.11">
<h3 data-number="1.11" class="anchored" data-anchor-id="franz-kafka"><span class="header-section-number">1.11</span> Franz Kafka</h3>
<p>A German-speaking Jewish insurance clerk in Prague who happened also to be the writer who tells us how it feels to live inside a bureaucratic empire. Published almost nothing in his lifetime, and demanded that it all be burned.</p>
<p>Is <a href="https://www.kafka-online.info/the-great-wall-of-china.html?ref=danmackinlay.name">The Great Wall of China</a> (<a href="https://teachsam.de/deutsch/d_literatur/d_aut/kaf/kaf_beim_bau_der%20chinesischen_mauer_txt.htm?ref=danmackinlay.name">de</a>) hopeless orientalism or genius? Both.</p>
<p>Also, it was <a href="https://de.wikipedia.org/wiki/Beim_Bau_der_Chinesischen_Mauer?ref=danmackinlay.name#Entstehung">inspired by a wall in Prague</a>.</p>
</section>
<section id="jan-švankmajer" class="level3" data-number="1.12">
<h3 data-number="1.12" class="anchored" data-anchor-id="jan-švankmajer"><span class="header-section-number">1.12</span> Jan Švankmajer</h3>
<p>Surrealist animator. The 1988 <em>Alice</em> (<em>Něco z Alenky</em>) gives me intrusive thoughts years afterwards: tactile, dreadful, made of dust and meat. I still send people animated GIFs grabbed from this nightmare.</p>
</section>
<section id="karel-zeman" class="level3" data-number="1.13">
<h3 data-number="1.13" class="anchored" data-anchor-id="karel-zeman"><span class="header-section-number">1.13</span> Karel Zeman</h3>
<p>“The Czech Méliès” — live-action and animation hybrid films, particularly the Jules Verne adaptations. They are, like, really good. <a href="https://karelzemanmuseum.org/karel-zeman-museum/karel-zeman-films/?ref=danmackinlay.name">Annoying they are only on DVD</a>.</p>
</section>
<section id="antonín-dvořák" class="level3" data-number="1.14">
<h3 data-number="1.14" class="anchored" data-anchor-id="antonín-dvořák"><span class="header-section-number">1.14</span> Antonín Dvořák</h3>
<p>Composer, nineteenth century. We played <em>New World Symphony</em> (1893) in my family home to feel classy when I was a child. He wrote other music, but I do not know what, so I looked him up. I learned he spent three years running the National Conservatory of Music in New York, during which he told the Americans they should build a national music on Black spirituals and Native American melodies; this was unwelcome advice that the country eventually took.</p>
</section>
<section id="karel-čapek-and-robots" class="level3" data-number="1.15">
<h3 data-number="1.15" class="anchored" data-anchor-id="karel-čapek-and-robots"><span class="header-section-number">1.15</span> Karel Čapek and Robots</h3>
<p>Playwright and journalist. His play <em>R.U.R.</em> (1920) introduced the word <em>robot</em> to the world, and <em>War with the Newts</em> (1936) is on my to-read list as one of the better satires-with-amphibians ever written.<sup>1</sup></p>
</section>
<section id="pilsner" class="level3" data-number="1.16">
<h3 data-number="1.16" class="anchored" data-anchor-id="pilsner"><span class="header-section-number">1.16</span> Pilsner</h3>
<p>The pale lager style invented in Plzeň in 1842, when a Bavarian brewer named Josef Groll got the local brewery to combine bottom-fermenting yeast, pale Moravian malt, and the local soft water. The result was a beer so popular it took over the world: every <em>lager</em> in a green or clear bottle anywhere is a descendant. Pilsner Urquell still exists; opinions on whether it has held up vary. I do not drink though, so I do not care either way.</p>
</section>
<section id="amanita-design" class="level3" data-number="1.17">
<h3 data-number="1.17" class="anchored" data-anchor-id="amanita-design"><span class="header-section-number">1.17</span> Amanita design</h3>
<p><a href="https://amanita-design.net?ref=danmackinlay.name">Amanita design</a>: The studio behind the <em>Machinarium</em> and <em>Samorost</em> series of point-and-click puzzle adventure games with achingly <a href="https://amanita-design.net/games/samorost3.html?ref=danmackinlay.name">lovely</a> and/or <a href="https://amanita-design.net/games/happy.html?ref=danmackinlay.name">upsetting</a> hand-made art. Relatedly, employee <a href="https://floex.cz/?ref=danmackinlay.name">Floex</a> is a musician I rather enjoy.</p>
</section>
<section id="yuku" class="level3" data-number="1.18">
<h3 data-number="1.18" class="anchored" data-anchor-id="yuku"><span class="header-section-number">1.18</span> YUKU</h3>
<p><a href="https://yukumusic.bandcamp.com/?ref=danmackinlay.name">YUKU</a>: Cult hipster record label.</p>
</section>
<section id="mendels-pea-garden" class="level3" data-number="1.19">
<h3 data-number="1.19" class="anchored" data-anchor-id="mendels-pea-garden"><span class="header-section-number">1.19</span> Mendel’s pea garden</h3>
<p>Gregor Mendel, an Augustinian friar at the abbey in Brno, spent the 1850s and 1860s growing tens of thousands of pea plants and counting their offspring’s traits. He published a paper that was ignored for thirty years and then, around 1900, became the foundation of genetics. The mathematics of inheritance was figured out, more or less correctly, in a monastery vegetable patch. Except it was fabricated <span class="citation" data-cites="Fisher1936Has">(Fisher 1936)</span>! <em>Or was it?</em> <span class="citation" data-cites="Ellis2019Mendels">(Ellis et al. 2019)</span></p>
</section>
<section id="jetbrains" class="level3" data-number="1.20">
<h3 data-number="1.20" class="anchored" data-anchor-id="jetbrains"><span class="header-section-number">1.20</span> JetBrains</h3>
<p>Developer-tools company originally headquartered in Prague since 2000. Makes IntelliJ IDEA, PyCharm, WebStorm, and the rest of the IDE bestiary; created Kotlin, which Google eventually decided was the right way to write Android apps. I just looked them up and they are now domiciled in the Netherlands; not sure if this still counts.</p>
</section>
<section id="atheism" class="level3" data-number="1.21">
<h3 data-number="1.21" class="anchored" data-anchor-id="atheism"><span class="header-section-number">1.21</span> Atheism</h3>
<p>Depending on how you count it, Czechia is the least religious country in Europe <a href="https://ourworldindata.org/religion?ref=danmackinlay.name">cf OurWorldInData</a>. We can tweak our criteria and find that Estonia is less religious, depending on what kind of irreligiosity race we are running. Definitely up in the godless major leagues, either way.</p>
</section>
</section>
<section id="people-and-things-from-czechia-i-missed" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="people-and-things-from-czechia-i-missed"><span class="header-section-number">2</span> People and things from Czechia I missed</h2>
<section id="czechia-is-basically-an-ethnostate" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="czechia-is-basically-an-ethnostate"><span class="header-section-number">2.1</span> Czechia is basically an ethnostate</h3>
<p>What WWII history class about Sudetenland did not tell me is what happened after the war: in 1945–1946 Czechoslovakia expelled around three million ethnic Germans from these regions under the Beneš decrees, in one of the larger forced population transfers in European history.</p>
<p>The Sudetenland was depopulated, partially resettled by Czechs and Slovaks, and the German-Bohemian and German-Moravian cultures, in place since the medieval <em>Ostsiedlung</em>, just… ended?</p>
<p>The upshot is that Czechia is remarkably <a href="https://vdb.czso.cz/vdbvo2/faces/en/index.jsf?page=vystup-objekt&amp;z=T&amp;f=TABULKA&amp;skupId=4293&amp;katalog=33522&amp;pvo=SLD210082-KR&amp;pvo=SLD210082-KR&amp;evo=v133_!_SLD21F1008-H2_1&amp;ref=danmackinlay.name">ethnically homogenous</a>; 89% of the country report being Czech. The next largest ethnic group (Moravians) are rebranded Czechs (or if they aren’t they should edit their Wikipedia page to say so). The borders of Czechia are basically the borders of the Czech-speaking people. I’m not used to this, as a child of a nation of <a href="https://en.wikipedia.org/wiki/Demographics_of_Australia?ref=danmackinlay.name">very patchwork origins</a>.</p>
<p>WELL! The post-war Polish displacement into <a href="https://en.wikipedia.org/wiki/Recovered_Territories?ref=danmackinlay.name">Recovered Territories</a> was even bigger than the purging of Sudetenland. Not that this is a competition, now folks, don’t start trying to climb that leaderboard.</p>
<p>On that mass-slavic displacement point though, HT Jan Kulveit for this post-Russian-Ukraine-invasion update: <a href="https://en.wikipedia.org/wiki/Demographics_of_the_Czech_Republic?ref=danmackinlay.name#Immigration">There were 600K foreign residents in Czechia</a>; but now many, many Ukrainians. I have heard numbers like 500K? I didn’t notice them because they don’t show up on the 5-yearly census.</p>
</section>
<section id="experiential-learning" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="experiential-learning"><span class="header-section-number">2.2</span> Experiential learning</h3>
<p><em>Zážitková pedagogika</em> and the other heirs of Prázdninová škola Lipnice (“Vacation school Lipnice”) <span class="citation" data-cites="Holec2022Historii Jirasek2020Experiential Kudlacek2009Czech">(Holec 2022; Jirásek and Turčová 2020; Kudláček et al. 2009)</span>.</p>
<p><strong>tl;dr</strong>; There is totally a local school of weird experimental learning (swimming across lakes with a fire, spending hours blindfolded) which originates in the communist era and still seems to be a local specialty.</p>
<p>The way Czech folks flippantly talk about “that time I was hilariously black-bagged and tied up” for pedagogy was startling the first time I heard it, but now I am somewhat inured.</p>
<p>But also, wtf?</p>
<p>(HT Ondřej Bajgar and Anička Doležalová).</p>
</section>
<section id="jára-cimrman" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="jára-cimrman"><span class="header-section-number">2.3</span> Jára Cimrman</h3>
<blockquote class="blockquote">
<p>In 2005, <a href="https://en.wikipedia.org/wiki/J%C3%A1ra_Cimrman?ref=danmackinlay.name">Jára Cimrman</a> won a public vote for the title of “The Greatest Czech” (he was subsequently disqualified due to being a fictional character).</p>
</blockquote>
</section>
<section id="tycho-brahe" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="tycho-brahe"><span class="header-section-number">2.4</span> Tycho Brahe</h3>
<p><a href="https://en.wikipedia.org/wiki/Tycho_Brahe?ref=danmackinlay.name">Tycho Brahe</a>: A Danish nobleman and astronomer whose observations of the heavens provided the basis for Kepler’s laws of planetary motion. Emperor Rudolf II appointed him imperial mathematician in Prague in 1601, and he died there. (HT Jan Kulveit.)</p>
</section>
<section id="oh-damn-johannes-kepler-too" class="level3" data-number="2.5">
<h3 data-number="2.5" class="anchored" data-anchor-id="oh-damn-johannes-kepler-too"><span class="header-section-number">2.5</span> Oh damn Johannes Kepler too</h3>
<p><a href="https://en.wikipedia.org/wiki/Johannes_Kepler?ref=danmackinlay.name">Johannes Kepler</a>, the guy who inspired Newton to do the gravity thing, spent 12 years of his career in Prague. I did not know that.</p>
</section>
<section id="rabbi-judah-loew-ben-bezalel-and-the-golem" class="level3" data-number="2.6">
<h3 data-number="2.6" class="anchored" data-anchor-id="rabbi-judah-loew-ben-bezalel-and-the-golem"><span class="header-section-number">2.6</span> Rabbi Judah Loew ben Bezalel and the Golem</h3>
<p><a href="https://en.wikipedia.org/wiki/Judah_Loew_ben_Bezalel?ref=danmackinlay.name">Rabbi Judah Loew ben Bezalel</a>: The Rabbi of Prague, often associated with the creation of the Golem, a mythical creature made from clay and an early metaphor for a loss of control in AI. The good Rabbi was a real person who actually died here. It’s embarrassing that I didn’t know about this. (HT Jan Kulveit.)</p>
</section>
<section id="mikuláš-of-kadaň-jan-šindel-and-pragues-astronomical-clock" class="level3" data-number="2.7">
<h3 data-number="2.7" class="anchored" data-anchor-id="mikuláš-of-kadaň-jan-šindel-and-pragues-astronomical-clock"><span class="header-section-number">2.7</span> Mikuláš of Kadaň, Jan Šindel and Prague’s Astronomical Clock</h3>
<p><a href="https://www.czechcenter.org/blog/prague-astronomical-clock?ref=danmackinlay.name">That famous clock</a> also has a twisted history. (HT Jan Kulveit.)</p>
</section>
<section id="jan-patočka" class="level3" data-number="2.8">
<h3 data-number="2.8" class="anchored" data-anchor-id="jan-patočka"><span class="header-section-number">2.8</span> Jan Patočka</h3>
<p><a href="https://en.wikipedia.org/wiki/Jan_Pato%C4%8Dka?ref=danmackinlay.name">Jan Patočka</a> was a philosopher, dissident, and <a href="https://en.wikipedia.org/wiki/Charter_77?ref=danmackinlay.name">Charter 77</a> signatory who died in police custody in 1977, and whose library I am currently working from. (HT Jan Kulveit.)</p>
</section>
<section id="jena-codex" class="level3" data-number="2.9">
<h3 data-number="2.9" class="anchored" data-anchor-id="jena-codex"><span class="header-section-number">2.9</span> Jena Codex</h3>
<div id="fig-jenska-kodex" class="right figure illustration quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-jenska-kodex-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/Jensky_kodex_satan_prodava_odpustky.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: Demon priest dispensing indulgences"><img src="https://danmackinlay.name/images/Jensky_kodex_satan_prodava_odpustky.avif" class="right figure illustration img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-jenska-kodex-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Demon priest dispensing indulgences
</figcaption>
</figure>
</div>
<p>The Jena Codex, also called the <em>Jenský kodex</em> or <em>Antithesis Christi et Antichristi</em>, is badass Figure&nbsp;2. It is, I am told <a href="https://cs.wikipedia.org/wiki/Jensk%C3%BD_kodex?ref=danmackinlay.name">by Czech Wikipedia</a>, an illuminated Hussite manuscript from around 1500, commissioned by the Utraquist (i.e., Hussite-flavoured) Bohuslav of Čechtice. I cannot read it, but apparently it is full of theological and polemical texts contrasting the early Christian Church with the Church of Jan Hus’s time. Here are three online versions of it:</p>
<ul>
<li><a href="https://www.manuscriptorium.com/hub/browser/default/detail?url=https:%2F%2Fcollectiones.manuscriptorium.com%2Fassorted%2FAIPDIG%2FNMP___%2FE%2FAIPDIG-NMP___IV_B_24_____3TQMIOE-cs%2F&amp;ref=danmackinlay.name">Manuscriptorium</a></li>
<li><a href="https://www.esbirky.cz/predmet/180453?ref=danmackinlay.name">eSbirky.cz - Antithesis Christi et Antichristi</a></li>
<li><a href="https://commons.wikimedia.org/wiki/Category:Jensk%C3%BD_kodex_(whole_pages)?uselang=de&amp;ref=danmackinlay.name">Wikimedia Commons</a></li>
</ul>
</section>
<section id="jan-žižka" class="level3" data-number="2.10">
<h3 data-number="2.10" class="anchored" data-anchor-id="jan-žižka"><span class="header-section-number">2.10</span> Jan Žižka</h3>
<p>If I think the Hussites are so impressive, I should probably have credited <a href="https://en.wikipedia.org/wiki/Jan_%C5%BDi%C5%BEka?ref=danmackinlay.name">Jan Žižka</a>, the general who led the radical, peasant-filled Hussite armies in the wars that followed Jan Hus’s death — while blind.</p>
</section>
<section id="coffee" class="level3" data-number="2.11">
<h3 data-number="2.11" class="anchored" data-anchor-id="coffee"><span class="header-section-number">2.11</span> Coffee</h3>
<p>Prague coffee is, remarkably, reliably good. Why is this?</p>
</section>
<section id="other-musicians-of-note" class="level3" data-number="2.12">
<h3 data-number="2.12" class="anchored" data-anchor-id="other-musicians-of-note"><span class="header-section-number">2.12</span> Other musicians of note</h3>
<ul>
<li><a href="https://www.clarinet-factory.cz/?ref=danmackinlay.name">Clarinet Factory</a></li>
<li><a href="https://www.maok.sk/?ref=danmackinlay.name">Maok</a></li>
</ul>
</section>
</section>
<section id="when-was-prague-the-capital-of-the-holy-roman-empire" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="when-was-prague-the-capital-of-the-holy-roman-empire"><span class="header-section-number">3</span> When was Prague the capital of the Holy Roman Empire?</h2>
<p>Turns out that by a sufficiently loose definition, five rulers of the Holy Roman Empire had their court in Prague between 1346 and 1612.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>When</th>
<th>Who</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1346–1378</td>
<td><strong>Charles IV</strong> / Karel IV</td>
<td>The textbook dude. Made Prague the imperial seat, built the bridge, the cathedral, the university.</td>
</tr>
<tr class="even">
<td>1378–1419</td>
<td><strong><a href="https://en.wikipedia.org/wiki/Wenceslaus_IV_of_Bohemia?ref=danmackinlay.name">Wenceslaus IV</a></strong> / Václav IV</td>
<td>Charles IV’s son. King of the Romans 1376–1400, deposed; never crowned Emperor. Reorganized Charles University in 1409. Tried to protect Jan Hus.</td>
</tr>
<tr class="odd">
<td>1436–1437</td>
<td><strong><a href="https://en.wikipedia.org/wiki/Sigismund,_Holy_Roman_Emperor?ref=danmackinlay.name">Sigismund</a></strong> of Luxembourg</td>
<td>HRE 1433–1437, brother of Wenceslaus IV. The Sigismund who duplicitously had Jan Hus burned at Constance in 1415. The Hussite Wars (1419–1436) were in part a war against him. The Bohemians would not have this guy as king and so he was based in Buda for all but the last year of his reign.</td>
</tr>
<tr class="even">
<td>1583–1611</td>
<td><strong><a href="https://en.wikipedia.org/wiki/Rudolf_II,_Holy_Roman_Emperor?ref=danmackinlay.name">Rudolf II</a></strong></td>
<td>Habsburg. Moved the imperial court Vienna → Prague in 1583 and kept it there about thirty years. Patron of Tycho Brahe, Kepler, assorted alchemists.</td>
</tr>
<tr class="odd">
<td>1611–1612</td>
<td><strong><a href="https://en.wikipedia.org/wiki/Matthias,_Holy_Roman_Emperor?ref=danmackinlay.name">Matthias</a></strong></td>
<td>Inherited Rudolf’s Prague court and moved it back to Vienna within a year at his wife Anna of Tyrol’s urging. Ended Prague-as-imperial-capital. (booo!)</td>
</tr>
</tbody>
</table>
<p>Six years after Matthias left, the Second Defenestration of Prague (1618) launched the Thirty Years’ War. The Habsburgs ultimately won in 1620 and Catholicized Bohemia by force, grinding out at least the religious dimension of Czech national identity for the next three centuries. Czech politics (at least as <em>national</em> politics) then went into hibernation until 1918.</p>
</section>
<section id="language" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="language"><span class="header-section-number">4</span> Language</h2>
<section id="survival-kit" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="survival-kit"><span class="header-section-number">4.1</span> Survival kit</h3>
<dl>
<dt>Hello</dt>
<dd>
<em>Dobrý den</em> [ˈdobriː ˈdɛn] (roughly “DOB-ree den”; literally “good day”). Informal: <em>Ahoj</em> [ˈaɦoj], like sailors.
</dd>
<dt>Thank you</dt>
<dd>
<em>Děkuji</em> [ˈɟɛkujɪ] (roughly “DYEH-koo-yee”). Casual short form <em>Díky</em> [ˈɟiːkɪ].
</dd>
<dt>Please</dt>
<dd>
<em>Prosím</em> [ˈprosiːm]. The same word does multiple duty for “you’re welcome”, “pardon?”, “here you are”, and “after you.”
</dd>
<dt>Excuse me / sorry</dt>
<dd>
<em>Promiňte</em> [ˈpromiɲtɛ] (formal). <em>Pardon</em> [ˈpardon] (yes, the French loan) is widely understood.
</dd>
<dt>Goodbye</dt>
<dd>
<em>Na shledanou</em> [na ˈsxlɛdanou̯] formally; <em>Ahoj</em> [ˈaɦoj] or <em>Čau</em> [ˈt͡ʃau̯] (like Italian <em>ciao</em>) informally.
</dd>
<dt>Do you speak English?</dt>
<dd>
<em>Mluvíte anglicky?</em> [ˈmluviːtɛ ˈaŋɡlit͡ski] (roughly “MLOO-vee-teh AHN-glits-ki”). The verb takes the adverbial form <em>anglicky</em>, in the same shape as <em>mluvím česky</em>, “I speak Czech-ly”. Cool.
</dd>
<dt>Sorry, I am an idiot foreigner who only speaks English</dt>
<dd>
<em>Promiňte, jsem hloupý cizinec a mluvím jenom anglicky</em> [ˈpromiɲtɛ jsɛm ˈɦloupiː ˈt͡sizinɛt͡s a ˈmluviːm ˈjɛnom ˈaŋɡlit͡ski] (roughly “PRO-min-teh, ysem HLOH-pee TSEE-zee-nets ah MLOO-veem YEH-nom AHN-glits-ki”). <em>Hloupá cizinka</em> for the female form, apparently. The terser retreat <em>Promiňte, neumím česky</em>, “Sorry, I don’t know Czech”, may also serve but that is not as funny. NB this is an AI-generated translation; how did we do, Czech speakers?
</dd>
<dt>Yes</dt>
<dd>
<em>Ano</em> [ˈano] (“AH-no”). Casually shortened to <em>jo</em> [jo], which is German <em>ja</em> in Czech orthography. Confusingly to English ears, the Czech word <em>no</em> is also a colloquial agreement particle (“well, yeah”).
</dd>
<dt>No</dt>
<dd>
<em>Ne</em> [nɛ] (“neh”). Repeat for emphasis: <em>Ne, ne, ne.</em>
</dd>
</dl>
<p>And of course, I love <a href="../notebook/flashcards.html">flashcards</a>. Here are some:</p>
<ul>
<li><a href="https://ankiweb.net/shared/info/1600445115?ref=danmackinlay.name">🇨🇿 The Ultimate Czech Deck – Languages on Fire (A1 to B1) - AnkiWeb</a></li>
<li><a href="https://ankiweb.net/shared/info/1320507125?ref=danmackinlay.name">Czech Core 100 - Basic words (EN CS with audio) - AnkiWeb</a> The author <em>really wants</em> you to know dates, days of the week etc before you learn how to say “Promiňte” and “Prosím”. I wonder what the creator’s project was in Czechia? Very heavy on calendars, whatever it was. Anyway they have pronunciation audio for everything, so I still recommend it even for people who do not want to start their Czech learning journey with “Tuesday the 12th of September”.</li>
</ul>
</section>
<section id="václav-and-wenceslaus" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="václav-and-wenceslaus"><span class="header-section-number">4.2</span> Václav and Wenceslaus</h3>
<p>Secretly the same. In modern English we use <em>Václav</em> for contemporary Czechs (Havel, Smil, Klaus) and reserve <em>Wenceslaus</em> (the Latin form) or <em>Wenceslas</em> (the slimmed-down English version) for medieval rulers — Wenceslaus I Duke of Bohemia, Wenceslaus IV who issued the 1409 reorganization of Charles University, and so on.</p>
<p>AFAICT this distinction is not drawn in Czechia. <em>Václav</em> descends from an old Slavic <em>Vęceslavъ</em>, “greater glory”, which Latin took up as <em>Wenceslaus</em>; English kept the Latin form for kings even after dropping it for everyone else.</p>
</section>
<section id="prague-and-praha" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="prague-and-praha"><span class="header-section-number">4.3</span> Prague and Praha</h3>
<p>The city is <em>Praha</em> in Czech. English took the form <em>Prague</em> via Latin <em>Praga</em> and French. The same pattern by which Anglophones say Vienna, Munich, Warsaw, Cologne, Geneva instead of Wien, München, Warszawa, Köln, Genève.</p>
</section>
<section id="bohemians" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="bohemians"><span class="header-section-number">4.4</span> “Bohemians”</h3>
<p>Czechia used to more-or-less be called Bohemia by non-Czechs. It seems that the word was <a href="https://english.radio.cz/bohemia-czechia-8220362?ref=danmackinlay.name">always an exonym</a>. IIRC the reason that <em>bohemian</em> now means “artistic nonconformists” in English is not because the Czechs were especially artistic or nonconformist, but that the word was borrowed from French in the nineteenth century, and the French called Romani <em>bohémiens</em> as a kind of geographic origin story for the Romani and romanticized their lifestyle as carefree and creative-looking. <a href="https://en.wikipedia.org/wiki/Romani_people?ref=danmackinlay.name">There are not especially many Romani people in Czechia now</a>, but maybe this made more sense before the war and communist repression etc.</p>
</section>
<section id="czechs" class="level3" data-number="4.5">
<h3 data-number="4.5" class="anchored" data-anchor-id="czechs"><span class="header-section-number">4.5</span> “Czechs”</h3>
<p>English has some fossilized spelling from before the 19th century. Czech = <em>Čech</em> (masc) or <em>Češka</em> (fem) in Czech. The language is <em>čeština</em>, the adjective is <em>český</em>, and the country is <em>Česko</em>. But I am sticking with <em>Czechia</em>, since it is the official English name.</p>
</section>
</section>
<section id="is-czechia-in-central-europe-or-eastern-europe" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="is-czechia-in-central-europe-or-eastern-europe"><span class="header-section-number">5</span> Is Czechia in Central Europe or Eastern Europe?</h2>
<p>I vote Central.</p>
<p>Reasoning:</p>
<ul>
<li>It was in the Holy Roman Empire, the most central European institution there ever was</li>
<li>OK, it was in the Soviet bloc, but as a vassal state like Poland and Hungary were, not like Russia was</li>
<li>Speaks a Slavic language, which tends to code easternness (but then cf Romania, which speaks a non-Slavic language and is coded way more eastern). Also, their script has been Latin since the Middle Ages.</li>
<li>More geographically west and less religiously Orthodox than Greece, which is not eastern because they invented democracy and philosophy and stuff and the Mediterranean gets you a pass for some reason?</li>
<li>Godless, which is to say, has not joined the eastern trend to re-embrace religion as a marker of identity post-communism</li>
<li>Successfully invaded by Hitler in 1938, but not successfully invaded by Stalin in 1948 (unlike Hungary and Poland)</li>
<li>They sauna naked (unlike the Russians, but like the Scandinavians)</li>
<li>Invented the Protestant Reformation 80 years before Luther and <a href="https://en.wikipedia.org/wiki/Hussite_Wars?ref=danmackinlay.name">fought a war about it</a>. What could be more central European than a war of protestant peasants against an oppressive church?</li>
<li>To paraphrase my Romanian colleague Teodora, “pfft you call this eastern? you should see Romania.”</li>
</ul>
<p>Conclusion: No more eastern than Slovenia, and more central than Hungary and Poland. Therefore: Central.</p>
<p>Now, a more interesting case is former partner state Slovakia. Are <em>they</em> central or eastern Europe? This question is left as an exercise for the reader.</p>
</section>
<section id="prague" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="prague"><span class="header-section-number">6</span> Prague</h2>
<p>OK, it is actually pretty. Decayed imperial grandeur, baroque churches, gothic cathedrals, Art Nouveau facades, and a river running through it. Overlaid w/ soviet utilitarianism, low-budget 90s lowest-bidder capitalist renovation. Not quite as pretty as Venice, say, but way more lived-in and less museum-y.</p>
<p>By which I mean, it is less museum-<em>like</em>. It is probably more museum-<em>dense</em>. Wow, so many museums! Some really fancy-sounding ones, and a long tail of very random stuff; Madame Tussaud’s franchise, chocolate shops with “chocolate museums” attached to them, and some really suspect Ripley-axis stuff, e.g.&nbsp;“Museum of Sex Machines”.</p>
<p>More tourists than I was expecting, but like, even in the old town, also some normal humans doing non-tourist lives in between the tourists. The mix of tourists, Habsburg cosplayers and philosophy students on their way to band practice seems fine?</p>
<section id="bikes" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="bikes"><span class="header-section-number">6.1</span> Bikes</h3>
<div id="fig-lol-no-prague-bikes" class="right figure quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-lol-no-prague-bikes-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/lol_no_prague_bikes.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;3: Hahah don’t go the direct route, it will be more fun to go through this cobblestone mall full of ambling tourists"><img src="https://danmackinlay.name/images/lol_no_prague_bikes.avif" class="right figure img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-lol-no-prague-bikes-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Hahah don’t go the direct route, it will be more fun to go through this cobblestone mall full of ambling tourists
</figcaption>
</figure>
</div>
<p>Prague’s bike infrastructure in the old town is meh, but it gets better in the more modern bits. “Similar to Sydney but with more cobblestones” is my review. Also the riding directions are aggressively bad on Apple Maps, like “Please ignore the bitumen road and take this much longer detour via some cobbled pedestrian walking street which is both more crowded and more difficult to ride on” Figure&nbsp;3.</p>
<p>To be fair, Apple Maps generally sucks here. <del>Google Maps</del> <a href="https://mapy.cz?ref=danmackinlay.name">Mapy.cz</a> is the main game.</p>
</section>
<section id="workout-stations" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="workout-stations"><span class="header-section-number">6.2</span> Workout stations</h3>
<ul>
<li><a href="https://maps.apple.com/?q=Fitness+Park+RVL13+Riegrovy+Sady&amp;ll=50.0804018,14.443552&amp;ref=danmackinlay.name">Fitness Park RVL13 – Riegrovy Sady</a>. Inside the popular Riegrovy Sady park in Vinohrady (Prague 2). Full calisthenics setup with gym foam flooring, good shade, and a public toilet 12 metres away. I’ve been using this one.</li>
<li><a href="https://maps.apple.com/?q=Workout+Hriste+Viktoria+Zizkov&amp;ll=50.0846291,14.4448248&amp;ref=danmackinlay.name">Workout Hřiště u FK Viktoria Žižkov</a> — Prague 3, right next to a tram stop making it very easy to reach. Covers all muscle groups, good surface for ground-level exercises too.</li>
<li><a href="https://maps.apple.com/?q=Venkovni+Posilovna+Vinohrady&amp;ll=50.0725381,14.4445134&amp;ref=danmackinlay.name">Venkovní Posilovna Vinohrady</a> — Prague 10, solid pull-up and dip bar setup.</li>
<li><a href="https://maps.apple.com/?q=Workout+Place+V+Olsinach&amp;ll=50.0723383,14.495670&amp;ref=danmackinlay.name">Workout Place – V Olšinách</a> — Quieter spot in Prague 10 in a park setting.</li>
<li><a href="https://maps.apple.com/?q=Street+Workout+Hriste+Prosek&amp;ll=50.1197621,14.4925579&amp;ref=danmackinlay.name">Street Workout Hřiště Prosek</a> — Prague 9, pull-up bars, parallel bars, abs benches.</li>
</ul>
</section>
<section id="saunas" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="saunas"><span class="header-section-number">6.3</span> Saunas</h3>
<p>TBD. I had better return and find out.</p>
</section>
<section id="suicide-bridge" class="level3" data-number="6.4">
<h3 data-number="6.4" class="anchored" data-anchor-id="suicide-bridge"><span class="header-section-number">6.4</span> Suicide bridge</h3>
<p><a href="https://livingprague.com/architecture-design/nusle-bridge/?ref=danmackinlay.name">Nusle Bridge</a> famed for suicides and being stress-tested by tanks.</p>
</section>
</section>
<section id="postage" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="postage"><span class="header-section-number">7</span> Postage</h2>
<p>Everything seems to require parcel lockers. Parcel lockers are annoyingly parochial and require HU/CZ/RO phone numbers to use.</p>
<ul>
<li><a href="https://www.zasilkovna.cz/en/zbox?ref=danmackinlay.name">Z-BOX: self-service pick-up point | Zásilkovna.cz</a></li>
<li><a href="https://www.alzabox.cz/en/for-customers?ref=danmackinlay.name">AlzaBox - Collect All Your Orders 24 Hours a Day</a></li>
</ul>
</section>
<section id="incoming" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="incoming"><span class="header-section-number">8</span> Incoming</h2>
<p>🚧TODO🚧 get corrected on all of the above by someone who lives there.</p>
<ul>
<li>A local friend’s doc about this: <a href="https://docs.google.com/document/d/17svgMVbfURk8YAYol2JuEwA9QdpFYCWD84y1V563KuY/edit?tab=t.0&amp;ref=danmackinlay.name">Prague visit card - Couchsurfing</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Ellis2019Mendels" class="csl-entry">
Ellis, Hofer, Swain, et al. 2019. <span>“<a href="https://doi.org/10.1186/s41065-019-0111-y">Mendel’s Pea Crosses: Varieties, Traits and Statistics</a>.”</span> <em>Hereditas</em>.
</div>
<div id="ref-Fisher1936Has" class="csl-entry">
Fisher. 1936. <span>“<a href="https://doi.org/10.1080/00033793600200111">Has Mendel’s Work Been Rediscovered?</a>”</span> <em>Annals of Science</em>.
</div>
<div id="ref-Holec2022Historii" class="csl-entry">
Holec. 2022. <span>“<a href="https://doi.org/10.5817/PedOr2021-2-7">Historií Výchovy v Přírodě v Čechách k Lipnické Prázdninové Škole</a>.”</span> <em>Pedagogická Orientace</em>.
</div>
<div id="ref-Jirasek2020Experiential" class="csl-entry">
Jirásek, and Turčová. 2020. <span>“<a href="https://doi.org/10.4324/9780429298806-2">Experiential Pedagogy in the Czech Republic</a>.”</span> In <em>Experiential Learning and Outdoor Education: Traditions of Practice and Philosophical Perspectives</em>. Routledge Focus.
</div>
<div id="ref-Kudlacek2009Czech" class="csl-entry">
Kudláček, Bocarro, Jirásek, et al. 2009. <span>“<a href="https://doi.org/10.1177/105382590903200103">The Czech Way of Inclusion Through an Experiential Education Framework</a>.”</span> <em>Journal of Experiential Education</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>A short list, admittedly.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Europe</category>
  <category>place</category>
  <guid>https://danmackinlay.name/notebook/czechia.html</guid>
  <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/Prager.Fenstersturz.1618.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Homunculi all the way down</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/homunculi.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p>Research-background notes. I want to pin down what it would mean, formally, for a social entity to contain a reduced-rank model of another social entity — possibly even a reduced-rank model of <em>itself</em>. Here are the formalisms I’m aware of, drawing on LLM lit review, some PDFs I had in a folder, and some vibes-y dot points I sketched out at the <a href="../post/pibbss_x_iliad.html">PIBBS x ILIAD residency</a>. Much slop in here be careful.</p>
<div class="callout callout-style-simple callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Adjacent run-ups
</div>
</div>
<div class="callout-body-container callout-body">
<p>One of several notebooks I started on the same underlying problem. The others are <a href="../notebook/agency_bounded_compute.html">agency under bounded compute</a> (the foundational why-must-the-agent-compress angle) and <a href="../notebook/ai_economics_of_cognition.html">Economics of Cognition</a> (compute as a substitutable factor of production). I think that I have run into a dead end with this one. My current working hypothesis is that I will have more success starting from <a href="../notebook/causality_mechanization_decisions.html">mechanized causal graphs</a>.</p>
<p>I think the version in open world inference is a better foundation.</p>
</div>
</div>
<div class="callout callout-style-simple callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Adjacent run-ups
</div>
</div>
<div class="callout-body-container callout-body">
<p>HT <a href="https://aclevername.substack.com?ref=danmackinlay.name">Chris Pang</a> for pointing out that I previous missed out Hofstadter’s <em>I am a Strange Loop</em> <span class="citation" data-cites="Hofstadter2008Am">(Hofstadter 2008)</span> in the first draft of this notebook.</p>
</div>
</div>
<div id="fig-vanmuydenlaboratory" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-vanmuydenlaboratory-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/vanmuydenlaboratory.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/vanmuydenlaboratory.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-vanmuydenlaboratory-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<section id="a-phenomenon-of-note" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="a-phenomenon-of-note"><span class="header-section-number">1</span> A phenomenon of note</h2>
<p>A mind modelling another mind is an agent embedded in an environment that contains agents with comparable representational capacity to itself. If the only faithful model of Alice is Alice, then Bob cannot fit one in his head. A practical Bob must carry a <em>compressed</em> Alice: fewer parameters, coarser predictions, maybe with a cartoon-level ontology. Call this a <em>reduced-rank other-model</em>.<sup>1</sup></p>
<p>Bob must also act, and acting well might require that Bob predict <em>his own</em> future behaviour. If the only faithful model of Bob is Bob, he cannot fit one of those in his head either. So Bob carries a “reduced-rank” self-model. This self-model is what <a href="https://en.wikipedia.org/wiki/Thomas_Metzinger?ref=danmackinlay.name">Metzinger</a> calls the <em>phenomenal self-model</em> <span class="citation" data-cites="Metzinger2003Being">(Metzinger 2003)</span>, what Graziano’s <em>Attention Schema Theory</em> <span class="citation" data-cites="Graziano2013Consciousness">(Graziano 2013)</span> makes into a neural control-theoretic object, what Schmidhuber-flavoured AI calls a <em>world-model-containing-self</em> <span class="citation" data-cites="Ha2018World">(Ha and Schmidhuber 2018)</span>, and what Hofstadter had been calling a <em>strange loop</em> since before any of them <span class="citation" data-cites="Hofstadter2008Am">(Hofstadter 2008)</span> — of which more below.</p>
<p>The bicameral-mind literature <span class="citation" data-cites="Jaynes1976Origin">(Jaynes 1976)</span> sounds like it’s somewhat related — the sense that “I” am addressed by a voice that is also “I” — but it doesn’t seem formal enough to build on, so I will basically ignore it here.</p>
<p>I can think of three axes along which theories might vary:</p>
<ol type="1">
<li><strong>Other-modelling.</strong> How do formalisms represent nested belief (“I think that you think that I think…”)?</li>
<li><strong>Self-modelling.</strong> How do formalisms represent an agent that contains a compressed simulacrum of itself?</li>
<li><strong>Reduced rank.</strong> By <em>rank</em> I mean the computational fidelity of a sub-model — the bits or parameters devoted to it, equivalently the resolution at which it can discriminate the situations it cares about. How is this reduction made rigorous — rate-distortion, PAC bounds, etc.?</li>
</ol>
</section>
<section id="other-models" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="other-models"><span class="header-section-number">2</span> Other-models</h2>
<section id="interactive-pomdps" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="interactive-pomdps"><span class="header-section-number">2.1</span> Interactive POMDPs</h3>
<p>Gmytrasiewicz and Doshi’s <em>interactive partially observable Markov decision processes</em> (I-POMDPs) <span class="citation" data-cites="Gmytrasiewicz2005Framework">(Gmytrasiewicz and Doshi 2005)</span> provide one formulation of recursive belief. The state space of any one agent is augmented with <em>models of the other agents</em>, which themselves include models of <em>this</em> agent, and so on. A finitely-nested I-POMDP truncates the recursion at level <img src="https://latex.codecogs.com/png.latex?k"> — agents at level 0 treat others as noise; level 1 models level-0s; level 2 models level-1s; and so on. This is an operationalization of “reduced rank”: the recursion is cut off, and the depth is tunable.</p>
<p>Suggestively related work in game theory:</p>
<ul>
<li><strong>Level-k / cognitive hierarchy</strong> models in behavioural game theory <span class="citation" data-cites="Camerer2004Cognitive">(Camerer, Ho, and Chong 2004)</span>, where players assume others are reasoning at a lower level than themselves. Humans reportedly cluster at <img src="https://latex.codecogs.com/png.latex?k%20%5Cin%20%5C%7B0,1,2%5C%7D">.</li>
<li><strong>Quantal response equilibrium</strong> <span class="citation" data-cites="McKelvey1995Quantal">(McKelvey and Palfrey 1995)</span>, where bounded rationality is modelled by stochastic best-response rather than a deeper recursion.</li>
<li><strong>Epistemic game theory</strong> <span class="citation" data-cites="Dekel2015Epistemic">(Dekel and Siniscalchi 2015)</span>, which formalizes <a href="../notebook/common_knowledge.html">common knowledge</a>, common belief, and the belief hierarchies above.</li>
</ul>
</section>
<section id="bayesian-theory-of-mind" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="bayesian-theory-of-mind"><span class="header-section-number">2.2</span> Bayesian Theory of Mind</h3>
<p>Baker, Saxe, and Tenenbaum <span class="citation" data-cites="Baker2017Rational Baker2011Bayesian">(C. L. Baker et al. 2017; C. Baker, Saxe, and Tenenbaum 2011)</span> formalize human social cognition as inverse planning: observers invert a generative model of rational action to infer the latent goals and beliefs of others. The other-model here is the generative model — typically a small MDP or POMDP parameterized by a utility and belief — and inference is Bayesian. This gives us a concrete <em>posterior over other minds’ rationality and actions</em> that one can compute with and prove things about.</p>
</section>
<section id="machine-theory-of-mind" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="machine-theory-of-mind"><span class="header-section-number">2.3</span> Machine Theory of Mind</h3>
<p>Rabinowitz et al.’s <em>ToMnet</em> <span class="citation" data-cites="Rabinowitz2018Machine">(Rabinowitz et al. 2018)</span> is a deep-learning analogue: a meta-learning agent that, from a few observations of a target agent, infers an embedding which predicts the target’s future behaviour. The embedding <em>is</em> the reduced-rank other-model. ToMnet variants have been extended to false-belief tasks and inverse-RL settings <span class="citation" data-cites="Oguntola2023Theory">(Oguntola et al. 2023)</span>.</p>
<p>Oguntola and colleagues push ToMnet toward interpretability with Concept Bottleneck Models <span class="citation" data-cites="Oguntola2021Deep">(Oguntola, Hughes, and Sycara 2021)</span>: the network is forced to predict named mental-state concepts (the opponent <em>believes</em> the door is locked, <em>wants</em> the key) before producing an action, so the recursive belief-state is in principle inspectable by a human overseer. The catch, as ever, is concept leakage — the net routes information around the bottleneck through residual pathways and re-hides the mental states the bottleneck was meant to expose <span class="citation" data-cites="Margeloiu2021Do">(Margeloiu et al. 2021)</span>. The same pathology is plausibly what any <img src="https://latex.codecogs.com/png.latex?%5CPhi">-probe on a residual stream will face (below).</p>
</section>
<section id="opponent-modelling-in-multi-agent-rl" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="opponent-modelling-in-multi-agent-rl"><span class="header-section-number">2.4</span> Opponent-modelling in multi-agent RL</h3>
<p>This also has a game-theoretic shape if we squint at it, but with learning theory sprinkled on top.</p>
<ul>
<li><strong>LOLA</strong> (Learning with Opponent-Learning Awareness) <span class="citation" data-cites="Foerster2018Learning">(Foerster et al. 2018)</span> computes gradients <em>through</em> a model of the opponent’s learning dynamics. This is a differentiable other-model.</li>
<li><strong>COLA, POLA, M-FOS</strong> and successors refine LOLA with higher-order or policy-level models <span class="citation" data-cites="Willi2022COLA Zhao2022Proximal Lu2022ModelFree">(Willi et al. 2022; Zhao et al. 2022; Lu et al. 2022)</span>.</li>
<li><strong><a href="../notebook/opponent_shaping.html">Opponent shaping</a></strong> more generally treats the other agent as a learnable dynamical system, which is a particular operationalization of “the other is a reduced-rank version of me”.</li>
<li><strong>Self Other-Modelling</strong> (SOM) <span class="citation" data-cites="Raileanu2018Modeling">(Raileanu et al. 2018)</span> takes the last bullet literally: the agent uses its <em>own</em> policy to predict the other agent’s actions, and online-updates a belief over the other’s hidden goal. A single generative model is reconfigured between egocentric and allocentric readings of the same observation. This is computational simulation theory — model the other by running our own machinery with the other’s sensors wired in — and it induces cooperation in imperfect-information resource-gathering without reward shaping or explicit communication. In the budget frame below it is the corner case where the self-model <em>is</em> the other-model and pays for both with the same bits.</li>
</ul>
</section>
<section id="llms-and-emergent-theory-of-mind" class="level3" data-number="2.5">
<h3 data-number="2.5" class="anchored" data-anchor-id="llms-and-emergent-theory-of-mind"><span class="header-section-number">2.5</span> LLMs and emergent theory of mind</h3>
<p>The question of whether transformer language models contain an implicit theory of mind has generated a cottage industry <span class="citation" data-cites="Kosinski2023Theory Ullman2023Large Sclar2023Minding Gandhi2023Understanding">(Kosinski 2023; Ullman 2023; Sclar et al. 2023; Gandhi et al. 2023)</span>. The answer seems to be that they carry shallow heuristics that look like ToM on canonical tasks and break on adversarial ones. Whatever they do have is, almost by construction, a reduced-rank model: compressed into attention patterns and residual-stream features.</p>
</section>
<section id="mechanized-causal-graphs" class="level3" data-number="2.6">
<h3 data-number="2.6" class="anchored" data-anchor-id="mechanized-causal-graphs"><span class="header-section-number">2.6</span> Mechanized causal graphs</h3>
<p>Causal games <span class="citation" data-cites="Hammond2023Reasoning">(Hammond et al. 2023)</span> and mechanized causal Bayesian networks <span class="citation" data-cites="MacDermott2023Characterising">(MacDermott, Everitt, and Belardinelli 2023)</span> supply the graph-theoretic vocabulary for the formalisms above. Each object variable <img src="https://latex.codecogs.com/png.latex?V"> acquires a <em>mechanism parent</em> <img src="https://latex.codecogs.com/png.latex?%5Ctilde%7BV%7D">: if <img src="https://latex.codecogs.com/png.latex?V"> is an agent’s decision <img src="https://latex.codecogs.com/png.latex?D">, then <img src="https://latex.codecogs.com/png.latex?%5Ctilde%7BD%7D"> is its decision rule. The strategic structure — who observes what, who chooses what — lives at the mechanism layer, and reasoning about an agent becomes reasoning over a graph of mechanism variables connected by edges of <em>strategic relevance</em>.</p>
<p>Two things matter for us.</p>
<p>One, what Bob carries of Alice has to be a model of Alice’s <em>mechanism</em>, not of Alice’s <em>output</em>. A pure action-predictor short-circuits the recursion: any Alice who outsmarts the predictor falsifies it, and the graph picks up a cycle <span class="citation" data-cites="MacDermott2023Characterising">(MacDermott, Everitt, and Belardinelli 2023)</span>. This is a graphical criterion for what counts as a model-of-another-agent in the first place, and it rhymes with the observer-relative construction in <span class="citation" data-cites="Virgo2025Good">Virgo et al. (2025)</span> at <a href="../notebook/internal_models.html">internal models</a>. Different formalisms, same shape of constraint — what Bob represents of Alice is a map to mechanism-space, not to action-space.</p>
<p>Two, one rung of mechanism-level modelling is native to the framework, but deeper recursion is not — there is no <img src="https://latex.codecogs.com/png.latex?%5Ctilde%7B%5Ctilde%7BD%7D%7D">. Hammond et al.&nbsp;recover depth by <em>unrolling</em> a mechanized MAID into an extensive-form game, with the familiar exponential blow-up in tree size. In the budget frame below that exponential is the cost of depth in this particular vocabulary — nodes rather than, say, bits.</p>
</section>
</section>
<section id="self-models" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="self-models"><span class="header-section-number">3</span> Self-models</h2>
<section id="world-models-containing-self" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="world-models-containing-self"><span class="header-section-number">3.1</span> World models containing self</h3>
<div id="fig-wat-arse" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-wat-arse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/wat_arse_D12976.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: Knowing thyself through reflection"><img src="https://danmackinlay.name/images/wat_arse_D12976.avif" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-wat-arse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Knowing thyself through reflection
</figcaption>
</figure>
</div>
<p>A direct ML instantiation is Ha &amp; Schmidhuber’s <em>World Models</em> <span class="citation" data-cites="Ha2018World">(Ha and Schmidhuber 2018)</span>, where a recurrent latent model predicts both environment dynamics <em>and</em> the consequences of the agent’s own actions. The agent’s policy is trained inside this compressed dreamscape. The self here is a reduced-rank conditional — “what would my controller do, given this latent” — rather than an introspectable entity, but the “compression” part sounds well-posed.</p>
<p>The lineage continues through Dreamer <span class="citation" data-cites="Hafner2024Mastering">(Hafner et al. 2024)</span>, MuZero <span class="citation" data-cites="Schrittwieser2020Mastering">(Schrittwieser et al. 2020)</span>, and the larger world-model-RL programme.</p>
<p>See also <a href="../notebook/world_models.html">world models</a>.</p>
</section>
<section id="active-inference-and-the-self-as-generative-model" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="active-inference-and-the-self-as-generative-model"><span class="header-section-number">3.2</span> Active inference and the self as generative model</h3>
<p><a href="../notebook/predictive_coding.html">Active inference</a> <span class="citation" data-cites="Friston2017Active Parr2022Active">(Friston et al. 2017; Parr, Pezzulo, and Friston 2022)</span> treats the agent as a generative model of its own sensorium, including its own actions. Free-energy minimization forces the self-model to be as compressed as is consistent with prediction — a direct rate-distortion pressure. The <em>self</em> here is a probabilistic model with the agent’s own observation-action trajectory as a latent.</p>
</section>
<section id="self-modelling-robots" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="self-modelling-robots"><span class="header-section-number">3.3</span> Self-modelling robots</h3>
<p>A concrete line: Bongard, Zykov, and Lipson’s <em>Resilient Machines Through Continuous Self-Modeling</em> <span class="citation" data-cites="Bongard2006Resilient">(Bongard, Zykov, and Lipson 2006)</span> — a quadruped robot that learns a forward model of its own body, then uses it to plan locomotion; when a limb is damaged, the model updates, and the robot recovers. The self-model is an explicit, parameterized, low-rank dynamical system. See the follow-up <span class="citation" data-cites="Kwiatkowski2019Taskagnostic">(Kwiatkowski and Lipson 2019)</span> for differentiable variants.</p>
</section>
<section id="attention-schema-theory" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="attention-schema-theory"><span class="header-section-number">3.4</span> Attention Schema Theory</h3>
<p>Graziano <span class="citation" data-cites="Graziano2013Consciousness Graziano2019Standard">(Graziano 2013; Graziano et al. 2019)</span> argues that consciousness <em>is</em> the brain’s (incomplete, schematic) model of its own attention. This is explicitly a reduced-rank model: the schema is coarser than the machinery it represents, because representing attention in full would require as much machinery as attention itself. Kaplan, Dolan, and colleagues have attempted to operationalize this in neural-network models <span class="citation" data-cites="Wilterson2021Attention">(Wilterson and Graziano 2021)</span>.</p>
</section>
<section id="schmidhuber-and-reflective-learners" class="level3" data-number="3.5">
<h3 data-number="3.5" class="anchored" data-anchor-id="schmidhuber-and-reflective-learners"><span class="header-section-number">3.5</span> Schmidhuber and reflective learners</h3>
<p>Schmidhuber’s early work on <em>self-referential neural networks</em> <span class="citation" data-cites="Schmidhuber1993SelfReferential">(Schmidhuber 1993)</span> and later <em>Gödel machines</em> <span class="citation" data-cites="Schmidhuber2003Godel">(Schmidhuber 2003)</span> formalizes learners that inspect and modify their own code, subject to provability constraints. The Gödel-machine construction is where the <em>proof-theoretic</em> aspect of self-modelling seems to cause grief: self-modification is gated by a proof that the modification improves expected utility.</p>
</section>
<section id="predictive-coding-and-hierarchical-self" class="level3" data-number="3.6">
<h3 data-number="3.6" class="anchored" data-anchor-id="predictive-coding-and-hierarchical-self"><span class="header-section-number">3.6</span> Predictive coding and hierarchical self</h3>
<p>Hierarchical predictive coding architectures <span class="citation" data-cites="Rao1999Predictive Clark2013Whatever">(Rao and Ballard 1999; Clark 2013)</span> include top-down predictions that span the whole sensory hierarchy, including proprioceptive and interoceptive signals — i.e., representations <em>of</em> the organism. See <a href="../notebook/predictive_coding.html">predictive coding</a>, again. And harder.</p>
</section>
<section id="reflective-llm-agents" class="level3" data-number="3.7">
<h3 data-number="3.7" class="anchored" data-anchor-id="reflective-llm-agents"><span class="header-section-number">3.7</span> Reflective LLM agents</h3>
<p>A lighter ML lineage treats self-reflection as a control loop over the decoder rather than as a learnt world-model. <em>Reflexion</em> <span class="citation" data-cites="Shinn2023Reflexion">(Shinn et al. 2023)</span> keeps a log of past behaviour, self-critiques, and revised plans in the context window, so the agent adjusts across episodes without any gradient update — verbal reinforcement learning rather than the usual kind. <em>Language Agent Tree Search</em> <span class="citation" data-cites="Zhou2023Language">(Zhou et al. 2024)</span> extends this to a Monte-Carlo tree search over action paths, with an LLM-powered value function and self-reflection pruning unpromising branches. The self-model here is an in-context transcript and the reflection is a prompt — coarse compared with active inference or Dreamer, and with no mechanism for the self-model to outlive the context. I include them as an existence proof for a reduced-rank self-model whose entire cost lives at inference time rather than in training, which is a distinct point on the budget frontier from everything above.</p>
</section>
</section>
<section id="reducing-fidelity-of-representation" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="reducing-fidelity-of-representation"><span class="header-section-number">4</span> Reducing fidelity of representation</h2>
<p>Several toolkits formalize “reduced rank”:</p>
<ul>
<li><strong>Rate-distortion theory</strong> applied to cognition <span class="citation" data-cites="Sims2012Ideal Zenon2019Informationtheoretic Lai2021Policy">(Sims, Jacobs, and Knill 2012; Zénon, Solopchuk, and Pezzulo 2019; Lai and Gershman 2021)</span>: the cost of mental representation is an information-theoretic rate, the benefit is task performance, and optimal bounded agents sit on the rate-distortion frontier.</li>
<li><strong><a href="../notebook/nearly_sufficient_statistics_bottlenecks.html">Information bottleneck</a></strong> <span class="citation" data-cites="Tishby2000Information Alemi2019Deep">(Tishby, Pereira, and Bialek 2000; Alemi et al. 2019)</span>: compress inputs to a latent that is maximally informative about a downstream variable. When the downstream variable is “the other agent’s next action”, the bottleneck induces a reduced-rank other-model.</li>
<li><strong>Resource-rational analysis</strong> <span class="citation" data-cites="Lieder2020Resourcerational">(Lieder and Griffiths 2020)</span>: agents are optimal given bounded compute; the bound is the reduction.</li>
<li><strong>Successor representations / features</strong> <span class="citation" data-cites="Dayan1993Improving Barreto2017Successor">(Dayan 1993; Barreto et al. 2017)</span>: compressed future-prediction models that generalize well across reward functions. A kind of reduced-rank self-model of one’s own policy.</li>
<li><strong><a href="../notebook/bounded_rationality.html">Bounded rationality</a></strong> as a research programme <span class="citation" data-cites="Simon1955Behavioral Russell1995Provably Russell2016Rationality">(Simon 1955; S. J. Russell and Subramanian 1995; S. Russell 2016)</span>.</li>
<li><strong><a href="../notebook/computational_mechanics.html">Epsilon-machines / computational mechanics</a></strong> <span class="citation" data-cites="Crutchfield1989Inferring Shalizi2000Computational">(Crutchfield and Young 1989; Shalizi and Crutchfield 2000)</span>: minimal-sufficient-statistic models of a process. The <em>causal states</em> are the minimum-rank predictor.</li>
</ul>
<p>My research agent further recommends we look at <em>theory of mind as mutual information</em> <span class="citation" data-cites="JaraEttinger2019Theory">(Jara-Ettinger 2019)</span> and the recent graph-theoretic accounts of <em>social abstraction</em> <span class="citation" data-cites="Stolk2016Conceptual">(Stolk, Verhagen, and Toni 2016)</span>.</p>
</section>
<section id="multi-agent-self" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="multi-agent-self"><span class="header-section-number">5</span> Multi-agent self</h2>
<p>The bicameral intuition — “the mind is many minds talking” — turns up across Minsky’s <em>Society of Mind</em> <span class="citation" data-cites="Minsky1986Society">(Minsky 1986)</span>, Global Workspace Theory <span class="citation" data-cites="Baars1993Cognitive Dehaene2014Consciousness">(Baars 1993; Dehaene 2014)</span> and its neural-network and RL formalizations <span class="citation" data-cites="VanRullen2021Deep Goyal2021Coordination">(VanRullen and Kanai 2021; Goyal et al. 2021)</span>, mixture of experts <span class="citation" data-cites="Jacobs1991Adaptive Shazeer2017Outrageously">(Jacobs et al. 1991; Shazeer et al. 2017)</span>, and Dennett’s multiple drafts <span class="citation" data-cites="Dennett1993Consciousness">(Dennett 1993)</span>. They share an architectural claim: the “self” is what falls out of specialist modules competing for a low-capacity shared bottleneck, and that bottleneck is where the reduced-rank self- and other-models have to sit. Attention Schema Theory (above) fits here too. Fuller treatment at <a href="../notebook/multi_agent_self.html">multi-agent self</a>.</p>
</section>
<section id="strange-loops" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="strange-loops"><span class="header-section-number">6</span> Strange loops</h2>
<p><em>I Am a Strange Loop</em> <span class="citation" data-cites="Hofstadter2008Am">(Hofstadter 2008)</span> is Hofstadter restating the central argument of <em>Gödel, Escher, Bach</em> decades later because, by his own account, everyone read the first book for the fugues and missed it (including me). The claim: the “I” is not <em>the thing that has a self-model</em>; the “I” <em>is</em> the self-model. A brain past a threshold of representational capacity cannot, he argues, avoid coining a symbol for the system it finds itself to be, for the same reason Gödel could make <em>Principia Mathematica</em> talk about <em>Principia Mathematica</em> — past that threshold, self-reference stops being optional. Something seems off about that metaphor, but we can run with it.</p>
<p>The symbol is coarser than the thing, necessarily; this is the opening observation again, the only faithful model of Bob being Bob. And it is causally load-bearing: actions get computed <em>through</em> the self-symbol, which must then re-represent the behaviour it just helped produce. A representation steering the thing it represents, around and around, is the loop; the level-crossing — a coarse abstraction pushing on the micro-machinery that implements it — is what garners the adjective.<sup>2</sup></p>
<p>The relevance to this notebook’s title is that a homunculus theory sounds like it should account for who watches the watcher, and a strange loop is a candidate answer.</p>
<p>First attempt: the chain can terminate: each inner model is coarser than its owner, and a few rungs down the innermost agent has decayed into noise.</p>
<p>Second attempt: the chain can close: followed far enough, the watcher turns out to be the watched, met again after a trip around the loop. Hofstadter’s bet is that selves take the second exit, and that closure is a fixed point of the representational hierarchy.</p>
<p>His standing physical model is video feedback: point a camera at its own monitor and the screen fills with nested corridors, spirals, reverberating structure not present in the room. Each pass through the loop re-represents the whole scene at the camera’s resolution, so the regress of frames-within-frames does not run to infinity; it runs to the grain of the sensor and ends in blur. The lossiness is why the iteration settles at all.</p>
<p>Whether the loop converges depends on the rank of the self-model. A full-rank, lossless self-model is where closure breaks: it supports diagonalization, so the agent can always compute the act that defeats its own prediction. That is the same pathology that puts a cycle into a mechanized causal graph when Bob models Alice’s output instead of her mechanism <span class="citation" data-cites="MacDermott2023Characterising">(MacDermott, Everitt, and Belardinelli 2023)</span>, and demanding that the loop close <em>provably</em> is where Löb’s theorem charges its toll <span class="citation" data-cites="Yudkowsky2013Tiling">(Yudkowsky and Herreshoff 2013)</span>. A reduced-rank self-model loses detail on every pass, so the regress of self-within-self converges instead of running forever — the way the video corridor settled into blur.</p>
<p>Two different properties are in play: the self-model being low-rank (a static fact about fidelity) and the self-referential iteration converging to a fixed point (a dynamic one). The budget frame below treats the first as a cost charged against reflectivity; Hofstadter’s picture makes the first the precondition for the second — reduced rank is what lets the loop converge at all. The homunculus can regulate the head only because it is smaller than the head.</p>
<p>There is also an accounting reading. A closed loop reuses the same model on every pass, so its rank is paid for once, no matter how deep the nominal recursion. Unrolling a mechanized MAID into an extensive-form game instead pays for depth in nodes, exponentially <span class="citation" data-cites="Hammond2023Reasoning">(Hammond et al. 2023)</span>; SOM pays for its other-model by reusing the agent’s own policy <span class="citation" data-cites="Raileanu2018Modeling">(Raileanu et al. 2018)</span>, and a quine reproduces itself by reusing code as data. Closing the loop trades storage cost for convergence cost — how many passes, settling to what error — which is at least the kind of quantity rate-distortion tools could price.</p>
<p>The book also contains, almost in passing, the most direct pre-formal statement of the reduced-rank other-model I have encountered: knowing a person is hosting a low-resolution copy of their loop, each of us running the self-symbols of everyone we are close to at whatever fidelity contact has paid for, the copy differing from the original in rank rather than in kind. (The book is organized around grief — the surviving copy of his late wife’s loop is the worked example.) This sharpens the Bob-and-Alice setup at the top: when the thing being compressed is itself a self, what Bob carries is a compressed <em>loop</em> — Alice’s self-symbol, not merely her policy — and two people modelling each other host a pair of mutually embedded loops. Program equilibrium makes the same shape exact: modal agents conditioning on one another’s source code close a two-agent loop by Löbian handshake <span class="citation" data-cites="Barasz2014Robust Critch2019Parametric">(Bárász et al. 2014; Critch 2019)</span> — a mutual strange loop with the poetry stripped out.</p>
<p>Where does this leave the formal programme? The self-consistency cluster below reads naturally as the project of closing Hofstadter’s loop exactly — fixed points over randomized answers, beliefs converging on themselves in the limit, proof-gated self-modification — with the Löbian obstacle as the price of exactness. And the mechanized-causal-graph pivot flagged in the callout up top inherits a sharp question. In that formalism a cycle is a symptom: it appears when Bob models Alice’s output instead of her mechanism. On Hofstadter’s account a cycle at the right level just <em>is</em> the self. Whether the mechanism layer can host a benign cycle — and what “benign” costs in rank — looks like the version of this post’s question that survives the pivot.</p>
</section>
<section id="self-referential-agents-and-the-proof-theoretic-frontier" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="self-referential-agents-and-the-proof-theoretic-frontier"><span class="header-section-number">7</span> Self-referential agents and the proof-theoretic frontier</h2>
<p>This is where <em>reflectivity</em> — the pressure for an agent’s beliefs about its own future beliefs to be consistent — becomes a first-class concern. If we want to <em>prove</em> things about minds that model themselves, we hit self-reference, and self-reference hits Löb’s theorem and friends if we’re not careful. As presaged, I am not super excited about the parts of this line of work that edge into unbounded compute.</p>
<p>Reflectivity is operationalized differently by each of the constructions below, and each buys self-consistency in a different coin — fixed-point iteration, market mixing, proof search — so its “cost” lives on a different axis depending on who we ask. I’ll treat its cost as real but construction-dependent.</p>
<ul>
<li><strong>The self-consistency cluster.</strong> Reflective oracles <span class="citation" data-cites="Fallenstein2015Reflective">(Fallenstein, Taylor, and Christiano 2015)</span> (a fixed-point construction of probability distributions closed under self-reference), logical induction <span class="citation" data-cites="Garrabrant2020Logical">(Garrabrant et al. 2020)</span> (a market-based learner whose beliefs about its own future beliefs converge), and the Löbian obstacle <span class="citation" data-cites="Yudkowsky2013Tiling">(Yudkowsky and Herreshoff 2013)</span> (the negative result that a self-modifying agent unwilling to endorse a weaker-or-equal successor runs into Löb’s theorem): three constructions attacking the same self-consistency problem in three different coins.</li>
<li><strong><a href="../notebook/bayes_imprecise.html">Infra-Bayesianism</a></strong> <span class="citation" data-cites="Kosoy2020Infra">(Kosoy and Appel 2020)</span> replaces the prior — a single measure over environments — with a convex set of sub-probability measures (a credal set, in the imprecise-probability sense), and decides by minimax over the set. Two things this buys at once: non-realizability (the true environment need not be in the support of any single measure, so the Big-World problem from <a href="../notebook/agency_bounded_compute.html">agency under bounded compute</a> is no longer an outright contradiction), and a way to dodge some self-reference paradoxes by letting the agent’s beliefs about its own future actions be imprecise rather than committed to a specific distribution. Infra-Bayesian RL has regret bounds against non-realizable environments, and the <em>infra-Bayesian physicalism</em> extension <span class="citation" data-cites="Kosoy2021InfraBayesian">(Kosoy and Appel 2021)</span> targets embeddedness directly. In the budget frame it spends on reflectivity via imprecision, in a different coin from fixed-point iteration, market mixing, or proof search — and like the others, it spends nothing on bounded compute.</li>
<li><strong>Modal combat agents</strong> and <em>program equilibrium</em> <span class="citation" data-cites="Barasz2014Robust Critch2019Parametric">(Bárász et al. 2014; Critch 2019)</span>: agents that condition on source-code-level models of each other; admits bona fide equilibria in the one-shot prisoner’s dilemma.</li>
<li><strong>AIXI and approximations</strong> <span class="citation" data-cites="Hutter2005Universal Leike2016Thompson">(Hutter 2005; Leike et al. 2016)</span>: a formally optimal agent whose self-model is implicit in the universal prior; computable approximations (e.g., AIXI-tl, MC-AIXI) buy tractability at the cost of reducing the rank of the prior.</li>
</ul>
<p>MIRI’s <a href="../notebook/agent_foundations.html">Agent Foundations</a> programme is the main hub for this line of work.</p>
</section>
<section id="lets-attempt-synthesis" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="lets-attempt-synthesis"><span class="header-section-number">8</span> Let’s attempt synthesis!</h2>
<p>The three axes above — other-modelling, self-modelling, reduced-rank — are not independent knobs an architect sets arbitrarily. I’d argue they are three demands on the compute embedded in a single joint representation carried by a social agent — depth of recursion, rank of each sub-model, and reflectivity — competing for the same finite resources. This is of a piece with my general heuristic that we should always think about <a href="../notebook/ai_economics_of_cognition.html">where to spend our compute</a> to make sense of the AI landscape as it exists.</p>
<p>“Compute” here is a cover term: it lumps together bits of representation at rest, operations per decision, and data to fit the representation in the first place. These don’t fully interchange, and nobody defines a fungible unit that resolves the trade-offs cleanly AFAIK. For the argument that follows the weaker claim is enough — that the three axes all draw on a shared pool, and that each spends it through its own, largely unknown, return on investment. Rate-distortion theory gives a concave <img src="https://latex.codecogs.com/png.latex?R(D)"> for rank <span class="citation" data-cites="Sims2012Ideal">(Sims, Jacobs, and Knill 2012)</span>; the analogous curves for recursion depth and reflectivity are open problems.</p>
<p>On this reading the existing literature is a tour of <em>corner solutions</em>: each framework spends its compute on one or two axes and lets the rest go to zero.</p>
<ul>
<li><strong>I-POMDPs</strong> and <strong>level-<img src="https://latex.codecogs.com/png.latex?k"> / cognitive hierarchy</strong>: spend on depth; each level is a cartoon of the next. Humans clustering at <img src="https://latex.codecogs.com/png.latex?k%20%5Cin%20%5C%7B0,1,2%5C%7D"> is consistent with a small budget.</li>
<li><strong>Rate-distortion cognition</strong>, <strong>information bottleneck</strong>, <strong>successor features</strong>: spend on rank at fixed depth (<img src="https://latex.codecogs.com/png.latex?k=1">); reflectivity ignored.</li>
<li><strong>Reflective oracles</strong>, <strong>logical induction</strong>, <strong>Gödel machines</strong>, <strong>infra-Bayesianism</strong>: spend on reflectivity; other-structure kept simple enough that the construction survives.</li>
<li><strong>Strange loops</strong> <span class="citation" data-cites="Hofstadter2008Am">(Hofstadter 2008)</span>: all-in on closure; depth amortized into the cycle; rank acknowledged — the self-symbol is coarse by construction — but never priced.</li>
<li><strong>Active inference</strong>: allocation across self- and world-model; depth and reflectivity implicit.</li>
<li><strong>LOLA / opponent shaping</strong>: <img src="https://latex.codecogs.com/png.latex?k=2">; rank inherited from the opponent’s parameter count.</li>
<li><strong>World models</strong> (Ha/Schmidhuber, Dreamer, MuZero): storage in the latent dimension; <img src="https://latex.codecogs.com/png.latex?k=1">; reflectivity absent.</li>
<li><strong>ToMnet</strong> and <strong>I-POMDP-Net</strong> <span class="citation" data-cites="Han2019IPOMDPNet">(Han and Gmytrasiewicz 2019)</span>: learned reduced-rank other-models; <img src="https://latex.codecogs.com/png.latex?k%20%5Cle%202">; reflectivity absent.</li>
<li><strong>SOM</strong> <span class="citation" data-cites="Raileanu2018Modeling">(Raileanu et al. 2018)</span>: <img src="https://latex.codecogs.com/png.latex?k=1"> with self-model and other-model sharing bits; reflectivity absent. A case of the axes interacting rather than competing.</li>
<li><strong>Reflexion / LATS</strong> <span class="citation" data-cites="Shinn2023Reflexion Zhou2023Language">(Shinn et al. 2023; Zhou et al. 2024)</span>: inference-time reflectivity over an in-context self-model; no learnt other-model; rank bounded by the context window.</li>
</ul>
<section id="depth-as-a-profile" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="depth-as-a-profile"><span class="header-section-number">8.1</span> Depth as a profile</h3>
<p>The “depth” axis above reads as a single number <img src="https://latex.codecogs.com/png.latex?k"> in level-<img src="https://latex.codecogs.com/png.latex?k"> and I-POMDP treatments, but that collapses some structure worth pulling apart. Bob regulates a scene that contains Alice; there is a natural partial ordering on what kind of thing Alice can be inside Bob’s world-model.</p>
<ol start="0" type="1">
<li>Alice as noise — a draw from a distribution over behaviours. No agent-shaped variables in Bob’s ontology.</li>
<li>Alice as a stateful process — memory, non-Markovian dynamics, possibly a “type” Bob is inferring. Not intentional.</li>
<li>Alice as a belief-carrier whose model of Bob is type-0. She has goals, she is optimizing against a world-model, but that world-model contains Bob only as noise.</li>
<li>Alice as a belief-carrier whose Bob-model is type-1. She conditions on Bob’s type but does not treat him as intentional.</li>
<li>Alice as a belief-carrier whose Bob-model is itself belief-carrying. Recursion; the same classification applies at the next rung.</li>
</ol>
<p>Scalar <img src="https://latex.codecogs.com/png.latex?k"> is the uniform case: every rung up to depth <img src="https://latex.codecogs.com/png.latex?k"> is type-4, and the chain terminates in type-0 at the bottom. What the scalar flattens is the <em>profile</em> — a sequence of (ontology type, rank) pairs along the chain, which in general need not be uniform. In particular it throws away <em>asymmetric drop-off</em>: “Alice thinks I am nearly noise” (type-2 terminal) and “Alice thinks I am stateful” (type-3 terminal) are both depth-2 in scalar terms, but play differently. In the first Bob has slack to act unpredictably; in the second Alice is already conditioning on his type, and only type-misrepresentation is exploitable. Budget-constrained agents probably allocate non-uniformly — most of their rank on the first rung or two, terminating early into type 0 or 1. The human plateau at <img src="https://latex.codecogs.com/png.latex?k%20%5Cin%20%5C%7B0,1,2%5C%7D"> is at least as consistent with this as with “humans can’t recurse further”, and the shallow-but-lopsided flavour of LLM-ToM failure <span class="citation" data-cites="Ullman2023Large">(Ullman 2023)</span> looks similar.</p>
<p>Two scalars suggest themselves in place of <img src="https://latex.codecogs.com/png.latex?k"> and they answer different questions: chain length (the traditional quantity), and total rank spent on reflection, <img src="https://latex.codecogs.com/png.latex?%5Csum_i%20R_i"> along the chain. The budget frame of this post is more naturally the second; the level-<img src="https://latex.codecogs.com/png.latex?k"> literature has mostly used the first.<sup>3</sup></p>
<p>Both scalars, and the profile itself, assume the chain is a chain. A self-model is the case where the chain closes instead of terminating, and the profile picks up a cycle — which scalar <img src="https://latex.codecogs.com/png.latex?k"> cannot write down at all. The strange-loop section above argues the cycle is not a degenerate case; it is where the “self” part of the programme lives.</p>
<p>Humans, LLMs, and the agents we would actually like to build do not sit at any corner. They are interior points, and the interior is where unwritten papers live.</p>
</section>
</section>
<section id="angles-of-attack" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="angles-of-attack"><span class="header-section-number">9</span> Angles of attack</h2>
<p>I got an LLM to ideate below ideas for me. Lightly edited for baseline sanity. The budget view suggests several angles of attack on what we actually care about — understanding how real cognition, human or machine, allocates its compute across world, self, and other. Two concrete ones, plus a brief aside on scaling the same calculus to collectives.</p>
<section id="finding-phi-in-a-transformer" class="level3" data-number="9.1">
<h3 data-number="9.1" class="anchored" data-anchor-id="finding-phi-in-a-transformer"><span class="header-section-number">9.1</span> Finding <img src="https://latex.codecogs.com/png.latex?%5CPhi"> in a transformer</h3>
<p>The synthesis above says a bounded social agent carries a structured latent — call it <img src="https://latex.codecogs.com/png.latex?%5CPhi"> — partitioned into world, self, each other, and reflective bookkeeping. The ToM-in-LLMs literature <span class="citation" data-cites="Kosinski2023Theory Ullman2023Large">(Kosinski 2023; Ullman 2023)</span> presently seems to treat these pieces as present-or-absent behavioural properties. If a frontier model has acquired any social competence under finite pretraining compute, there should be detectable structure in <img src="https://latex.codecogs.com/png.latex?%5CPhi"> tracking these pieces — not necessarily a neat partition, because superposition lets a learner share parameters across sparse features, but some signature visible to the right probe.</p>
<p>That is a mechanistic-interpretability problem. Run sparse-autoencoder or dictionary-learning analysis on the residual stream during ToM-style tasks, and look for:</p>
<ul>
<li>a low-rank subspace whose ablation selectively breaks co-agent prediction without breaking world-prediction;</li>
<li>a disjoint self-model subspace whose ablation breaks in-character behaviour but not factual recall;</li>
<li>approximately no reflective-bookkeeping subspace in base models, and a small one in post-RLHF models — which are rewarded for consistency with a remembered “I”, and so should be under pressure to allocate bits to reflective bookkeeping that a base model is not.</li>
</ul>
<p>It is also the careenium question <span class="citation" data-cites="Hofstadter2008Am">(Hofstadter 2008)</span> with an effect size attached — Hofstadter asks whether the causal story of a system is better told over coarse symbols (his <em>simmballs</em>, groan) than over the micro-dynamics implementing them; ablation asks the residual stream the same thing.</p>
<p>If the structure is not there, one of three things is wrong: the framework, our assumption that representational storage is actually binding for this class of model, or our belief that the pretraining signal actually rewards social prediction rather than a confound. Each possibility is informative. If the structure <em>is</em> there, we have an interpretability handle on social cognition specifically, rather than features in general.</p>
</section>
<section id="a-value-of-reflectivity-calculation" class="level3" data-number="9.2">
<h3 data-number="9.2" class="anchored" data-anchor-id="a-value-of-reflectivity-calculation"><span class="header-section-number">9.2</span> A value-of-reflectivity calculation</h3>
<p>The MIRI tradition treats reflectivity as a correctness property: a reasoner that cannot consistently model its own reasoning has a bug, and the whole <a href="../notebook/agent_foundations.html">agent foundations</a> edifice is a search for bug-free constructions. The budget view recasts it as an economic question: when is it worth spending bits on reflective machinery, and when is it better to skip it?</p>
<p>An idea in this space is that reflectivity pays in proportion to the <em>reactivity</em> of the environment — the degree to which other agents, or future versions of ourselves, condition on our <em>internal commitments</em> rather than just our past actions. Non-reactive one-shot stateless POMDPs: reflectivity is dispensable. Program-equilibrium and modal-combat settings <span class="citation" data-cites="Barasz2014Robust Critch2019Parametric">(Bárász et al. 2014; Critch 2019)</span>: reflectivity must be high, because opponents are literally reading our source. Open-ended self-modification (Gödel-machine style): Löbian obstacles arise exactly because reflectivity has to be preserved while the substrate is mutating underneath it.</p>
<p>A testable version of this, without the formalism: build toy environments in each regime — a non-reactive bandit, a modal-combat tournament, a self-modification sandbox — and train the same architecture across all three. The conjecture predicts that reflective structure (by whatever detector the <img src="https://latex.codecogs.com/png.latex?%5CPhi"> angle supplies) should emerge spontaneously in the second and third, and not the first. That is a scaling claim about spontaneous situational awareness under training pressure, and it falsifies in either direction.</p>
<p>Premakumar et al. <span class="citation" data-cites="Premakumar2024Unexpected">(Premakumar et al. 2024)</span> train networks with an auxiliary task of predicting their own internal activations and observe narrower weight distributions and reduced effective complexity — a “self-prediction-as-regularizer” effect. As a bonus the regularized networks are easier for <em>other</em> networks to model. If the effect replicates beyond their setting then reflectivity is paying for itself partly in rank elsewhere, which is the shape of trade-off the budget frame predicts. It also gives an interpretability-friendly story about alignment: agents incentivised to self-predict become more predictable to overseers, for free. <a href="https://www.lesswrong.com/posts/5se67gAcaExEYdCCg/self-prediction-acts-as-an-emergent-regularizer?ref=danmackinlay.name">The LessWrong writeup</a> makes the alignment spin explicit.</p>
</section>
<section id="collectives-are-instrumentable" class="level3" data-number="9.3">
<h3 data-number="9.3" class="anchored" data-anchor-id="collectives-are-instrumentable"><span class="header-section-number">9.3</span> Collectives are instrumentable</h3>
<p>The same budget calculus should scale past individual minds. A firm, team, or agency is a social agent carrying compressed models of itself and its counterparties — self-model embodied in policy and narrative, other-models in competitor dossiers and customer segments, reflectivity in governance and audit. This complements the transformer angle above: organizations are, if anything, easier to instrument than LLMs — meeting notes, decision logs, communication graphs, and promotion criteria are legible in a way residual-stream activations are not. Mapping those proxies to rank, depth, and reflectivity is a separate project, but the framework commits to specific organizational over-allocations (governance theatre, regulatory capture, siloed product teams, academic insularity) being allocation failures of the same kind as the cognitive ones, which gives two independent scales at which to play with it.</p>
</section>
</section>
<section id="related" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="related"><span class="header-section-number">10</span> Related</h2>
<ul>
<li><a href="../notebook/agent_foundations.html">Agent foundations</a></li>
<li><a href="../notebook/causality_multi_agent.html">Causality in multi-agent systems</a></li>
<li><a href="../notebook/predictive_coding.html">Predictive coding</a></li>
<li><a href="../notebook/dunning_kruger_theory_of_mind.html">Dunning-Kruger theory of mind</a> — the failure modes of <em>human</em> other-models</li>
<li><a href="../notebook/iterated_social_games.html">Iterated social games</a></li>
</ul>
<div class="callout callout-style-simple callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Credit
</div>
</div>
<div class="callout-body-container callout-body">
<p>This notebook didn’t come from nowhere; I have been writing this to process ideas that I have been discussing with my colleagues. I am especially indebted to Martin Biehl, <a href="https://artemyk.github.io?ref=danmackinlay.name">Artemy Kolchinsky</a>, <a href="https://aclevername.substack.com?ref=danmackinlay.name">Chris Pang</a>, <a href="https://boundedlyrational.substack.com?ref=danmackinlay.name">Jan Kulveit</a> and <a href="https://www.tomeveritt.se?ref=danmackinlay.name">Tom Everitt</a>.</p>
</div>
</div>
</section>
<section id="incoming" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="incoming"><span class="header-section-number">11</span> Incoming</h2>
<ul>
<li>Hofstadter and Dennett’s <em>The Mind’s I</em> as a pre-formal reading list.</li>
<li>Tononi’s IIT as a measure on self-models, if one is feeling brave.</li>
<li>Whether the “observer self” of contemplative traditions corresponds to an attention-schema-style reduced-rank self-model — this seems to be Metzinger’s read.</li>
</ul>
</section>
<section id="references" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="references"><span class="header-section-number">12</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Alemi2019Deep" class="csl-entry">
Alemi, Fischer, Dillon, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1612.00410">Deep Variational Information Bottleneck</a>.”</span>
</div>
<div id="ref-Baars1993Cognitive" class="csl-entry">
Baars. 1993. <em>A Cognitive Theory of Consciousness</em>.
</div>
<div id="ref-Baker2017Rational" class="csl-entry">
Baker, Chris L., Jara-Ettinger, Saxe, et al. 2017. <span>“<a href="https://doi.org/10.1038/s41562-017-0064">Rational Quantitative Attribution of Beliefs, Desires and Percepts in Human Mentalizing</a>.”</span> <em>Nature Human Behaviour</em>.
</div>
<div id="ref-Baker2011Bayesian" class="csl-entry">
Baker, Chris, Saxe, and Tenenbaum. 2011. <span>“<a href="https://escholarship.org/uc/item/5rk7z59q">Bayesian Theory of Mind: Modeling Joint Belief-Desire Attribution</a>.”</span> <em>Proceedings of the Annual Meeting of the Cognitive Science Society</em>.
</div>
<div id="ref-Barasz2014Robust" class="csl-entry">
Bárász, Christiano, Fallenstein, et al. 2014. <span>“<a href="https://arxiv.org/abs/1401.5577">Robust Cooperation in the Prisoner’s Dilemma: Program Equilibrium via Provability Logic</a>.”</span>
</div>
<div id="ref-Barreto2017Successor" class="csl-entry">
Barreto, Dabney, Munos, et al. 2017. <span>“<a href="http://arxiv.org/abs/1606.05312">Successor Features for Transfer in Reinforcement Learning</a>.”</span> In <em>Advances in Neural Information Processing Systems</em>.
</div>
<div id="ref-Berg2024Selfprediction" class="csl-entry">
Berg, Plumb, and Ganin. 2024. <span>“<a href="https://www.lesswrong.com/posts/5se67gAcaExEYdCCg/self-prediction-acts-as-an-emergent-regularizer">Self-Prediction Acts as an Emergent Regularizer</a>.”</span> <em>LessWrong</em>.
</div>
<div id="ref-Bilodeau2023Minimax" class="csl-entry">
Bilodeau, Foster, and Roy. 2023. <span>“<a href="https://doi.org/10.1214/23-AOS2270">Minimax Rates for Conditional Density Estimation via Empirical Entropy</a>.”</span> <em>The Annals of Statistics</em>.
</div>
<div id="ref-Bongard2006Resilient" class="csl-entry">
Bongard, Zykov, and Lipson. 2006. <span>“<a href="https://doi.org/10.1126/science.1133687">Resilient Machines Through Continuous Self-Modeling</a>.”</span> <em>Science</em>.
</div>
<div id="ref-Camerer2004Cognitive" class="csl-entry">
Camerer, Ho, and Chong. 2004. <span>“<a href="https://doi.org/10.1162/0033553041502225">A Cognitive Hierarchy Model of Games</a>.”</span> <em>The Quarterly Journal of Economics</em>.
</div>
<div id="ref-Clark2013Whatever" class="csl-entry">
Clark. 2013. <span>“<a href="https://doi.org/10.1017/s0140525x12000477">Whatever Next? Predictive Brains, Situated Agents, and the Future of Cognitive Science</a>.”</span> <em>Behavioral and Brain Sciences</em>.
</div>
<div id="ref-Critch2019Parametric" class="csl-entry">
Critch. 2019. <span>“<a href="https://doi.org/10.1017/jsl.2017.42">A Parametric, Resource-Bounded Generalization of Löb’s Theorem, and a Robust Cooperation Criterion for Open-Source Game Theory</a>.”</span> <em>The Journal of Symbolic Logic</em>.
</div>
<div id="ref-Crutchfield1989Inferring" class="csl-entry">
Crutchfield, and Young. 1989. <span>“<a href="https://doi.org/10.1103/PhysRevLett.63.105">Inferring Statistical Complexity</a>.”</span> <em>Physical Review Letters</em>.
</div>
<div id="ref-Dayan1993Improving" class="csl-entry">
Dayan. 1993. <span>“<a href="https://doi.org/10.1162/neco.1993.5.4.613">Improving Generalization for Temporal Difference Learning: The Successor Representation</a>.”</span> <em>Neural Computation</em>.
</div>
<div id="ref-Dehaene2014Consciousness" class="csl-entry">
Dehaene. 2014. <em>Consciousness and the Brain: Deciphering How the Brain Codes Our Thoughts</em>.
</div>
<div id="ref-Dekel2015Epistemic" class="csl-entry">
Dekel, and Siniscalchi. 2015. <span>“<a href="https://doi.org/10.1016/b978-0-444-53766-9.00012-4">Epistemic Game Theory</a>.”</span> In.
</div>
<div id="ref-Dennett1993Consciousness" class="csl-entry">
Dennett. 1993. <em>Consciousness Explained</em>.
</div>
<div id="ref-Fallenstein2015Reflective" class="csl-entry">
Fallenstein, Taylor, and Christiano. 2015. <span>“<a href="https://doi.org/10.48550/arXiv.1508.04145">Reflective Oracles: A Foundation for Classical Game Theory</a>.”</span>
</div>
<div id="ref-Feng2020Online" class="csl-entry">
Feng, and Kirkley. 2020. <span>“<a href="https://arxiv.org/abs/2009.10461">Online Geolocalized Emotion Across US Cities During the COVID Crisis: Universality, Policy Response, and Connection with Local Mobility</a>.”</span> <em>arXiv.org</em>.
</div>
<div id="ref-Foerster2018Learning" class="csl-entry">
Foerster, Chen, Al-Shedivat, et al. 2018. <span>“<a href="http://arxiv.org/abs/1709.04326">Learning with Opponent-Learning Awareness</a>.”</span> In <em>Proceedings of the 17th International Conference on Autonomous Agents and MultiAgent Systems</em>. AAMAS ’18.
</div>
<div id="ref-Friston2017Active" class="csl-entry">
Friston, FitzGerald, Rigoli, et al. 2017. <span>“<a href="https://doi.org/10.1162/neco_a_00912">Active Inference: A Process Theory</a>.”</span> <em>Neural Computation</em>.
</div>
<div id="ref-Gandhi2023Understanding" class="csl-entry">
Gandhi, Franken, Gerstenberg, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2306.15448">Understanding Social Reasoning in Language Models with Language Models</a>.”</span> <em>Neural Information Processing Systems</em>.
</div>
<div id="ref-Garrabrant2020Logical" class="csl-entry">
Garrabrant, Benson-Tilsen, Critch, et al. 2020. <span>“<a href="https://doi.org/10.48550/arXiv.1609.03543">Logical Induction</a>.”</span>
</div>
<div id="ref-Gmytrasiewicz2005Framework" class="csl-entry">
Gmytrasiewicz, and Doshi. 2005. <span>“<a href="https://doi.org/10.1613/jair.1579">A Framework for Sequential Planning in Multi-Agent Settings</a>.”</span> <em>Journal of Artificial Intelligence Research</em>.
</div>
<div id="ref-GodfreySmith2016Othera" class="csl-entry">
Godfrey-Smith. 2016. <em>Other Minds</em>.
</div>
<div id="ref-Goyal2021Coordination" class="csl-entry">
Goyal, Didolkar, Lamb, et al. 2021. <span>“<a href="https://arxiv.org/abs/2103.01197">Coordination Among Neural Modules Through a Shared Global Workspace</a>.”</span> <em>International Conference on Learning Representations</em>.
</div>
<div id="ref-Graziano2013Consciousness" class="csl-entry">
Graziano. 2013. <em>Consciousness and the Social Brain</em>.
</div>
<div id="ref-Graziano2019Standard" class="csl-entry">
Graziano, Guterstam, Bio, et al. 2019. <span>“<a href="https://doi.org/10.1080/02643294.2019.1670630">Toward a Standard Model of Consciousness: Reconciling the Attention Schema, Global Workspace, Higher-Order Thought, and Illusionist Theories</a>.”</span> <em>Cognitive Neuropsychology</em>.
</div>
<div id="ref-Hafner2024Mastering" class="csl-entry">
Hafner, Pasukonis, Ba, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2301.04104">Mastering Diverse Domains Through World Models</a>.”</span>
</div>
<div id="ref-Hammond2023Reasoning" class="csl-entry">
Hammond, Fox, Everitt, et al. 2023. <span>“<a href="https://doi.org/10.1016/j.artint.2023.103919">Reasoning about Causality in Games</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Han2019IPOMDPNet" class="csl-entry">
Han, and Gmytrasiewicz. 2019. <span>“<a href="https://doi.org/10.1609/AAAI.V33I01.33016062">IPOMDP-Net: A Deep Neural Network for Partially Observable Multi-Agent Planning Using Interactive POMDPs</a>.”</span> In <em>AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Ha2018World" class="csl-entry">
Ha, and Schmidhuber. 2018. <span>“<a href="https://doi.org/10.5281/zenodo.1207631">World Models</a>.”</span> <em>arXiv.org</em>.
</div>
<div id="ref-Hofstadter2008Am" class="csl-entry">
Hofstadter. 2008. <em>I Am a Strange Loop</em>.
</div>
<div id="ref-Hutter2005Universal" class="csl-entry">
Hutter. 2005. <em><a href="https://doi.org/10.1007/b138233">Universal Artificial Intelligence: Sequential Decisions Based on Algorithmic Probability</a></em>. Texts in Theoretical Computer Science.
</div>
<div id="ref-Jacobs1991Adaptive" class="csl-entry">
Jacobs, Jordan, Nowlan, et al. 1991. <span>“<a href="https://doi.org/10.1162/neco.1991.3.1.79">Adaptive Mixtures of Local Experts</a>.”</span> <em>Neural Computation</em>.
</div>
<div id="ref-JaraEttinger2019Theory" class="csl-entry">
Jara-Ettinger. 2019. <span>“<a href="https://doi.org/10.1016/j.cobeha.2019.04.010">Theory of Mind as Inverse Reinforcement Learning</a>.”</span> <em>Current Opinion in Behavioral Sciences</em>.
</div>
<div id="ref-Jaynes1976Origin" class="csl-entry">
Jaynes. 1976. <em>The Origin of Consciousness in the Breakdown of the Bicameral Mind</em>.
</div>
<div id="ref-Kosinski2023Theory" class="csl-entry">
Kosinski. 2023. <span>“<a href="http://arxiv.org/abs/2302.02083">Theory of Mind May Have Spontaneously Emerged in Large Language Models</a>.”</span> <em>Research Papers</em>, Research Papers,.
</div>
<div id="ref-Kosinski2024Evaluating" class="csl-entry">
———. 2024. <span>“<a href="https://doi.org/10.1073/pnas.2405460121">Evaluating Large Language Models in Theory of Mind Tasks</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Kosoy2020Infra" class="csl-entry">
Kosoy, and Appel. 2020. <span>“<a href="https://www.alignmentforum.org/s/CmrW8fCmSLK7E25sa">Infra-Bayesianism Sequence</a>.”</span>
</div>
<div id="ref-Kosoy2021InfraBayesian" class="csl-entry">
Kosoy, and Appel. 2021. <span>“<a href="https://www.alignmentforum.org/posts/gHgs2e2J5azvGFatb/infra-bayesian-physicalism-a-formal-theory-of-naturalized">Infra-Bayesian Physicalism: A Formal Theory of Naturalized Induction</a>.”</span>
</div>
<div id="ref-Kwiatkowski2019Taskagnostic" class="csl-entry">
Kwiatkowski, and Lipson. 2019. <span>“<a href="https://doi.org/10.1126/scirobotics.aau9354">Task-Agnostic Self-Modeling Machines</a>.”</span> <em>Science Robotics</em>.
</div>
<div id="ref-Lai2021Policy" class="csl-entry">
Lai, and Gershman. 2021. <span>“<a href="https://doi.org/10.1016/bs.plm.2021.02.004">Policy Compression: An Information Bottleneck in Action Selection</a>.”</span> In <em>Psychology of Learning and Motivation</em>.
</div>
<div id="ref-Latek2009Bounded" class="csl-entry">
Latek, Axtell, and Kaminski. 2009. <span>“Bounded Rationality via Recursion.”</span> In.
</div>
<div id="ref-Leike2016Thompson" class="csl-entry">
Leike, Lattimore, Orseau, et al. 2016. <span>“<a href="http://arxiv.org/abs/1602.07905">Thompson Sampling Is Asymptotically Optimal in General Environments</a>.”</span> In <em>Conference on Uncertainty in Artificial Intelligence</em>.
</div>
<div id="ref-Lieder2020Resourcerational" class="csl-entry">
Lieder, and Griffiths. 2020. <span>“<a href="https://doi.org/10.1017/S0140525X1900061X">Resource-Rational Analysis: Understanding Human Cognition as the Optimal Use of Limited Computational Resources</a>.”</span> <em>Behavioral and Brain Sciences</em>.
</div>
<div id="ref-Lu2022ModelFree" class="csl-entry">
Lu, Willi, Witt, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2205.01447">Model-Free Opponent Shaping</a>.”</span> In <em>Proceedings of the 39th International Conference on Machine Learning</em>.
</div>
<div id="ref-MacDermott2023Characterising" class="csl-entry">
MacDermott, Everitt, and Belardinelli. 2023. <span>“<a href="http://arxiv.org/abs/2307.10987">Characterising Decision Theories with Mechanised Causal Graphs</a>.”</span>
</div>
<div id="ref-Mao2024Review" class="csl-entry">
Mao, Liu, Ni, et al. 2024. <span>“<a href="https://doi.org/10.1109/TCSS.2024.3416707">A Review on Machine Theory of Mind</a>.”</span> <em>IEEE Transactions on Computational Social Systems</em>.
</div>
<div id="ref-Margeloiu2021Do" class="csl-entry">
Margeloiu, Ashman, Bhatt, et al. 2021. <span>“<a href="http://arxiv.org/abs/2105.04289">Do Concept Bottleneck Models Learn as Intended?</a>”</span> In <em>ICLR 2021 Workshop on Responsible AI</em>.
</div>
<div id="ref-McKelvey1995Quantal" class="csl-entry">
McKelvey, and Palfrey. 1995. <span>“<a href="https://doi.org/10.1006/game.1995.1023">Quantal Response Equilibria for Normal Form Games</a>.”</span> <em>Games and Economic Behavior</em>.
</div>
<div id="ref-Metzinger2003Being" class="csl-entry">
Metzinger. 2003. <em><a href="https://doi.org/10.7551/mitpress/1551.001.0001">Being No One: The Self-Model Theory of Subjectivity</a></em>.
</div>
<div id="ref-Minsky1986Society" class="csl-entry">
Minsky. 1986. <em>The Society of Mind</em>.
</div>
<div id="ref-Oguntola2023Theory" class="csl-entry">
Oguntola, Campbell, Stepputtis, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2307.01158">Theory of Mind as Intrinsic Motivation for Multi-Agent Reinforcement Learning</a>.”</span> <em>arXiv.org</em>.
</div>
<div id="ref-Oguntola2021Deep" class="csl-entry">
Oguntola, Hughes, and Sycara. 2021. <span>“<a href="https://doi.org/10.1109/RO-MAN50785.2021.9515505">Deep Interpretable Models of Theory of Mind</a>.”</span> In <em>IEEE International Symposium on Robot and Human Interactive Communication</em>.
</div>
<div id="ref-Parr2022Active" class="csl-entry">
Parr, Pezzulo, and Friston. 2022. <em>Active Inference: The Free Energy Principle in Mind, Brain, and Behavior</em>.
</div>
<div id="ref-Premakumar2024Unexpected" class="csl-entry">
Premakumar, Vaiana, Pop, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2407.10188">Unexpected Benefits of Self-Modeling in Neural Systems</a>.”</span>
</div>
<div id="ref-Rabinowitz2018Machine" class="csl-entry">
Rabinowitz, Perbet, Song, et al. 2018. <span>“Machine Theory of Mind.”</span> In <em>International Conference on Machine Learning</em>.
</div>
<div id="ref-Raileanu2018Modeling" class="csl-entry">
Raileanu, Denton, Szlam, et al. 2018. <span>“<a href="https://proceedings.mlr.press/v80/raileanu18a.html">Modeling Others Using Oneself in Multi-Agent Reinforcement Learning</a>.”</span> In <em>Proceedings of the 35th International Conference on Machine Learning</em>.
</div>
<div id="ref-Rao1999Predictive" class="csl-entry">
Rao, and Ballard. 1999. <span>“<a href="https://doi.org/10.1038/4580">Predictive Coding in the Visual Cortex: A Functional Interpretation of Some Extra-Classical Receptive-Field Effects</a>.”</span> <em>Nature Neuroscience</em>.
</div>
<div id="ref-Russell2016Rationality" class="csl-entry">
Russell, Stuart. 2016. <span>“<a href="https://doi.org/10.1007/978-3-319-26485-1_2">Rationality and Intelligence: A Brief Update</a>.”</span> In.
</div>
<div id="ref-Russell1995Provably" class="csl-entry">
Russell, S. J., and Subramanian. 1995. <span>“<a href="https://doi.org/10.1613/jair.133">Provably Bounded-Optimal Agents</a>.”</span> <em>Journal of Artificial Intelligence Research</em>.
</div>
<div id="ref-Schmidhuber1993SelfReferential" class="csl-entry">
Schmidhuber. 1993. <span>“<a href="https://doi.org/10.1007/978-1-4471-2063-6_107">A ‘Self-Referential’ Weight Matrix</a>.”</span> In.
</div>
<div id="ref-Schmidhuber2003Godel" class="csl-entry">
———. 2003. <span>“<a href="https://arxiv.org/abs/cs/0309048">Gödel Machines: Self-Referential Universal Problem Solvers Making Provably Optimal Self-Improvements</a>.”</span> <em>arXiv.org</em>.
</div>
<div id="ref-Schrittwieser2020Mastering" class="csl-entry">
Schrittwieser, Antonoglou, Hubert, et al. 2020. <span>“<a href="https://doi.org/10.1038/s41586-020-03051-4">Mastering Atari, Go, Chess and Shogi by Planning with a Learned Model</a>.”</span> <em>Nature</em>.
</div>
<div id="ref-Sclar2023Minding" class="csl-entry">
Sclar, Kumar, West, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2306.00924">Minding Language Models’ (Lack of) Theory of Mind: A Plug-and-Play Multi-Character Belief Tracker</a>.”</span> In <em>Annual Meeting of the Association for Computational Linguistics</em>.
</div>
<div id="ref-Shalizi2000Computational" class="csl-entry">
Shalizi, and Crutchfield. 2000. <span>“<a href="https://doi.org/10.1023/A:1010388907793">Computational Mechanics: Pattern and Prediction, Structure and Simplicity</a>.”</span> <em>Journal of Statistical Physics</em>.
</div>
<div id="ref-Shazeer2017Outrageously" class="csl-entry">
Shazeer, Mirhoseini, Maziarz, et al. 2017. <span>“<a href="https://arxiv.org/abs/1701.06538v1">Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer</a>.”</span>
</div>
<div id="ref-Shinn2023Reflexion" class="csl-entry">
Shinn, Cassano, Berman, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2303.11366">Reflexion: Language Agents with Verbal Reinforcement Learning</a>.”</span> In <em>Advances in Neural Information Processing Systems 36 (NeurIPS 2023)</em>.
</div>
<div id="ref-Simon1955Behavioral" class="csl-entry">
Simon. 1955. <span>“<a href="https://doi.org/10.2307/1884852">A Behavioral Model of Rational Choice</a>.”</span> <em>The Quarterly Journal of Economics</em>.
</div>
<div id="ref-Sims2012Ideal" class="csl-entry">
Sims, Jacobs, and Knill. 2012. <span>“<a href="https://doi.org/10.1037/a0029856">An Ideal Observer Analysis of Visual Working Memory</a>.”</span> <em>Psychological Review</em>.
</div>
<div id="ref-Stolk2016Conceptual" class="csl-entry">
Stolk, Verhagen, and Toni. 2016. <span>“<a href="https://doi.org/10.1016/j.tics.2015.11.007">Conceptual Alignment: How Brains Achieve Mutual Understanding</a>.”</span> <em>Trends in Cognitive Sciences</em>.
</div>
<div id="ref-Tishby2000Information" class="csl-entry">
Tishby, Pereira, and Bialek. 2000. <span>“<a href="http://arxiv.org/abs/physics/0004057">The Information Bottleneck Method</a>.”</span>
</div>
<div id="ref-Ullman2023Large" class="csl-entry">
Ullman. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2302.08399">Large Language Models Fail on Trivial Alterations to Theory-of-Mind Tasks</a>.”</span>
</div>
<div id="ref-VanRullen2021Deep" class="csl-entry">
VanRullen, and Kanai. 2021. <span>“<a href="https://doi.org/10.1016/j.tins.2021.04.005">Deep Learning and the Global Workspace Theory</a>.”</span> <em>Trends in Neurosciences</em>.
</div>
<div id="ref-Virgo2025Good" class="csl-entry">
Virgo, Biehl, Baltieri, et al. 2025. <span>“<a href="https://doi.org/10.1162/ISAL.a.874">A “Good Regulator Theorem” for Embodied Agents</a>.”</span> In.
</div>
<div id="ref-Willi2022COLA" class="csl-entry">
Willi, Letcher, Treutlein, et al. 2022. <span>“<a href="http://arxiv.org/abs/2203.04098">COLA: Consistent Learning with Opponent-Learning Awareness</a>.”</span> In <em>Proceedings of the 39th International Conference on Machine Learning</em>.
</div>
<div id="ref-Wilterson2021Attention" class="csl-entry">
Wilterson, and Graziano. 2021. <span>“<a href="https://doi.org/10.1073/pnas.2102421118">The Attention Schema Theory in a Neural Network Agent: Controlling Visuospatial Attention Using a Descriptive Model of Attention</a>.”</span> <em>Proceedings of the National Academy of Sciences</em>.
</div>
<div id="ref-Yudkowsky2013Tiling" class="csl-entry">
Yudkowsky, and Herreshoff. 2013. <span>“<a href="https://intelligence.org/files/TilingAgentsDraft.pdf">Tiling Agents for Self-Modifying AI, and the Löbian Obstacle</a>.”</span> <em>Early Draft MIRI</em>.
</div>
<div id="ref-Zenon2019Informationtheoretic" class="csl-entry">
Zénon, Solopchuk, and Pezzulo. 2019. <span>“<a href="https://doi.org/10.1016/j.neuropsychologia.2018.09.013">An Information-Theoretic Perspective on the Costs of Cognition</a>.”</span> <em>Neuropsychologia</em>, Cognitive Effort,.
</div>
<div id="ref-Zhao2022Proximal" class="csl-entry">
Zhao, Lu, Grosse, et al. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2210.10125">Proximal Learning With Opponent-Learning Awareness</a>.”</span> <em>Neural Information Processing Systems</em>.
</div>
<div id="ref-Zhou2023Language" class="csl-entry">
Zhou, Yan, Shlapentokh-Rothman, et al. 2024. <span>“<a href="http://arxiv.org/abs/2310.04406">Language Agent Tree Search Unifies Reasoning, Acting, and Planning in Language Models</a>.”</span> In <em>Proceedings of the 41st International Conference on Machine Learning (ICML 2024)</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Alternatively it could be a full-rank model, which gets very weird and makes people <a href="../notebook/agency_embedded.html">worry about</a> <a href="https://en.wikipedia.org/wiki/L%C3%B6b%27s_theorem?ref=danmackinlay.name">Löb’s theorem</a>. That is not the main focus here↩︎</p></li>
<li id="fn2"><p>Selfhood comes in sizes on this account, measured in <em>hunekers</em>, after the music critic who warned that small-souled men should not attempt a particular Chopin étude.↩︎</p></li>
<li id="fn3"><p>An observer-relative formulation of what it means for Bob to have a model at all is in <span class="citation" data-cites="Virgo2025Good">Virgo et al. (2025)</span>, sitting alongside the internal-model-principle thread at <a href="../notebook/internal_models.html">internal models</a>. Their construction gives <em>a</em> floor on what counts as modelling — one among several plausible candidates, with differing divergence and ontology commitments.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>agent foundations</category>
  <category>bounded compute</category>
  <category>cooperation</category>
  <category>learning</category>
  <category>mind</category>
  <category>neural nets</category>
  <category>probability</category>
  <category>quagmire</category>
  <category>reinforcement learning</category>
  <category>theory of mind</category>
  <category>when to compute</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/homunculi.html</guid>
  <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/vanmuydenlaboratory.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Optionality as an end in itself</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/optionality_as_end.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Attention conservation notice: In hindsight this is a somewhat low signal post. The good bits will be salvaged for other things.</p>
</div>
</div>
</div>
<div id="fig-whore-of-babylon" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-whore-of-babylon-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/whore_of_Babylon_gri_33125014730804_0032.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/whore_of_Babylon_gri_33125014730804_0032.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-whore-of-babylon-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>An intuition I took from <a href="https://longnow.org/ideas/civilizational-optionality-indy-johar-long-now/?ref=danmackinlay.name">Indy Johar’s recent Long Now essay on civilisational optionality</a><sup>1</sup> is something like the following: what we ought to be optimizing — at least at civilisational scale — is not any particular state of affairs, but the <em>space of states of affairs we could still reach in the future</em>. Not expected utility from the likely states, but the ‘raw volume’ of futures still on the table at the time we next decide.</p>
<p>Is that a coherent moral aim? What kind of object is it? How does it relate to the <a href="../notebook/diversity_as_end.html">diversity-as-end</a> intuition, the <a href="../notebook/informational_empowerment.html">empowerment</a> formalism, the <a href="../notebook/intrinsic_motivation.html">intrinsic motivation</a> literature, or the <a href="../notebook/returns_to_leviathan.html">asymptotic leviathan</a> civilisational story? I don’t know!<sup>2</sup></p>
<p>My research agent surfaced three practical formalisations, and a couple more intuitionistic variants. Let us unpack ’em.</p>
<section id="formal-versions" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="formal-versions"><span class="header-section-number">1</span> Formal versions</h2>
<p>By “formal” here I mean “we can compute this in principle, and someone has for at least one case”.</p>
<section id="informational-empowerment" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="informational-empowerment"><span class="header-section-number">1.1</span> (Informational) Empowerment</h3>
<p><a href="../notebook/informational_empowerment.html">(Informational) Empowerment</a> <span class="citation" data-cites="Klyubin2005Empowerment">(A. S. Klyubin, Polani, and Nehaniv 2005)</span> is clean and simple-ish, under certain assumptions, e.g.&nbsp;in a stationary world, known up to aleatoric dynamics. The target is the channel capacity between an agent’s actions and its future sensory states, <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7BE%7D(s)%20=%20%5Cmax_%7Bp(a)%7D%20I(A;%5C,%20S')">. An empowerment-maximizing agent favours states from which many futures are reachable, and avoids states from which few are. That is a drive to keep options open, where options = entropy.</p>
<p>Caveat: Empowerment is defined over a fixed action space, a fixed state space, and a fixed (or at least learnable) transition kernel. In an open or non-stationary world — where the action space itself can grow, new states emerge, or the dynamics drift — the channel capacity <img src="https://latex.codecogs.com/png.latex?I(A;%20S')"> ceases to be a well-posed quantity. <a href="../notebook/information_estimating.html">Calculating it for any non-trivial horizon is rather punishing even in the closed, stationary case</a>; in the open world, I don’t know what it would even mean.</p>
<section id="causal-entropic-forces" class="level4" data-number="1.1.1">
<h4 data-number="1.1.1" class="anchored" data-anchor-id="causal-entropic-forces"><span class="header-section-number">1.1.1</span> Causal entropic forces</h4>
<p><span class="citation" data-cites="WissnerGross2013Causal">Wissner-Gross and Freer (2013)</span> is an alternative but similar version that estimates a somewhat different objective (maximise entropy of my future path entropy conditional upon a single next action and thereafter following the dynamics), but it is in the same spirit: a drive to keep options open.</p>
<ul>
<li><a href="https://www.newyorker.com/tech/annals-of-technology/a-grand-unified-theory-of-everything?ref=danmackinlay.name">A Grand Unified Theory of Everything</a></li>
<li><a href="https://github.com/dyth/causal-entropic-forces?ref=danmackinlay.name">dyth/causal-entropic-forces</a>: Python reimplementation of experiment 1 from the paper.</li>
</ul>
</section>
</section>
<section id="ergodicity-economics" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="ergodicity-economics"><span class="header-section-number">1.2</span> Ergodicity economics</h3>
<p><span class="citation" data-cites="Peters2019Ergodicity">Ole Peters (2019)</span> arrives at a similar place from a different direction; the worked coin-flip example and my reservations about the programme now live at <a href="../notebook/ergodicity_economics.html">ergodicity economics</a>. The part that matters here: under multiplicative dynamics the ergodic observable is the time-average growth rate, which is an expected log, and maximizing it over stake sizes is the Kelly criterion, which disfavours any action that risks ejecting the agent from the support of viable trajectories. Optionality-flavoured behaviour falls out of this without anyone having to add “preserve options” as a separate goal.</p>
</section>
<section id="quality-diversity-algorithms" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="quality-diversity-algorithms"><span class="header-section-number">1.3</span> Quality-diversity algorithms</h3>
<p>Quality-diversity algorithms — MAP-Elites <span class="citation" data-cites="Cully2015Robots">(Cully et al. 2015)</span>, novelty search <span class="citation" data-cites="Lehman2011Abandoning">(Lehman and Stanley 2011)</span> — return an <em>archive</em> of qualitatively different behaviours rather than a single champion; the broken-legged hexapod consults its archive of gaits until one works with five legs, so the archive is insurance against environment shifts the training objective never anticipated. But the maximand is a static diversity measure over <em>currently-realized</em> behaviours, evaluated at a snapshot: strictly this is <a href="../notebook/diversity_as_end.html"><em>diversity</em>-as-end</a>. I briefly auditioned it for this notebook because it has the exciting feature of being easy to calculate — because it is not <em>actually</em> about future options.</p>
</section>
<section id="what-did-that-get-us" class="level3" data-number="1.4">
<h3 data-number="1.4" class="anchored" data-anchor-id="what-did-that-get-us"><span class="header-section-number">1.4</span> What did that get us?</h3>
<p>Hmm, did we sketch out a conceptual space there? I feel like we just sampled some cool ideas and got nowhere.</p>
<ul>
<li>Empowerment maximizes mutual information from actions to futures, at least in the agent’s own model of the world, which it somehow knows.</li>
<li>Ergodicity economics minimizes the probability of leaving the support of viable trajectories, by re-averaging a single time series the “right” way.</li>
<li>Quality-diversity maximizes entropy over an archive of currently-realized behaviours, treating diversity as a hedge against an <em>unknown</em> future fitness function.</li>
</ul>
<p>All three put an entropy-like quantity in the place where standard utility would have put a single-target loss. They differ on what the entropy is <em>over</em> — futures, trajectories, or current behaviours.</p>
<p>I’m skeptical we can even “solve for optionality”, at least in any meaningful sense, in a world where the action space itself can grow, new states emerge, and the dynamics drift.</p>
<p>The problem of calculating optionality is not just <em>difficult</em> in an open world; it seems to be ill-defined, or if well-defined then intractable. So any claim that we should be optimizing for optionality will naïvely cash out as a claim that we should be optimizing for some <em>proxy</em> for optionality, which is just another objective, innit? Is anything especially good about such proxies compared to the default?</p>
<p>Hell, isn’t the desire for wealth already a proxy for optionality, in that it is a hedge against (some large class of) unknown futures?</p>
</section>
</section>
<section id="intuitive-versions" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="intuitive-versions"><span class="header-section-number">2</span> Intuitive versions</h2>
<p>God help you if you want to compute <em>these</em> bad boys.</p>
<section id="moral-uncertainty" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="moral-uncertainty"><span class="header-section-number">2.1</span> Moral uncertainty</h3>
<p>If we do not know what is good, we should not lock in any one answer. This is one motivation for Bostrom’s <em>long reflection</em> <span class="citation" data-cites="Bostrom2014Superintelligence">(Bostrom 2014)</span>, and behind milder claims that we should not race to build single-objective superintelligences before we have finished arguing about what their objective should be (and maybe, how to regularize it?). There is a formal moral-uncertainty framework <span class="citation" data-cites="MacAskill2020Moral">(MacAskill, Bykvist, and Ord 2020)</span> for how to act when uncertain across moral theories; explicit optionality is one possible response to that uncertainty rather than the only one. Optionality here is the meta-property that increases our chances that <em>if</em> we ever figure out what the real objective is, we are still in a position to act on it.</p>
<p>This one is very popular amongst those already committed to the potential to bring about <a href="../notebook/superintelligence.html">superintelligences</a> which might need to act using a <a href="../notebook/utility_if_we_must.html">specific utility</a> that we have not yet figured out.</p>
</section>
<section id="antifragility" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="antifragility"><span class="header-section-number">2.2</span> Antifragility</h3>
<p>Taleb’s <em>antifragility</em> <span class="citation" data-cites="Taleb2013Antifragile">(Taleb 2013)</span> argument is that some systems gain from disorder, where exposure to small shocks improves long-run resilience. Scott Alexander wrote <a href="https://astralcodexten.substack.com/p/more-antifragile-diversity-libertarianism?ref=danmackinlay.name">about diversity, libertarianism, and corporate censorship along these lines</a>. Antifragility is not quite the same as optionality — antifragile systems <em>benefit</em> from volatility, whereas option-preserving systems merely refuse to foreclose — but they share an aversion to lock-in and a fondness for redundancy. Also Taleb can spin a yarn and coin a sticky metaphor, so this one is here to stay, even if it’s hard to pin down anything helpfully formal.</p>
</section>
<section id="diversity" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="diversity"><span class="header-section-number">2.3</span> Diversity</h3>
<p>Plain old vanilla diversity is closely adjacent but not identical. <a href="../notebook/diversity_as_end.html">Diversity-as-end</a> is about the configuration space of possible humans, possible cultures, possible intelligences <em>now</em>. Optionality is about the configuration space of possible futures <em>from now</em>. Diversity is a static observable; optionality is a forward operator on diversity — how much of it will still be available in <img src="https://latex.codecogs.com/png.latex?T"> steps?</p>
<p>These could come apart in cases like a homogeneous society that nonetheless preserves the option to diversify, or a maximally diverse society that has, through some narrowing of common infrastructure or language, foreclosed the ability to recombine. I’m skeptical that the former is realizable (Maybe Tokugawa Japan under the sakoku policy?); the latter is roughly what some critiques of platform monocultures claim is happening to us.</p>
</section>
</section>
<section id="why-might-optionality-be-a-moral-good" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="why-might-optionality-be-a-moral-good"><span class="header-section-number">3</span> Why might optionality be a moral good?</h2>
<p>A few candidate arguments, in increasing order of how much weight they bear:</p>
<ol type="1">
<li><em>Instrumental.</em> Optionality is a low-regret proxy for whatever the good turns out to be. If we cannot identify the target, preserving the ability to aim is the next best thing. Optionality lets us defer the hard moral philosophy to some poor future bastard.</li>
<li><em>Aggregative.</em> There are many possible goods, they are not commensurable, and we cannot pick one. The union of futures realizing different goods is therefore better than any single future, in something like the Dixit-Pindyck sense of option value <span class="citation" data-cites="Dixit1994Investment">(Dixit and Pindyck 1994)</span>, applied to ethics rather than capital budgeting. This crops up in environmental economics of natural resources: the preservation value of an ecosystem includes the option value of being able to use it later under future preferences and information we do not yet have <span class="citation" data-cites="Weisbrod1964Collective Krutilla1967Conservation Arrow1974Environmental">(Weisbrod 1964; Krutilla 1967; Arrow and Fisher 1974)</span>.</li>
<li><em>Constitutive.</em> The open-endedness of possibility is itself the thing we value. A frozen optimum, even an optimal one, is dead in some intuitively important sense. Is not being <a href="../notebook/open_ended_intelligence.html">open-ended</a> somehow a “good”? Seems important.</li>
</ol>
</section>
<section id="compared-with-utilitarianism" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="compared-with-utilitarianism"><span class="header-section-number">4</span> Compared with utilitarianism</h2>
<p>Let us suppose I have committed to optionality as a moral good. How does this distinguish my position from utilitarianism? Both are <em>consequentialist</em> ethics — actions are evaluated by their downstream effects on the world. However, we disagree about which downstream effects matter. Deontological and virtue-ethics objections to consequentialism apply equally to both ofc.</p>
<p>Thoughts:</p>
<p>First, as noted above, optionality can be made to look like a particular utility function, or a regularizer on a utility function. Define <img src="https://latex.codecogs.com/png.latex?U(s)%20=%20%5Clog%20%7C%5Ctext%7Breachable%20futures%20from%20%7D%20s%7C">, or some entropy-like proxy thereof, and a maximiser of <img src="https://latex.codecogs.com/png.latex?U"> is a maximiser of optionality. So we could call this just a special utilitarianism, with a specific if weird utility function. The empowerment literature does flag this — empowerment is sometimes called a <em>pseudo-utility</em>, with some nice properties wrt generalizing to changing or under-specified objectives.</p>
<p>So, it is utility with a structural commitment to how it aggregates over futures: diminishing returns for piling up additional futures and an increasing penalty for foreclosing any as we run low on futures. That seems … fine? Consider some alternative aggregations: maximin (no improvement elsewhere compensates any worsening of the worst case), Kelly criterion (maximise the expected log of wealth), and expected utility (maximise the expected value of wealth). Kelly/log implies smooth substitution everywhere except at the ruin boundary, at which point it gets arbitrarily aversive (we try really hard not to die). These objectives are all on the spectrum of generalized means: linear utilitarianism aggregates futures by the arithmetic mean (<img src="https://latex.codecogs.com/png.latex?p=1">, perfect substitutability), maximin is the <img src="https://latex.codecogs.com/png.latex?p%5Cto-%5Cinfty"> limit (none), and log/Kelly/optionality utilities sit at the geometric mean (<img src="https://latex.codecogs.com/png.latex?p%5Cto%200">), implying partial substitutability, hardening to refusal at the boundary.</p>
<p>Second: aggregation across agents. Utilitarianism’s (default) aggregation between agents is linear: <img src="https://latex.codecogs.com/png.latex?U_%7B%5Ctext%7Btotal%7D%7D%20=%20%5Csum_i%20u_i">. The agents are commensurable; my utility trades for yours one-for-one. Information-theoretic optionality does not aggregate that way, and two toy channels show why not. Synergy: suppose the future is the XOR of two agents’ actions, <img src="https://latex.codecogs.com/png.latex?S'%20=%20A_1%20%5Coplus%20A_2">. Alone, each of us is powerless — whatever I do, the future remains a fair coin — but together we determine it completely. Redundancy: suppose agent 2 merely echoes agent 1. Then each of us looks fully empowered on our own, yet jointly we control no more than either did alone. So in general <img src="https://latex.codecogs.com/png.latex?I(A_1,%20%5Cldots,%20A_n;%5C,%20S')%20%5Cneq%20%5Csum_i%20I(A_i;%5C,%20S')">, and which side is bigger is a fact about the world, not a moral choice. Worse, the summands on the right are not even well-defined: my empowerment <img src="https://latex.codecogs.com/png.latex?I(A_i;%5C,%20S')"> depends on what everyone else is doing, because the other agents are part of my environment — in the XOR world I am powerless against a random partner and fully empowered against a predictable one. There is no canonical “we” to aggregate over without first fixing a convention for everyone’s behaviour; the parts do not exist prior to the whole. We <em>can</em> force linear aggregation by fiat — fix such a convention, assign each agent a scalar empowerment under it, and add them up — but at that point we are doing utilitarianism with a particular intermediate quantity, not optionality at the collective level. Whether the natural sub- or super-additivity of joint information is closer to the moral truth than utilitarianism’s linearity is not at all clear to me. Has someone written about this? Surely someone has written about this.</p>
<p>Partially: <em>partial information decomposition</em> <span class="citation" data-cites="Williams2010Nonnegative">(Williams and Beer 2010)</span> is the formalism for splitting joint information into synergistic and redundant shares, and <em>coupled empowerment maximization</em> <span class="citation" data-cites="Guckelsberger2016Intrinsically">(Guckelsberger, Salge, and Colton 2016)</span> deploys multi-agent empowerment to steer companion NPCs in games, though neither of these is exactly concerned with ethics. The general problem of attributing a non-additive joint quantity linearly to its contributors is the Shapley value <span class="citation" data-cites="Shapley1953Value">(Shapley 1953)</span>; treating collective empowerment as a cooperative game in that sense looks like a paper someone should have written, but I have not found it.</p>
<p>Anyway, as such optionality evades some of utilitarianism’s classical headaches. It does not produce a Parfit-style repugnant conclusion: it has no built-in preference for vast populations of barely-distinct futures over small populations of richly-distinct ones. It does not endorse extinction by negative-utilitarian logic, because extinction has measure zero in reachable-futures space.</p>
<p>It also might dodge the standard form of Pascal’s mugging — the longtermist worry that tiny probabilities of astronomically large future utilities should dominate present moral reasoning. Pascal’s mugging gets its leverage from the multiplicative structure of expected utility (<img src="https://latex.codecogs.com/png.latex?EV%20=%20P%20%5Ctimes%20U">, with <img src="https://latex.codecogs.com/png.latex?U"> unbounded above). Optionality measures are typically bounded — channel capacity by <img src="https://latex.codecogs.com/png.latex?%5Clog%20%7CS'%7C">, archive entropy by archive size — and the ergodicity-economics move explicitly rejects ensemble-average reasoning that lets tail outcomes dominate. Tiny-probability astronomical futures simply do not get the same purchase on the maximand. <span class="citation" data-cites="Tarsney2025Expected">Tarsney (2025)</span> makes a parallel move within utilitarianism itself, capping expected-value reasoning under sufficiently high background uncertainty; the structural worry is similar but the bound is imposed as a side constraint rather than built into the framework.</p>
</section>
<section id="optionality-catastrophes" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="optionality-catastrophes"><span class="header-section-number">5</span> Optionality catastrophes</h2>
<p>Empowerment, at least in some form, is one of the convergent instrumental goals that the AI-safety literature worries about — Omohundro’s <em>basic AI drives</em> <span class="citation" data-cites="Omohundro2008Basic">(Omohundro 2008)</span>, Turner et al.&nbsp;on optimal policies seeking power <span class="citation" data-cites="Turner2021Optimal">(A. Turner et al. 2021)</span>. An agent that preserves <em>its own</em> future optionality can, by the same arithmetic, be foreclosing <em>ours</em>. “Keep options open” is symmetric across agents only when there is no resource competition; there is, so it isn’t. The information-theoretic version of “live and let live” can still end up looking like imperialism if we give it enough time and compute. And even if optionalities are not linearly summable, we can still argue about the weighting.</p>
<p>Also, optionality metrics on their own only oppose dystopia-bound trajectories <em>if</em> dystopia in fact has low reachable-future-volume. That is plausibly true for some dystopias and less obviously true for others — a stable totalitarian regime with lots of internal variation might preserve a respectable amount of state-space volume, and the framework would not flag it as problematic on its own terms, even where other moral intuitions would object. Would you enjoy a future where you get to choose between a hundred different tortures over ten different interesting jobs? In this sense, optionality looks insufficient.</p>
</section>
<section id="preserving-other-agents-optionality" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="preserving-other-agents-optionality"><span class="header-section-number">6</span> Preserving other agents’ optionality</h2>
<p>The failure mode above has a complementary literature: rather than maximizing my optionality, measure and preserve <em>yours</em>. I previously had these filed under <a href="../notebook/intrinsic_motivation.html">intrinsic motivation</a>, which was a category error — they are not drives, they are preservation targets, and they are the closest thing I have found to a formalisation of what this notebook is actually asking for.</p>
<section id="relative-reachability" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="relative-reachability"><span class="header-section-number">6.1</span> Relative reachability</h3>
<p><span class="citation" data-cites="Krakovna2019Penalizing">Krakovna et al. (2019)</span> penalize side effects by <em>stepwise relative reachability</em>: an action is impactful to the extent that it reduces the set of states that would have remained reachable under an inaction baseline. This is preservation of raw future-volume — the nearest formal cousin of the civilisational-optionality intuition at the top of this page.</p>
</section>
<section id="attainable-utility-preservation" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="attainable-utility-preservation"><span class="header-section-number">6.2</span> Attainable utility preservation</h3>
<p><span class="citation" data-cites="Turner2020Conservative">A. M. Turner, Hadfield-Menell, and Tadepalli (2020)</span> penalize change in attainable utility across a broad set of auxiliary reward functions: keep the world in a position from which many different goals could still be pursued. This is option value over <em>goals</em> rather than over states, which quietly handles the objection that most state distinctions do not matter.</p>
</section>
<section id="iccea-power" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="iccea-power"><span class="header-section-number">6.3</span> ICCEA power</h3>
<p><span class="citation" data-cites="Heitzig2025ModelBased">Heitzig and Potham (2025)</span> go for informationally and cognitively constrained effective autonomous (ICCEA) power — “essentially how many goals a human can freely choose to reach with more or less certainty, given their information, cognitive capabilities, and others’ behavior.”</p>
<blockquote class="blockquote">
<p>It is built in three steps: defining what it means to achieve a goal, adjusting for human bounded rationality, and aggregating the achievement ability across all possible goals into one number. Rather than trying to capture the full range of subtle aspects of existing notions of human power, we focus on those aspects we believe a robot can robustly infer from the structure of its world model, encoded in state and action sets, transition kernel, and observation functions. Since we want to incentivize the robot to remove constraints and uncertainties, share information, make commitments, and improve human cognition, coordination, and cooperation, our power metric will also depend on <img src="https://latex.codecogs.com/png.latex?r">’s model of human decision making.</p>
</blockquote>
</section>
<section id="the-controllability-reading" class="level3" data-number="6.4">
<h3 data-number="6.4" class="anchored" data-anchor-id="the-controllability-reading"><span class="header-section-number">6.4</span> The controllability reading</h3>
<p>What unifies the three: each preserves the <em>channel capacity of a controller</em>. Reachability preserves which futures remain steerable-to; attainable utility preserves which goals remain pursuable; ICCEA power measures both for a bounded human. I think this is the right reading of optionality in general: not entropy over futures, but preserved controllability. The reason “do not kill anyone” comes out as an optionality claim is not that a surviving person makes the world more entropic — thermodynamically it is rather the opposite — but that death destroys a controller: alive is a state from which many futures remain reachable <em>by that person</em>, and dead sends that channel capacity to zero, permanently. Two loose ends from earlier tighten under this reading. The stable-dystopia objection above is unanswerable if optionality is raw future-volume — a hundred selectable tortures is a respectable volume — but immediate if optionality is controllability: the tortured retain no channel capacity over which future they get. And the aside about wealth already being an optionality proxy sharpens to: wealth is generic actuation capacity, empowerment denominated in dollars, which is exactly why it functions as a hedge against unspecified futures. One further distinction the impact literature needs and raw empowerment does not supply: capacity versus use. The considerate agent should retain large capacity over its neighbours and be paid to leave it unexercised; penalizing the capacity itself rewards self-blinding and self-crippling. Directed information — influence actually exerted — is the natural price on use, while the preservation terms above guard capacity. What no functional in this family decides is which systems count as controllers whose capacity matters. That is the value judgment we do not get to outsource.</p>
</section>
</section>
<section id="incoming" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="incoming"><span class="header-section-number">7</span> Incoming</h2>
<ul>
<li>Multi-agent optionality aggregation and its weirdness: has this been exploited by actual moral philosophers? The technical literature is thin and the moral literature seems unaware that the technical literature exists.</li>
<li>Robust Decision Making <span class="citation" data-cites="Lempert2003Shaping">(Lempert, Popper, and Bankes 2003)</span> in policy analysis under deep uncertainty operationalizes optionality preservation in policy. There is presumably useful cross-pollination between RDM and the formalisations above that I haven’t explored.</li>
<li>Ecosystem robustness and biodiversity as the biological cousin of the civilisational claim. The same maths probably applies; ecologists have been there longer.</li>
<li>Does the <a href="../notebook/ethical_consumption.html">moral worth of chickens uneaten</a> scale linearly in number of chickens? — i.e., does optionality over animal-welfare futures aggregate the same way utility supposedly does, or does it have its own pathologies of aggregation?</li>
<li>The connection back to antifragility (Taleb; ACX): is antifragility a strict superset of optionality, a strict subset, or a sibling concept? My current guess is sibling, but I haven’t done the work.</li>
<li>How do <em>individual</em> optionality (empowerment) and <em>collective</em> optionality come apart, and what the moral arithmetic looks like in those cases. I suspect this is where the substance of an “optionality ethics” resides.</li>
<li>The notion of generalized Kelly betting seems like a cool natural result to arise from optionality. I suspect it also arises in <a href="../notebook/prefigurative_politics.html">prefigurative politics</a> and endogenous growth theory.</li>
</ul>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Arrow1974Environmental" class="csl-entry">
Arrow, and Fisher. 1974. <span>“<a href="https://doi.org/10.2307/1883074">Environmental Preservation, Uncertainty, and Irreversibility</a>.”</span> <em>Quarterly Journal of Economics</em>.
</div>
<div id="ref-Bostrom2014Superintelligence" class="csl-entry">
Bostrom. 2014. <em>Superintelligence: Paths, Dangers, Strategies</em>.
</div>
<div id="ref-Chatzilygeroudis2020QualityDiversity" class="csl-entry">
Chatzilygeroudis, Cully, Vassiliades, et al. 2020. <span>“<a href="https://arxiv.org/abs/2012.04322v2">Quality-Diversity Optimization: A Novel Branch of Stochastic Optimization</a>.”</span>
</div>
<div id="ref-Cully2015Robots" class="csl-entry">
Cully, Clune, Tarapore, et al. 2015. <span>“<a href="https://doi.org/10.1038/nature14422">Robots That Can Adapt Like Animals</a>.”</span> <em>Nature</em>.
</div>
<div id="ref-Dixit1994Investment" class="csl-entry">
Dixit, and Pindyck. 1994. <em>Investment Under Uncertainty</em>.
</div>
<div id="ref-Guckelsberger2016Intrinsically" class="csl-entry">
Guckelsberger, Salge, and Colton. 2016. <span>“<a href="https://doi.org/10.1109/CIG.2016.7860406">Intrinsically Motivated General Companion NPCs via Coupled Empowerment Maximisation</a>.”</span> In <em>2016 IEEE Conference on Computational Intelligence and Games (CIG)</em>.
</div>
<div id="ref-Hansson1997PrecautionaryMaximin" class="csl-entry">
Hansson. 1997. <span>“<a href="https://doi.org/10.1023/A:1009620112655">The Limits of Precaution</a>.”</span> <em>Foundations of Science</em>.
</div>
<div id="ref-Heitzig2025ModelBased" class="csl-entry">
Heitzig, and Potham. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2508.00159">Model-Based Soft Maximization of Suitable Metrics of Long-Term Human Power</a>.”</span>
</div>
<div id="ref-Klyubin2005All" class="csl-entry">
Klyubin, Alexander S., Polani, and Nehaniv. 2005. <span>“<a href="https://doi.org/10.1007/11553090_75">All Else Being Equal Be Empowered</a>.”</span> In <em>Advances in Artificial Life</em>. Lecture Notes in Computer Science.
</div>
<div id="ref-Klyubin2005Empowerment" class="csl-entry">
Klyubin, A.S., Polani, and Nehaniv. 2005. <span>“<a href="https://doi.org/10.1109/CEC.2005.1554676">Empowerment: A Universal Agent-Centric Measure of Control</a>.”</span> In <em>2005 IEEE Congress on Evolutionary Computation</em>.
</div>
<div id="ref-Krakovna2019Penalizing" class="csl-entry">
Krakovna, Orseau, Kumar, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1806.01186">Penalizing Side Effects Using Stepwise Relative Reachability</a>.”</span>
</div>
<div id="ref-Krakovna2020Avoiding" class="csl-entry">
Krakovna, Orseau, Ngo, et al. 2020. <span>“<a href="http://arxiv.org/abs/2010.07877">Avoiding Side Effects By Considering Future Tasks</a>.”</span> In <em>Advances in Neural Information Processing Systems</em>.
</div>
<div id="ref-Krutilla1967Conservation" class="csl-entry">
Krutilla. 1967. <span>“Conservation Reconsidered.”</span> <em>American Economic Review</em>.
</div>
<div id="ref-Lehman2007Evolution" class="csl-entry">
Lehman. 2007. <span>“Evolution Through the Search for Novelty.”</span>
</div>
<div id="ref-Lehman2011Abandoning" class="csl-entry">
Lehman, and Stanley. 2011. <span>“<a href="https://doi.org/10.1162/EVCO_a_00025">Abandoning Objectives: Evolution Through the Search for Novelty Alone</a>.”</span> <em>Evolutionary Computation</em>.
</div>
<div id="ref-Lehman2013Evolvability" class="csl-entry">
———. 2013. <span>“<a href="https://doi.org/10.1371/journal.pone.0062186">Evolvability Is Inevitable: Increasing Evolvability Without the Pressure to Adapt</a>.”</span> <em>PLoS ONE</em>.
</div>
<div id="ref-Lempert2003Shaping" class="csl-entry">
Lempert, Popper, and Bankes. 2003. <em>Shaping the Next One Hundred Years: New Methods for Quantitative, Long-Term Policy Analysis</em>.
</div>
<div id="ref-MacAskill2020Moral" class="csl-entry">
MacAskill, Bykvist, and Ord. 2020. <em>Moral Uncertainty</em>.
</div>
<div id="ref-Omohundro2008Basic" class="csl-entry">
Omohundro. 2008. <span>“<a href="https://selfawaresystems.files.wordpress.com/2008/01/ai_drives_final.pdf">The Basic AI Drives</a>.”</span> In <em>Proceedings of the 2008 Conference on Artificial General Intelligence 2008: Proceedings of the First AGI Conference</em>.
</div>
<div id="ref-Ord2020Precipice" class="csl-entry">
Ord. 2020. <em>The Precipice: Existential Risk and the Future of Humanity</em>.
</div>
<div id="ref-Peters2019Ergodicity" class="csl-entry">
Peters, Ole. 2019. <span>“<a href="https://doi.org/10.1038/s41567-019-0732-0">The Ergodicity Problem in Economics</a>.”</span> <em>Nature Physics</em>.
</div>
<div id="ref-Peters2016Evaluating" class="csl-entry">
Peters, O., and Gell-Mann. 2016. <span>“<a href="https://doi.org/10.1063/1.4940236">Evaluating Gambles Using Dynamics</a>.”</span> <em>Chaos: An Interdisciplinary Journal of Nonlinear Science</em>.
</div>
<div id="ref-Shapley1953Value" class="csl-entry">
Shapley. 1953. <span>“<a href="https://doi.org/10.1017/CBO9780511528446.003">A Value for n-Person Games</a>.”</span> In <em>Contributions to the Theory of Games, Volume II</em>. Annals of Mathematics Studies.
</div>
<div id="ref-Taleb2013Antifragile" class="csl-entry">
Taleb. 2013. <em>Antifragile: Things That Gain from Disorder</em>.
</div>
<div id="ref-Tarsney2025Expected" class="csl-entry">
Tarsney. 2025. <span>“<a href="https://doi.org/10.1111/nous.12544">Expected Value, to a Point: Moral Decision-Making Under Background Uncertainty</a>.”</span> <em>Noûs</em>.
</div>
<div id="ref-Turner2020Conservative" class="csl-entry">
Turner, Alexander Matt, Hadfield-Menell, and Tadepalli. 2020. <span>“<a href="https://doi.org/10.1145/3375627.3375851">Conservative Agency via Attainable Utility Preservation</a>.”</span> In <em>Proceedings of the AAAI/ACM Conference on AI, Ethics, and Society</em>. AIES ’20.
</div>
<div id="ref-Turner2021Optimal" class="csl-entry">
Turner, Alex, Smith, Shah, et al. 2021. <span>“<a href="http://arxiv.org/abs/1912.01683">Optimal Policies Tend To Seek Power</a>.”</span> In <em>Advances in Neural Information Processing Systems</em>.
</div>
<div id="ref-Weisbrod1964Collective" class="csl-entry">
Weisbrod. 1964. <span>“<a href="https://doi.org/10.2307/1879478">Collective-Consumption Services of Individual-Consumption Goods</a>.”</span> <em>Quarterly Journal of Economics</em>.
</div>
<div id="ref-Williams2010Nonnegative" class="csl-entry">
Williams, and Beer. 2010. <span>“<a href="https://doi.org/10.48550/ARXIV.1004.2515">Nonnegative Decomposition of Multivariate Information</a>.”</span>
</div>
<div id="ref-WissnerGross2013Causal" class="csl-entry">
Wissner-Gross, and Freer. 2013. <span>“<a href="https://doi.org/10.1103/PhysRevLett.110.168702">Causal Entropic Forces</a>.”</span> <em>Physical Review Letters</em>.
</div>
<div id="ref-Wong2022Asymptotic" class="csl-entry">
Wong, and Bartlett. 2022. <span>“<a href="https://doi.org/10.1098/rsif.2022.0029">Asymptotic Burnout and Homeostatic Awakening: A Possible Solution to the Fermi Paradox?</a>”</span> <em>Journal of The Royal Society Interface</em>.
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>This was the kind of essay that buries a serviceable argument under a mound of unsourced abstraction. In my head I gloss such a style as <em>ChatDMT</em>.↩︎</p></li>
<li id="fn2"><p>I am also not the first to ask. The <a href="https://www.lesswrong.com/posts/Ncv5b2sjLtyi9oKGz/optionality-approach-to-ethics?ref=danmackinlay.name">‘Optionality approach to ethics’</a> proposes maximizing the number of meaningfully different choices available to agents, subject to not destroying the meaningful choices of other agents — i.e., the multi-agent constraint we will arrive at later, but seems to use a different route. Tyler Cowen wrote <a href="https://marginalrevolution.com/marginalrevolution/2019/01/option-value-civilization.html?ref=danmackinlay.name">a blog post on the option value of civilization</a> which approximates a Long Now thinkpiece. But canonized versions of the idea are hard to find.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>agents</category>
  <category>collective knowledge</category>
  <category>communicating</category>
  <category>cooperation</category>
  <category>culture</category>
  <category>democracy</category>
  <category>economics</category>
  <category>ethics</category>
  <category>faster pussycat</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>insurgency</category>
  <category>mind</category>
  <category>networks</category>
  <category>policy</category>
  <category>rhetoric</category>
  <category>sociology</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/optionality_as_end.html</guid>
  <pubDate>Sun, 12 Apr 2026 22:53:18 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/whore_of_Babylon_gri_33125014730804_0032.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Multi-agent RL tooling</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/marl_tooling.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p>Environments and frameworks for <a href="../notebook/multi_agent_reinforcement_learning.html">multi-agent RL</a> research. See also the <a href="../notebook/opponent_shaping.html">opponent shaping</a> page.</p>
<div id="fig-bee-tattoo" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-bee-tattoo-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/bee_tattoo_RP-P-1905-5650.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/bee_tattoo_RP-P-1905-5650.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-bee-tattoo-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<section id="neural-mmo" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="neural-mmo"><span class="header-section-number">1</span> Neural MMO</h2>
<p>A large, open-world game environment for multi-agent research <span class="citation" data-cites="Suarez2019Neural Suarez2024Neural Suarez2023Neural">(Suárez et al. 2019, 2023; Suarez 2024)</span>. Claim to fame: leveraging gaming-industry technology to provide a persistent, large-scale world for agent interaction, going beyond the typical matrix game or grid world.</p>
</section>
<section id="agentscope" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="agentscope"><span class="header-section-number">2</span> AgentScope</h2>
<p>See <span class="citation" data-cites="Pan2024Very">Pan et al. (2024)</span>.</p>
<p><a href="https://github.com/modelscope/agentscope/tree/main?ref=danmackinlay.name">modelscope/agentscope</a> / <a href="https://doc.agentscope.io/?ref=danmackinlay.name">documentation</a></p>
<p>A multi-agent platform oriented toward LLM-powered agents, with actor-based distribution and fault tolerance.</p>
</section>
<section id="pettingzoo" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="pettingzoo"><span class="header-section-number">3</span> PettingZoo</h2>
<p><a href="https://pettingzoo.farama.org/?ref=danmackinlay.name">PettingZoo</a> is the multi-agent extension of Gymnasium (formerly OpenAI Gym), providing a standard API for multi-agent environments. Covers classic games (chess, Go, connect four), Atari multiplayer, and MPE (multi-particle environments commonly used in cooperative/competitive MARL papers).</p>
</section>
<section id="melting-pot" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="melting-pot"><span class="header-section-number">4</span> Melting Pot</h2>
<p><a href="https://github.com/google-deepmind/meltingpot?ref=danmackinlay.name">DeepMind’s Melting Pot</a> is a suite of social dilemma environments for evaluating multi-agent cooperation and competition. Environments are designed to test generalisation: agents must cooperate with novel partners in scenarios inspired by the collective action literature.</p>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Pan2024Very" class="csl-entry">
Pan, Gao, Xie, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2407.17789">Very Large-Scale Multi-Agent Simulation in AgentScope</a>.”</span>
</div>
<div id="ref-Suarez2024Neural" class="csl-entry">
Suarez. 2024. <span>“<a href="https://dspace.mit.edu/handle/1721.1/156586">Neural MMO: Massively Multiagent Simulation and Learning</a>.”</span>
</div>
<div id="ref-Suarez2019Neural" class="csl-entry">
Suárez, Du, Isola, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1903.00784">Neural MMO: A Massively Multiagent Game Environment for Training and Evaluating Intelligent Agents</a>.”</span>
</div>
<div id="ref-Suarez2023Neural" class="csl-entry">
Suárez, Isola, Choe, et al. 2023. <span>“Neural MMO 2.0: A Massively Multi-Task Addition to Massively Multi-Agent Learning.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>agents</category>
  <category>distributed</category>
  <category>machine learning</category>
  <category>making things</category>
  <guid>https://danmackinlay.name/notebook/marl_tooling.html</guid>
  <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/bee_tattoo_RP-P-1905-5650.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Hosted Functions / Serverless</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/serverless.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-live-in-shoe" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-live-in-shoe-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/live_in_shoe.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/live_in_shoe.png" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-live-in-shoe-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>Running code in the cloud without (explicitly) managing servers.</p>
<p>In ye olde tymes, I’d rent a <a href="../notebook/virtual_machines.html">VM</a> (or a whole machine), install my OS, configure my runtime, set up monitoring, worry about security patches, and pay for it 24/7 whether anyone is using it or not. Modern provisioning tries to make life easier. We can say cool phrases like <code>devops</code> — which AFAICT is short for “<a href="../notebook/containerized_apps.html">containerization</a> means we can at least package and environment reproducibly and deploy it across providers without starting from scratch each time.” But we’re still doing way more stuff than I want to know about for my modest internet automation need, usually.</p>
<p>Serverless platforms abstract even more stuff away: we hand them a function, they run it when something triggers it, and we pay per invocation without sweating the details of the underlying infrastructure. No capacity planning, no idle costs, and scaling happens automatically. Crucially for me, I don’t need to spend my life checking whether that VM I built is still running.</p>
<p>Trade-offs: Cold starts can add noticeable latency when a function hasn’t run recently. Debugging is harder when we don’t control (or even see) the execution environment. Vendor lock-in is a thing, since AFAICT each platform has its own deployment model, runtime constraints, and surrounding ecosystem of queues and databases. For anything that runs continuously or at high volume, per-invocation pricing can end up more expensive than just keeping a cheap VM around.</p>
<p>Still, for bursty workloads, webhooks, background jobs, and lightweight APIs, “just running stuff” is convenient. These are useful for <a href="../notebook/web_api_automation.html">web API automation</a>, running <a href="../notebook/practical_cloud_ml.html">AI inference jobs</a>, or hosting small services. Or whatever, really.</p>
<section id="tooling" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="tooling"><span class="header-section-number">1</span> Tooling</h2>
<ul>
<li><a href="https://fly.io/?ref=danmackinlay.name">Fly.io</a>: A platform for developers to deploy applications and databases globally. Its value proposition is running apps “at the edge,” physically close to users, to dramatically reduce latency. It works by turning standard Docker containers into lightweight virtual machines that can be launched quickly across its 35+ global regions.</li>
<li><a href="https://docs.runpod.io/serverless/development/dual-mode-worker?ref=danmackinlay.name">runpod</a>: A serverless cloud platform specifically designed for AI and machine learning workloads. Its value proposition is providing on-demand, auto-scaling GPU compute. This allows developers to run compute-intensive AI models without managing servers, paying only for the processing time they use.</li>
<li><a href="https://www.val.town/?ref=danmackinlay.name">Val Town</a> e.g.&nbsp;<a href="https://www.robinsloan.com/lab/at-home-in-high-dimensional-space/?ref=danmackinlay.name#valtown">Delivering the mail in Val Town</a></li>
</ul>


</section>

 ]]></description>
  <category>compsci</category>
  <category>computers are awful together</category>
  <category>doing internet</category>
  <category>faster pussycat</category>
  <guid>https://danmackinlay.name/notebook/serverless.html</guid>
  <pubDate>Sun, 05 Apr 2026 10:56:23 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/live_in_shoe.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Local social platforms: a technical implementation guide</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/local_social_platform_technical.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-bantem-market" class="figure illustration full quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-bantem-market-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/Bantem_market_BI-1972-1043-8.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/Bantem_market_BI-1972-1043-8.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-bantem-market-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>This is the technical companion to <a href="../notebook/local_social_platforms.html">A social platform for your neighbourhood</a>, which lays out the social and institutional case for community-owned, local platforms. That post is the <em>why</em>. This one is the <em>how</em>—or at least a plausible <em>how</em>, since the right technical choices depend heavily on what the community actually wants to build. <a href="https://woozy-page-39c.notion.site/32d398af2ab0805293a9dfb1561b4bdf?pvs=105&amp;ref=danmackinlay.name">Sign up for updates</a> if you wish to know how this goes.</p>
<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>As with the <a href="../notebook/aus_sovereign_llm_technical.html">sovereign compute technical post</a>, this is heavily AI-assisted research. Treat specific version numbers, pricing, and benchmarks as “serving suggestions”. In practice we’d design this in a more iterative way, starting with a simple prototype and evolving the architecture as we learn what the community actually needs. Here is… way more intense. I let the LLM spin out my minimal dotpoints into some seriously wargamed scenarios.</p>
</div>
</div>
</div>
<p>I’ll sketch two deployment paths: a <strong>cloud-hosted option</strong> that’s cheap and quick to stand up, and a <strong>local hardware option</strong> that gives the community full physical sovereignty over its data. These aren’t mutually exclusive—we’d likely start with cloud hosting and migrate to local hardware if the project proves viable.</p>
<p>Does this sound interesting? <a href="../contact.html">Get in touch</a>.</p>
<section id="build-or-buy" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="build-or-buy"><span class="header-section-number">1</span> Build or buy?</h2>
<p>The first decision is whether to customize an existing platform or build from scratch.</p>
<section id="off-the-shelf-bonfire" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="off-the-shelf-bonfire"><span class="header-section-number">1.1</span> Off-the-shelf: Bonfire</h3>
<p><a href="https://bonfirenetworks.org/?ref=danmackinlay.name">Bonfire</a> is the most interesting pre-built option for this use case. It’s a modular, federated social platform built in Elixir/Phoenix, explicitly designed for community self-governance. It speaks <a href="../notebook/fediverse.html">ActivityPub</a>, so it can interoperate with the Fediverse, and it’s built around “extensions”—pluggable modules for different social functions (discussion, coordination, economic exchange).</p>
<p>Pros:</p>
<ul>
<li>Real-time by default (Phoenix LiveView—no polling, no page reloads)</li>
<li>Federation built-in, so we can start local and open up later</li>
<li>Governance features designed in from the start (roles, boundaries, moderation)</li>
<li>Active development community, explicitly aligned with cooperative values</li>
<li>Elixir/BEAM runtime is famously good at handling many concurrent connections cheaply</li>
</ul>
<p>Cons:</p>
<ul>
<li>Relatively early-stage; documentation is patchy</li>
<li>The extension ecosystem is thin—we’d need to build or heavily customize extensions for marketplace, events calendar, and tool library functionality</li>
<li>Elixir is not a common skill; if our volunteer maintainers are Python or JavaScript people, the learning curve is steep</li>
<li>The modular architecture means deployment is more complex than “just run the Docker container”</li>
</ul>
</section>
<section id="off-the-shelf-other-options" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="off-the-shelf-other-options"><span class="header-section-number">1.2</span> Off-the-shelf: other options</h3>
<p><strong><a href="https://www.discourse.org/?ref=danmackinlay.name">Discourse</a></strong> is battle-tested forum software with a plugin ecosystem. It could handle discussion and events but would need significant customization for marketplace or tool-library functions. It runs on Ruby/Rails; hosting requirements are heavier than Bonfire (we recommend 2GB+ RAM minimum).</p>
<p><strong><a href="https://joinmobilizon.org/?ref=danmackinlay.name">Mobilizon</a></strong> is Framasoft’s federated events platform, written in Elixir. It does events well, but <em>only</em> events—it’s not a general social platform. It could be part of a multi-service stack rather than the whole thing.</p>
</section>
<section id="custom-build" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="custom-build"><span class="header-section-number">1.3</span> Custom build</h3>
<p>Given that AI coding assistants can now scaffold a full-stack web application in a weekend, building from scratch is less crazy than it sounds. The advantage is total control over the feature set and UX. The disadvantage is total responsibility for maintenance, security, and ongoing development.</p>
<p>A sensible custom stack for this kind of project in 2026:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Layer</th>
<th>Choice</th>
<th>Rationale</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Backend</td>
<td><strong>Python (Django)</strong> or <strong>Elixir (Phoenix)</strong></td>
<td>Django: enormous ecosystem, easy to find contributors, great ORM. Phoenix: real-time by default, better concurrency model, but smaller talent pool</td>
</tr>
<tr class="even">
<td>Frontend</td>
<td><strong>HTMX + server-rendered templates</strong> or <strong>SvelteKit</strong></td>
<td>HTMX keeps things simple and server-authoritative. SvelteKit if we want a richer client experience</td>
</tr>
<tr class="odd">
<td>Database</td>
<td><strong>PostgreSQL</strong> with <strong>PostGIS</strong></td>
<td>PostGIS gives us spatial queries for free—essential for “things near me” in a neighbourhood platform</td>
</tr>
<tr class="even">
<td>Search</td>
<td><strong>Meilisearch</strong> or <strong>PostgreSQL full-text</strong></td>
<td>Meilisearch is fast and typo-tolerant; Postgres FTS is simpler to deploy and good enough for our scale</td>
</tr>
<tr class="odd">
<td>Real-time</td>
<td><strong>WebSockets</strong> (built into Phoenix; Django Channels for Django)</td>
<td>For live chat, marketplace notifications, etc.</td>
</tr>
<tr class="even">
<td>Auth</td>
<td><strong>Passkeys</strong> + email magic links</td>
<td>No passwords to manage or leak. Passkeys are the future; magic links are the pragmatic fallback</td>
</tr>
<tr class="odd">
<td>Federation</td>
<td><strong>ActivityPub</strong> (optional, add later)</td>
<td>Don’t build this in v1. Add it when/if we want to connect to the Fediverse</td>
</tr>
</tbody>
</table>
<p>My instinct, given the audience for this project (community-minded people in Melbourne, likely to include a few developers but not a dedicated engineering team), is to start with <strong>Django + HTMX + PostgreSQL/PostGIS</strong>. It’s the most boring choice, and that’s the point—boring means the biggest pool of people who can maintain it, the most Stack Overflow answers, and the lowest risk the project dies because the one person who understood the framework moved to Tasmania.</p>
<p>We can scaffold the initial version with Claude or a similar coding assistant in a couple of dedicated weekends. A marketplace with listings, search, user profiles, reputation, and messaging is maybe 20–30 screens. An events calendar adds another 5–10. It’s not trivial, but it’s not a moonshot either.</p>
</section>
</section>
<section id="path-1-cloud-hosting" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="path-1-cloud-hosting"><span class="header-section-number">2</span> Path 1: Cloud hosting</h2>
<p>This is a sensible starting point. Low upfront cost, no hardware to maintain, easy to iterate.</p>
<section id="architecture" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="architecture"><span class="header-section-number">2.1</span> Architecture</h3>
<pre><code>┌─────────────────────────────────────────────┐
│              Reverse proxy (Caddy)           │
│         TLS termination, static files        │
├─────────────────────────────────────────────┤
│            App server (Django/Gunicorn       │
│               or Phoenix)                    │
├──────────────────┬──────────────────────────┤
│   PostgreSQL     │     Redis                │
│   + PostGIS      │   (cache, sessions,      │
│                  │    WebSocket broker)      │
└──────────────────┴──────────────────────────┘
│              Object storage (S3/R2)          │
│          (user uploads, listing images)      │
└─────────────────────────────────────────────┘</code></pre>
</section>
<section id="hosting-options-and-costs" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="hosting-options-and-costs"><span class="header-section-number">2.2</span> Hosting options and costs</h3>
<p>For ~1,000 active users with moderate traffic (maybe 10,000 page views/day at peak, much less most of the time), we need very little compute.</p>
<p><strong>Option A: Single VPS (simplest)</strong></p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Provider</th>
<th>Spec</th>
<th>Monthly cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://www.hetzner.com/cloud/?ref=danmackinlay.name">Hetzner</a> CX32</td>
<td>4 vCPU, 8GB RAM, 80GB NVMe</td>
<td>€8.50 (~$14 AUD)</td>
</tr>
<tr class="even">
<td><a href="https://www.digitalocean.com/pricing?ref=danmackinlay.name">DigitalOcean</a></td>
<td>4 vCPU, 8GB RAM, 160GB</td>
<td>$48 USD (~$75 AUD)</td>
</tr>
<tr class="odd">
<td><a href="https://www.vultr.com/pricing/?ref=danmackinlay.name">Vultr</a></td>
<td>4 vCPU, 8GB RAM, 128GB</td>
<td>$48 USD (~$75 AUD)</td>
</tr>
<tr class="even">
<td><a href="https://www.binarylane.com.au/?ref=danmackinlay.name">BinaryLane</a> (Australian)</td>
<td>4 vCPU, 8GB RAM</td>
<td>~$40 AUD</td>
</tr>
</tbody>
</table>
<p>Hetzner is cheap and seems reliable, but their nearest data centre is Singapore—latency from Melbourne is ~60 ms, which is fine for a web app but noticeable for real-time features. BinaryLane and similar Australian providers cost more but give sub-10 ms latency.</p>
<p>For the MVP, a single Hetzner CX32 at $14 AUD/month runs everything—app server, database, Redis, Caddy at under the a latte-a-week price point.</p>
<p><strong>Option B: Managed services (less ops burden)</strong></p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Service</th>
<th>What it does</th>
<th>Monthly cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://railway.com/pricing?ref=danmackinlay.name">Railway</a> or <a href="https://render.com/pricing?ref=danmackinlay.name">Render</a></td>
<td>App hosting</td>
<td>$7–25 USD</td>
</tr>
<tr class="even">
<td><a href="https://neon.tech/pricing?ref=danmackinlay.name">Neon</a> or <a href="https://supabase.com/pricing?ref=danmackinlay.name">Supabase</a></td>
<td>Managed PostgreSQL + PostGIS</td>
<td>Free tier → $25 USD</td>
</tr>
<tr class="odd">
<td><a href="https://www.cloudflare.com/products/r2/?ref=danmackinlay.name">Cloudflare R2</a></td>
<td>Object storage (images)</td>
<td>~$0–5 USD (generous free tier)</td>
</tr>
<tr class="even">
<td><a href="https://upstash.com/pricing?ref=danmackinlay.name">Upstash</a></td>
<td>Managed Redis</td>
<td>Free tier → $10 USD</td>
</tr>
</tbody>
</table>
<p>Total: <strong>$15–65 USD/month (~$25–100 AUD)</strong> depending on tier. More expensive than a raw VPS, but with way less ops burden—no server patching, no database backups to manage, and automatic scaling.</p>
<p><strong>Option C: The free tier special</strong></p>
<p>For a truly bootstrapped start:</p>
<ul>
<li><a href="https://vercel.com/pricing?ref=danmackinlay.name">Vercel</a> (free tier) for the frontend, if we go with a SvelteKit or Next.js frontend</li>
<li><a href="https://fly.io/docs/about/pricing/?ref=danmackinlay.name">Fly.io</a> (free tier: 3 shared-cpu VMs, 256MB RAM each) for the backend</li>
<li><a href="https://neon.tech/pricing?ref=danmackinlay.name">Neon</a> (free tier: 0.5GB storage, autosuspend) for PostgreSQL</li>
<li><a href="https://www.cloudflare.com/products/r2/?ref=danmackinlay.name">Cloudflare R2</a> (free tier: 10GB) for images</li>
</ul>
<p>Total: <strong>$0/month</strong> until we outgrow the free tiers, which for a neighbourhood of a few hundred users might be a while. The catch is that free tiers have cold-start latency (the app literally goes to sleep and takes a few seconds to wake up) and resource limits that will get annoying.</p>
</section>
<section id="deployment" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="deployment"><span class="header-section-number">2.3</span> Deployment</h3>
<p>The whole stack should be containerized from day one. A <code>docker compose</code> setup makes it possible to run the same stack locally for development, on a VPS for production, and on local hardware (Path 2) when we’re ready.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource yaml number-lines code-with-copy"><code class="sourceCode yaml"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># docker-compose.yml (simplified)</span></span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">services</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">web</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">build</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> .</span></span>
<span id="cb2-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ports</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"8000:8000"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb2-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">environment</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">DATABASE_URL</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> postgres://db:5432/localplatform</span></span>
<span id="cb2-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">REDIS_URL</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> redis://redis:6379</span></span>
<span id="cb2-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">depends_on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">db</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> redis</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb2-10"></span>
<span id="cb2-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">db</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">image</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> postgis/postgis:16-3.4</span></span>
<span id="cb2-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">volumes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pgdata:/var/lib/postgresql/data"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb2-14"></span>
<span id="cb2-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">redis</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-16"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">image</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> redis:7-alpine</span></span>
<span id="cb2-17"></span>
<span id="cb2-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">caddy</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">image</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> caddy:2</span></span>
<span id="cb2-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ports</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"80:80"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"443:443"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb2-21"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">volumes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"./Caddyfile:/etc/caddy/Caddyfile"</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb2-22"></span>
<span id="cb2-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">volumes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-24"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pgdata</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span></code></pre></div></div>
<p>From here to production on a VPS: <code>ssh</code> in, install Docker, run <code>docker compose up -d</code>, and point DNS at the server. That’s a Saturday afternoon, including lunch.</p>
</section>
<section id="backups" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="backups"><span class="header-section-number">2.4</span> Backups</h3>
<p>The database is the thing we cannot afford to lose. A daily <code>pg_dump</code> backup to object storage (Cloudflare R2, Backblaze B2) costs essentially nothing and is straightforward to automate. Test restores monthly—a backup we’ve never restored from is a hypothesis, not a backup.</p>
</section>
<section id="data-sovereignty-note" class="level3" data-number="2.5">
<h3 data-number="2.5" class="anchored" data-anchor-id="data-sovereignty-note"><span class="header-section-number">2.5</span> Data sovereignty note</h3>
<p>If we use a European or American hosting provider, the community’s data lives in their jurisdiction. For a neighbourhood marketplace this is probably fine—we’re not storing state secrets, just listings for second-hand couches. But if the community cares about data sovereignty (and they might, given the ethos of this project), an Australian hosting provider or local hardware (Path 2) is the answer.</p>
</section>
</section>
<section id="path-2-closet-servers" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="path-2-closet-servers"><span class="header-section-number">3</span> Path 2: Closet servers</h2>
<p>This is the sovereign option. The community’s data lives on hardware the community physically owns, in someone’s house or a shared space. It costs more upfront, requires more operational skill, and is less reliable than cloud hosting—but it means no one can pull the plug on our community because of a terms-of-service change or a foreign government’s policy shift.</p>
<section id="hardware" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="hardware"><span class="header-section-number">3.1</span> Hardware</h3>
<p>We don’t need much. A neighbourhood platform for 1,000 users with moderate traffic is computationally trivial by modern standards—a decade-old laptop could handle it.</p>
<p><strong>The minimum viable closet server:</strong></p>
<p>Buy two second-hand business laptops. ThinkPads, Latitudes, HP EliteBooks—the kind of thing that comes off three-year corporate leases and shows up on eBay or Gumtree for $150–300 each. We go for something with 16 GB RAM, an Ethernet port, and a working battery. Ideally, we buy at least two of the same model.</p>
<p>Why two? Redundancy. One is the primary server; the other is a warm standby running the same stack, with the database replicated via PostgreSQL streaming replication. If the primary dies (hardware failure, house fire, someone trips over the power cord), the standby can take over.</p>
<p><strong>Estimated hardware costs:</strong></p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Item</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>2× second-hand business laptop</td>
<td>$300–600 AUD</td>
</tr>
<tr class="even">
<td>2× USB-C Ethernet adapter (if needed)</td>
<td>$30–50 AUD</td>
</tr>
<tr class="odd">
<td>1× small Ethernet switch</td>
<td>$20–40 AUD</td>
</tr>
<tr class="even">
<td>1× external SSD for backups</td>
<td>$60–100 AUD</td>
</tr>
<tr class="odd">
<td>Misc cables</td>
<td>$20 AUD</td>
</tr>
</tbody>
</table>
<p><strong>Total: $430–810 AUD.</strong> That’s about $4–8 per household in a 100-household community—less than a month’s coffee.</p>
</section>
<section id="networking" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="networking"><span class="header-section-number">3.2</span> Networking</h3>
<p>The same problem we discussed for the <a href="../notebook/aus_sovereign_llm.html#the-nbn-still-sucks-but-not-so-badly-that-we-cant-work-around-it">sovereign compute closet</a>: Australian residential internet is not great, and running a server on NBN has quirks.</p>
<p>The requirements:</p>
<ul>
<li><strong>A static IP or dynamic DNS.</strong> Most NBN plans give you a dynamic IP, which changes periodically. A dynamic DNS service (e.g.&nbsp;<a href="https://www.duckdns.org/?ref=danmackinlay.name">DuckDNS</a>, free) maps a hostname to your current IP automatically. A business-grade NBN plan with a static IP ($10–30/month more than residential) is more reliable.</li>
<li><strong>Port forwarding or a tunnel.</strong> If the server is behind a home router, we need to forward ports 80 and 443 to the server. Alternatively, a <a href="https://www.cloudflare.com/products/tunnel/?ref=danmackinlay.name">Cloudflare Tunnel</a> (free tier) creates an outbound connection from the server to Cloudflare’s edge, avoiding the need for port forwarding entirely and providing DDoS protection as a bonus. This is probably the right answer for most setups.</li>
<li><strong>Upload bandwidth.</strong> A web application serving mostly text and small images is not bandwidth-heavy. At our scale, even a 20 Mbps upload link (typical for NBN FTTP 100/20 plans) would handle hundreds of concurrent users without breaking a sweat. Listing photos are the heaviest content; resizing them to reasonable dimensions on upload (say, 1200px wide, WebP format) keeps each image under 100KB.</li>
</ul>
<p>The Cloudflare Tunnel approach is cool because it solves several problems at once: no static IP needed, no port forwarding, automatic TLS, DDoS protection, and it works even behind double-NAT (common with some NBN configurations). The server makes an outbound connection to Cloudflare; Cloudflare routes incoming requests back through that connection.</p>
</section>
<section id="operating-system-and-maintenance" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="operating-system-and-maintenance"><span class="header-section-number">3.3</span> Operating system and maintenance</h3>
<p>Install Ubuntu Server LTS (currently 24.04) on both laptops. Enable <a href="https://wiki.debian.org/UnattendedUpgrades?ref=danmackinlay.name">unattended-upgrades</a> for automatic security patches. Run the same <code>docker compose</code> stack as the cloud version—the containers don’t care whether they’re running on Hetzner or on a ThinkPad in someone’s hallway cupboard.</p>
<p>Monitoring: a lightweight monitoring tool like <a href="https://github.com/louislam/uptime-kuma?ref=danmackinlay.name">Uptime Kuma</a> (self-hosted, runs in Docker) sends alerts if the server goes down. Point an external uptime monitor (e.g.&nbsp;<a href="https://uptimerobot.com/?ref=danmackinlay.name">UptimeRobot</a>, free for 50 monitors) at the public URL for an independent check.</p>
</section>
<section id="power-and-heat" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="power-and-heat"><span class="header-section-number">3.4</span> Power and heat</h3>
<p>Two laptops draw roughly 30–60 watts total under moderate load—negligible on the electricity bill (under $10/month at Australian rates). They generate less heat than a desk lamp. A hallway cupboard with a slightly open door is genuinely fine as a “data centre” for this scale of operation.</p>
<p>A dedicated UPS is nice to have but not essential—the laptop batteries provide 30–60 minutes of runtime, which is enough to ride out most power blips. For a longer outage, the platform is simply down until power returns, which is fine—we’re not running a stock exchange.</p>
</section>
<section id="replication-and-failover" class="level3" data-number="3.5">
<h3 data-number="3.5" class="anchored" data-anchor-id="replication-and-failover"><span class="header-section-number">3.5</span> Replication and failover</h3>
<p>PostgreSQL streaming replication keeps the standby laptop’s database in near-real-time sync with the primary (seconds of lag, typically). A simple failover procedure:</p>
<ol type="1">
<li>Primary goes down (detected by monitoring)</li>
<li>A human (the designated ops person) SSHs into the standby</li>
<li>Promote the standby to primary: <code>pg_ctl promote</code></li>
<li>Update the Cloudflare Tunnel or DNS to point at the standby’s IP</li>
<li>Investigate and repair the primary; when it’s back, reverse the replication direction</li>
</ol>
<p>This is not automatic failover (that’s complex to get right and risky to get wrong at small scale), but it’s a 10-minute procedure that any moderately competent Linux user can follow from a runbook.</p>
</section>
<section id="backups-1" class="level3" data-number="3.6">
<h3 data-number="3.6" class="anchored" data-anchor-id="backups-1"><span class="header-section-number">3.6</span> Backups</h3>
<p>Even with two machines, we want off-site backups. A nightly <code>pg_dump</code>, compressed and encrypted with <code>age</code> or GPG, pushed to a cloud storage bucket (Backblaze B2: $5/TB/month) or even to a third community member’s machine via <code>rsync</code>. Belt and braces.</p>
</section>
</section>
<section id="the-migration-path" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="the-migration-path"><span class="header-section-number">4</span> The migration path</h2>
<p>Start on cloud hosting (Path 1). It’s cheaper, faster to set up, and lets the community focus on the social problem (do people actually want this?) rather than the operational one (is the server up?).</p>
<p>If the platform takes off and the community decides it wants data sovereignty, the migration to local hardware (Path 2) is straightforward because we’ve containerized everything from the start. The procedure is:</p>
<ol type="1">
<li>Set up the closet servers and run the same <code>docker compose</code> stack</li>
<li>Take a database dump from the cloud and restore it locally</li>
<li>Switch DNS to point at the local server</li>
<li>Keep the cloud instance as a fallback for a month</li>
<li>Decommission the cloud instance</li>
</ol>
<p>This should take an afternoon and the community will experience maybe 30 minutes of planned downtime.</p>
</section>
<section id="features-roadmap" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="features-roadmap"><span class="header-section-number">5</span> Features roadmap</h2>
<p>What do we actually build, and in what order? This is a suggestion, not a prescription—the community should decide.</p>
<section id="v0.1-marketplace-mvp-month-12" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="v0.1-marketplace-mvp-month-12"><span class="header-section-number">5.1</span> v0.1: Marketplace MVP (Month 1–2)</h3>
<ul>
<li>User registration with postcode verification (enter postcode, receive a physical postcard with a code—low-tech, high-trust, and fun)</li>
<li>Listing creation: title, description, photos, price (or “free” or “swap”), category</li>
<li>Search and browse with PostGIS-powered proximity sorting</li>
<li>Messaging between buyer and seller</li>
<li>Basic reputation: completed transactions, star ratings, text reviews</li>
<li>Simple moderation: flag/report button, admin review queue</li>
</ul>
</section>
<section id="v0.2-events-directory-month-34" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="v0.2-events-directory-month-34"><span class="header-section-number">5.2</span> v0.2: Events + directory (Month 3–4)</h3>
<ul>
<li>Events calendar: create, browse, RSVP</li>
<li>Skills/services directory: “I’m a plumber,” “I do tutoring,” “I have a ute”</li>
<li>Tag-based discovery: search by skill, service, or interest</li>
<li>Notification preferences: email digest (daily/weekly) rather than push notifications—deliberately low-urgency</li>
</ul>
</section>
<section id="v0.3-trust-and-governance-month-56" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="v0.3-trust-and-governance-month-56"><span class="header-section-number">5.3</span> v0.3: Trust and governance (Month 5–6)</h3>
<ul>
<li>Vouching system: existing members can vouch for new members, creating a trust graph</li>
<li>Community proposals and voting (for platform rules, feature requests, spending decisions)</li>
<li>Transparency log: all moderation actions visible to members</li>
<li>Privacy controls: who can see your profile, your listings, your reviews</li>
</ul>
</section>
<section id="v1.0-community-infrastructure-month-6" class="level3" data-number="5.4">
<h3 data-number="5.4" class="anchored" data-anchor-id="v1.0-community-infrastructure-month-6"><span class="header-section-number">5.4</span> v1.0: Community infrastructure (Month 6+)</h3>
<ul>
<li>Tool/resource library: list things available to borrow, manage lending/returning</li>
<li>Mutual aid board: request or offer help, coordinated through the platform</li>
<li>Group purchasing: organize bulk buys (firewood, solar panels, etc.) at better rates</li>
<li>Integration with the <a href="../notebook/neo_friendly_societies.html">friendly society</a> (if one exists): membership management, communications, voting on society matters</li>
<li>AI-assisted features (if the community has access to <a href="../notebook/aus_sovereign_llm.html">sovereign compute</a>): smart search, listing categorization, translation for multilingual neighbourhoods, moderation assistance</li>
</ul>
</section>
</section>
<section id="ai-assisted-development" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="ai-assisted-development"><span class="header-section-number">6</span> AI-assisted development</h2>
<p>Since I argued in the <a href="../notebook/local_social_platforms.html">companion post</a> that LLMs lower the development cost, let me be concrete about what that looks like.</p>
<p>A competent developer working with a coding assistant (Claude, Cursor, Copilot, or inference from the <a href="../notebook/aus_sovereign_llm.html">community’s own compute</a>) can realistically:</p>
<ul>
<li><strong>Scaffold the entire v0.1 in 2–3 weekends</strong> (Django project structure, models, views, templates, basic CSS)</li>
<li><strong>Generate test suites</strong> that would otherwise take days to write by hand</li>
<li><strong>Draft moderation tooling</strong> (content classifiers, spam detection) using local models rather than shipping community data to a commercial API</li>
<li><strong>Produce documentation and runbooks</strong> for operational procedures, reducing the bus factor</li>
</ul>
<p>This doesn’t eliminate the need for human judgement—architecture decisions, UX design, security review—but it compresses the grunt work enough that a small volunteer team can make it go.</p>
<p>The sovereign compute connection is particularly neat here: if the community owns an LLM inference box, the same machine that serves the platform’s AI features (search, moderation, translation) can also be the coding assistant that helps maintain the platform itself. The tools build the tools.</p>
</section>
<section id="security-considerations" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="security-considerations"><span class="header-section-number">7</span> Security considerations</h2>
<p>A neighbourhood platform holds personal information (names, addresses, phone numbers, transaction history, private messages) for people who literally live near each other. A breach would be personally harmful in a way that’s different from a breach of a global platform—the attacker knows where we live.</p>
<p>Non-negotiable security measures:</p>
<ul>
<li><strong>TLS everywhere.</strong> Caddy handles this automatically with Let’s Encrypt certificates.</li>
<li><strong>Passwords: don’t.</strong> Use passkeys where possible, email magic links as fallback. No password database to breach.</li>
<li><strong>Input sanitization.</strong> Django’s ORM and template system handle the common injection vectors, but we should still run a security-focused code review before launch.</li>
<li><strong>Rate limiting.</strong> Prevent brute-force attacks on auth and abuse of messaging. Django-ratelimit or equivalent.</li>
<li><strong>Encryption at rest.</strong> Full-disk encryption on the server (LUKS on the closet laptops; most cloud providers offer this as a checkbox).</li>
<li><strong>Minimal data collection.</strong> Don’t store data we don’t need. If we don’t need to know someone’s exact address (we probably don’t—postcode is enough for locality), don’t ask for it.</li>
<li><strong>Penetration testing.</strong> Before launch, ask a security-minded community member (or hire someone for a day) to try to break in. Fix what they find.</li>
</ul>
</section>
<section id="legal-structure-for-the-platform" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="legal-structure-for-the-platform"><span class="header-section-number">8</span> Legal structure for the platform</h2>
<p>The platform needs a legal home—someone has to own the domain, pay the hosting bills, and be accountable for the data.</p>
<p>The simplest option is an <strong>incorporated association</strong> under Victorian (or relevant state) law. This gives the platform a legal identity, limits members’ personal liability, and costs ~$200 to set up and ~$57/year to maintain. The association’s committee manages the platform on behalf of its members.</p>
<p>If the platform grows and starts handling money (marketplace transaction fees, membership dues), a <strong>co-operative</strong> under the Co-operatives National Law might be more appropriate—it’s the legal form designed for member-owned economic infrastructure. See the <a href="../notebook/neo_friendly_societies_technical.html">friendly society legal analysis</a> for a deeper discussion of the options, which overlap substantially.</p>
<p><strong>Privacy obligations:</strong> under the Australian Privacy Act, organisations with annual turnover under $3 million are generally exempt from the Australian Privacy Principles. A neighbourhood platform is unlikely to hit this threshold. That said, the community should adopt privacy-respecting practices regardless of legal obligation—it’s the right thing to do, and it builds trust.</p>
</section>
<section id="what-this-costs-total" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="what-this-costs-total"><span class="header-section-number">9</span> What this costs, total</h2>
<section id="cloud-path-first-year" class="level3" data-number="9.1">
<h3 data-number="9.1" class="anchored" data-anchor-id="cloud-path-first-year"><span class="header-section-number">9.1</span> Cloud path (first year)</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Item</th>
<th>Annual cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Hosting (Hetzner CX32)</td>
<td>~$170 AUD</td>
</tr>
<tr class="even">
<td>Domain name</td>
<td>~$20 AUD</td>
</tr>
<tr class="odd">
<td>Email (Fastmail or similar, for platform notifications)</td>
<td>~$60 AUD</td>
</tr>
<tr class="even">
<td>Backblaze B2 backups</td>
<td>~$10 AUD</td>
</tr>
<tr class="odd">
<td>Incorporated association setup</td>
<td>~$200 AUD</td>
</tr>
<tr class="even">
<td>Security review (1 day, hired)</td>
<td>~$500–1,000 AUD</td>
</tr>
<tr class="odd">
<td><strong>Total first year</strong></td>
<td><strong>~$960–1,460 AUD</strong></td>
</tr>
<tr class="even">
<td><strong>Total ongoing (year 2+)</strong></td>
<td><strong>~$320 AUD/year</strong></td>
</tr>
</tbody>
</table>
<p>At 200 households paying $50/year in membership, revenue is $10,000/year. Running costs are under $500/year ongoing. The surplus funds development, maintenance stipends, and community activities.</p>
</section>
<section id="closet-path-first-year-additional-to-cloud-year" class="level3" data-number="9.2">
<h3 data-number="9.2" class="anchored" data-anchor-id="closet-path-first-year-additional-to-cloud-year"><span class="header-section-number">9.2</span> Closet path (first year, additional to cloud year)</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Item</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>2× second-hand laptops</td>
<td>$300–600 AUD</td>
</tr>
<tr class="even">
<td>Networking gear</td>
<td>$50–90 AUD</td>
</tr>
<tr class="odd">
<td>Business NBN upgrade (static IP)</td>
<td>~$200–360 AUD/year</td>
</tr>
<tr class="even">
<td>Electricity</td>
<td>~$60–100 AUD/year</td>
</tr>
<tr class="odd">
<td><strong>Total hardware setup</strong></td>
<td><strong>$350–690 AUD (one-off)</strong></td>
</tr>
<tr class="even">
<td><strong>Total ongoing</strong></td>
<td><strong>~$260–460 AUD/year</strong></td>
</tr>
</tbody>
</table>
<p>The cloud and closet paths cost roughly the same ongoing. The closet path has a higher upfront cost (about ~$700 one-off, on top of the cloud path), but gives us physical data sovereignty and no ongoing dependency on a hosting provider.</p>
</section>
</section>
<section id="open-questions" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="open-questions"><span class="header-section-number">10</span> Open questions</h2>
<ul>
<li>Is Django + HTMX the right default stack, or should we optimize for the specific skills of whoever shows up to build it? (If the founding devs are Rails people, build it in Rails. If they’re Elixir people, use Phoenix. The technology matters less than the people.)</li>
<li>How do we handle identity verification without becoming creepy? The postcard-with-code idea is fun but has edge cases (apartments, PO boxes, renters who move). Vouching by existing members is socially robust but creates gatekeeping risks.</li>
<li>What’s the right moderation model for neighbourhood scale? Professional community managers are too expensive; pure volunteer moderation burns people out; algorithmic moderation misses context. Some hybrid is needed.</li>
<li>Should the platform support financial transactions (escrow for marketplace purchases, membership fee collection), or keep money external (bank transfers, cash on pickup)? Handling money adds regulatory complexity but removes friction.</li>
<li>How do we handle the “missing stair” problem—a community member who everyone quietly knows is problematic, but who hasn’t technically violated any rules?</li>
</ul>
<p>As with everything in this series: if any of us have opinions, experience, or want to help build this, <a href="../contact.html">get in touch</a>.</p>


</section>

 ]]></description>
  <category>communicating</category>
  <category>community project</category>
  <category>cooperation</category>
  <category>DIY</category>
  <category>engineering</category>
  <category>faster pussycat</category>
  <category>institutions</category>
  <category>networks</category>
  <category>P2P</category>
  <category>resilient tech</category>
  <category>straya</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/local_social_platform_technical.html</guid>
  <pubDate>Wed, 25 Mar 2026 01:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/Bantem_market_BI-1972-1043-8.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>A social platform for your neighbourhood</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/local_social_platforms.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-friendly-market" class="figure illustration full quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-friendly-market-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/friednly_market_RP-P-1979-116.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/friednly_market_RP-P-1979-116.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-friendly-market-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<div class="callout callout-style-simple callout-none no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">None</span>A local app for local people
</div>
</div>
<div class="callout-body-container callout-body">
<p>It’s 2029. Over the past three years, things have gotten worse in the ways everyone predicted and no one prepared for. Facebook Marketplace has become even less usable unusable—half the listings are AI-generated scam bait, the algorithm buries anything that isn’t a promoted post, and Meta’s latest “community standards” update broke half the local buy/sell groups overnight. Nextdoor pivoted to a “local commerce super-app” that’s basically a worse version of Uber Eats with a comments section full of people reporting each other’s bins. Instagram is a shopping mall. Twitter/X is a propaganda channel. The <a href="../notebook/moloch_vs_slack.html#involution">enshittification</a> cycle has completed on basically every platform you used to rely on for local life.</p>
<p>You moved to your neighbourhood five years ago, but you still don’t know most of your neighbours. You have a vague sense that the person two doors down is a plumber, and that there’s a woman on the next street who keeps chickens—you’ve seen them through the fence—but you have no way to contact either of them. There’s no noticeboard. There’s no village square, that’s all gone into developer-led apartment block with no communal space. There’s just a Facebook group with 4,000 members and a posting cadence that’s 40% scam, 30% rant, 20% lost cat, and 10% someone asking whether anyone heard that noise last night.</p>
<p>Now imagine that eighteen months ago, a small group of people in your area started a local platform. Nothing grand—it started as a marketplace, honestly, because someone was sick of getting catfished on Facebook Marketplace when trying to sell a bookshelf. A simple listings board, verified to actual local residents, with a reputation system that meant something because people could see each other at the shops.</p>
<p>That marketplace turned out to be the seed. Once a few hundred people were on it and trusted it, other things grew: an events calendar that actually worked (no algorithmic filtering, no promoted events from the other side of the city), a skills exchange, a mutual aid thread that spun up fast when the February heatwave knocked out power to half the suburb for three days. During those three days, the platform was how people found out who had a generator, who needed to move their insulin to someone’s working fridge, which streets still had water pressure.</p>
<p>Nobody planned for it to be emergency infrastructure. It just turned out that a neighbourhood where people already knew and trusted each other digitally was also a neighbourhood that could coordinate when things went wrong.</p>
</div>
</div>
<p><strong>Interested? <a href="../contact.html">Get in touch</a> or <a href="https://woozy-page-39c.notion.site/32d398af2ab0805293a9dfb1561b4bdf?pvs=105&amp;ref=danmackinlay.name">sign up for updates</a>.</strong></p>
<p>This is the third in a series of posts about <a href="../notebook/civic_tech.html">practical community infrastructure building</a>. The <a href="../notebook/neo_friendly_societies.html">first</a> proposed neo-friendly societies for financial mutual aid. The <a href="../notebook/aus_sovereign_llm.html">second</a> explored community-owned AI compute. This one is about something that sounds simpler but might be harder: building a local social platform that people actually use, despite the cheap availability of commodity options like Facebook that you need pay for only with that undervalued currency of personal information.</p>
<p>This post is even less definite than its companions, were that possible. A friendly society has a clear value proposition (money when you need it) and a well-understood legal form. A compute collective has a concrete asset (a machine) and a measurable output (tokens per second). A local social platform has … vibes? Community? The warm feeling of knowing your neighbours?</p>
<p>These are real goods. But they’re harder to price, harder to pitch, and much harder to bootstrap, because the value of a social platform is almost entirely a function of whether other people are on it—the cold start problem that has killed a thousand well-intentioned alternatives to Facebook.</p>
<p>IMO it’s worth it to dream about sovereign community comms because the returns, if it works, are potentially enormous—and because the other two projects in this series might naturally seed (or need) a compatible social platform as a side effect.</p>
<p>A friendly society is a social institution. A compute collective is a community of users. Both need trust, communication, and a shared sense of identity. Social platforms tie them together, as evidenced by the dozens of Discord servers I am on rn. A social platform is the rhizome that fertilizes other projects.</p>
<section id="why-now" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="why-now"><span class="header-section-number">1</span> Why now?</h2>
<p>Two things have changed that make this more tractable than it was five years ago.</p>
<section id="the-incumbents-are-falling-apart" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="the-incumbents-are-falling-apart"><span class="header-section-number">1.1</span> The incumbents are falling apart</h3>
<p>This is the <a href="../notebook/moloch_vs_slack.html#involution">enshittification</a> thesis, and it’s moved from theory to lived experience. Cory Doctorow’s account of the platform life cycle—subsidize users to build lock-in, extract value once they’re trapped, then pivot to serving advertisers and shareholders at users’ expense—is something that we’ve all seen play out so many times that it is getting embarrassing that we do not plan around it. It’s a description of what Facebook, Instagram, and Twitter have already done. Paul Krugman has <a href="https://paulkrugman.substack.com/p/the-general-theory-of-enshittification?ref=danmackinlay.name">argued</a> that this logic extends to any business with network effects, not just social media specifically.</p>
<p>The practical consequence is that the platforms people relied on for local coordination—Facebook Groups, Facebook Marketplace, Facebook Events—are getting worse, not by accident but by design. Meta is optimizing for ad revenue, not for helping us find a second-hand couch or organize a street party. The gap between what these platforms provide and what communities actually need is widening.</p>
<p>That gap is a market niche. It’s a small one, and it won’t make anyone rich, but that low financial return for a corporate might still be a comparatively high return in terms of impact, status and other human-scale values.</p>
</section>
<section id="llms-lower-the-development-cost" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="llms-lower-the-development-cost"><span class="header-section-number">1.2</span> LLMs lower the development cost</h3>
<p>Building a social platform used to require a well-funded startup. Now a competent developer with access to AI coding tools—or a small collective with a shared <a href="../notebook/aus_sovereign_llm.html">sovereign compute</a> instance—can stand up a functional web application in weeks rather than years. The technical barrier has dropped dramatically.</p>
<p>Robin Sloan has written beautifully about this: <a href="https://www.robinsloan.com/notes/home-cooked-app/?ref=danmackinlay.name">an app can be a home-cooked meal</a>. Not everything has to be a unicorn. Not everything has to scale. Sometimes we make a thing for the people we know, and it’s good because it’s <em>for them specifically</em>, in a way that a platform serving two billion users can never be.</p>
<p>Chris McCormick calls this <a href="https://mccormick.cx/news/entries/sub-dunbar-software?ref=danmackinlay.name">sub-Dunbar software</a>—software that works precisely <em>because</em> it doesn’t try to scale beyond the number of people who can actually know each other.</p>
<p>The question is whether we can build something that stays in that sweet spot: big enough to be useful (a marketplace needs liquidity; an events board needs events), small enough that trust is personal. I can imagine this working at the neighbourhood scale—somewhere between 500 and 2,000 people, roughly the population of a few city blocks or a small suburb. That’s slightly bigger than a Dunbar number, but enough that you can feel like you are adding real value.</p>
<p>Note that at that scale the platform would absolutely include arsehats, scammers, trolls, and people with <a href="../notebook/personality_disorders.html">cluster B personality disorders</a>, so like, strap in for the problems of community organizing in general. Less overconfident folks might like to cap out at &lt;200, which is more a village or a church.</p>
</section>
</section>
<section id="start-with-an-itch" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="start-with-an-itch"><span class="header-section-number">2</span> Start with an itch</h2>
<p>The graveyard of alternative social platforms is vast, and nearly all of them died of the same cause: they asked people to join a new social network <em>for its own sake</em>, on the promise that it would be better. It turns out that “better” is not a compelling reason to abandon the place where our friends already are.</p>
<p>So don’t start with “a generic social network”. Start with something concretely useful—something that scratches an itch that the incumbents have stopped scratching.</p>
<section id="a-local-marketplace-that-doesnt-suck" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="a-local-marketplace-that-doesnt-suck"><span class="header-section-number">2.1</span> A local marketplace that doesn’t suck</h3>
<p>Facebook Marketplace is notoriously terrible, and it’s getting worse. It’s flooded with scam listings, drop-shipped junk, and interstate sellers who aren’t actually local. The search is bad. The messaging is bad. There’s no meaningful reputation system, so we’re constantly fielding queries from people who ghost us. And Meta’s content moderation is simultaneously too aggressive (flagging legitimate listings for inscrutable policy violations) and too permissive (doing nothing about obvious fraud).</p>
<p>A neighbourhood marketplace that verifies residents (even loosely—a postcode check, a vouching system from existing members) and maintains a local reputation score would be <em>immediately, obviously better</em> than Facebook Marketplace for the specific use case of buying and selling things to people near us.</p>
<p>Is this a good business model? No, almost certainly not, if “business model” means “thing that generates venture-scale returns.” Classified advertising was once a goldmine, but the margins have been competed away, and a neighbourhood platform will never have the volume to make advertising work.</p>
<p>But “business model” can also mean “thing that sustains itself.” I’ve <a href="../notebook/public_sphere_business_models.html">written elsewhere</a> about how the business models we use to fund communication shape the communication we get—advertising-funded platforms optimize for attention capture, not for community welfare. The alternative is direct funding: an incorporated association with modest membership fees—say $5–10/month per household, or $50–100/year—could cover hosting and basic development costs for a platform serving a few hundred to a couple of thousand users. That’s the friendly society funding model applied to digital infrastructure: we pay for it because it’s ours and it’s useful, not because someone is extracting profit from our attention.</p>
</section>
<section id="other-wedges" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="other-wedges"><span class="header-section-number">2.2</span> Other wedges</h3>
<p>That one seems like the most obvious wedge, but here are some other ones that I would personally like solved:</p>
<p><strong>A local events calendar.</strong> I’ve <a href="../notebook/diy_socials.html#for-irl-events">ranted about this before</a>—Facebook is a strong incumbent, and Facebook does it badly. A simple calendar of what’s happening in our area, maintained by the people who live there, would fill a real gap.</p>
<p><strong>A skills and services directory.</strong> Who in the neighbourhood can fix a tap? Who does tutoring? Who has a ute and will help you move for a slab of beer? This information exists socially—people know it about their friends—but it’s not discoverable. A lightweight directory, backed by reputation, makes it discoverable.</p>
<p><strong>A tool and resource library.</strong> Do I really need to own a pressure washer I use twice a year? Tool libraries exist in physical form (<a href="https://www.sharingmap.com.au/?ref=danmackinlay.name">Melbourne has several</a>), but a digital layer makes them more accessible, and borrowing stuff from real neighbours is more convenient than borrowing from a centralised library.</p>
<p><strong>A mutual aid coordination board.</strong> When the heatwave hits or the storm knocks out power, people need to know who needs help and who can provide it. This is an actual major goal I would imagine from all possible versions of the software, but it’s not one I lead with. It works best if the social infrastructure already exists <em>before</em> the crisis.</p>
<p>I think finding the right wedge is very important—we need to scratch an actual itch. But we need to choose <em>something</em> concrete, see if people use it, and let the platform grow in the directions the community pulls it. This is the “home-cooked meal” philosophy: we’re making this for specific people—ourselves, so we should ask each other what we’re hungry for.</p>
<p>What I’m hungry for is community, but that’s more of an ingredient than a meal.</p>
</section>
</section>
<section id="the-hard-problems-are-actually-social" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-hard-problems-are-actually-social"><span class="header-section-number">3</span> The hard problems are actually social</h2>
<p>I am a person who writes about <a href="../notebook/diy_socials.html">DIY social networks</a> and <a href="../notebook/fediverse.html">federated protocols</a> and <a href="../notebook/decnet.html">decentralized infrastructure</a>, so I am constitutionally inclined to think about social problems in terms of technology.</p>
<p>The social angle is the real hard bit, though. The technology for running a local social platform is <a href="../notebook/diy_socials.html">essentially solved</a>—there are mature, open-source options that would work out of the box.</p>
<p>The hard problems are:</p>
<p><strong>Cold start.</strong> How do we get the first hundred users? Network effects mean a platform is useless until it isn’t, and the transition happens fast—but getting <em>to</em> that transition requires sustained effort by a small group of very motivated people. For a neighbourhood platform, the bootstrap group is probably 10-20 households who know each other and commit to using the platform for three months regardless of whether it feels “worth it” yet. That’s the price of admission.</p>
<p><strong>Governance.</strong> Who decides the rules? Who moderates disputes? What happens when someone is creepy? <a href="../notebook/community_governance.html">Platform democracy</a> is a real field of research, and the work on <a href="https://www.belfercenter.org/publication/towards-platform-democracy-policymaking-beyond-corporate-ceos-and-partisan-pressure?ref=danmackinlay.name">mini-publics and deliberative processes</a> is directly relevant here. But at neighbourhood scale, governance can be simpler: a small elected committee, rotating terms, transparent decisions, published minutes. The friendly society governance model maps cleanly onto this.</p>
<p><strong>Attention and norms.</strong> A neighbourhood platform lives or dies on whether it feels good to use. Not “engaging” in the way that Facebook means (maximum time-on-site, maximum emotional arousal, maximum ad impressions), but <em>good</em>—useful, warm, not a time-sink. The <a href="https://www.humanetech.com/podcast?ref=danmackinlay.name">Center for Humane Technology</a>, the <a href="https://www.schoolofattention.org/attention-activism?ref=danmackinlay.name">Strother School of Radical Attention</a>, and a growing body of work on <a href="../notebook/attention_economy.html">the attention economy</a> all remind us that the design choices in a social platform shape the social behaviour that emerges on it. A platform that doesn’t have an engagement-maximizing algorithm doesn’t produce engagement-maximized behaviour. That’s a feature, but also that’s a major source of incentive off the table.</p>
<p><strong>Sustainability.</strong> What happens when the founding enthusiasts burn out? Community projects die when they depend on a small number of unpaid heroes. The membership-fee model helps—it means there’s a budget for someone to do administration, even if “someone” is a member who gets a fee waiver in exchange for 10 hours a month of maintenance.</p>
</section>
<section id="what-about-the-attention-economy" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="what-about-the-attention-economy"><span class="header-section-number">4</span> What about the attention economy?</h2>
<p>The platforms we’re trying to escape are <a href="../notebook/attention_economy.html">attention-extraction machines</a>. Their business model is to capture as much of our time and cognitive bandwidth as possible, because that’s what they sell to advertisers. This produces the behaviours we’ve all learned to recognize: infinite scroll, notification bombardment, algorithmic rage-bait, the manufactured sense of urgency about things that don’t matter.</p>
<p>A neighbourhood platform funded by membership fees has no reason to do any of this. There’s no advertiser to sell attention to. The incentive is reversed: the platform should help us get what we need and then <em>go do something else</em>. Check the marketplace, find the bookshelf, arrange pickup, done. Check the calendar, see there’s a park clean-up on Saturday, RSVP, done.</p>
<p>This is a radical proposition in the current climate. We are building a platform that wants us to spend as little time as possible on it. The value is not in <em>engagement</em> but in the social infrastructure that exists because the platform exists.</p>
</section>
<section id="technical-sketch" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="technical-sketch"><span class="header-section-number">5</span> Technical sketch</h2>
<p>I don’t want to go deep on technology here—that’s for a <a href="../notebook/local_social_platform_technical.html">companion post</a>—but a rough sketch helps ground the costs.</p>
<p>The simplest version of this is a web app (mobile-friendly, not a native app—app store review is a bottleneck we don’t need) running on a modest VPS. Off-the-shelf options include <a href="https://bonfirenetworks.org/?ref=danmackinlay.name">Bonfire</a> (a modular, federatable social platform built for community governance), <a href="https://www.discourse.org/?ref=danmackinlay.name">Discourse</a> (which despite being “forum software” is increasingly used as community infrastructure), or even a custom build on top of something like <a href="https://www.phoenixframework.org/?ref=danmackinlay.name">Elixir/Phoenix</a> or <a href="https://www.djangoproject.com/?ref=danmackinlay.name">Django</a> with real-time features.</p>
<p>Ballpark costs for a neighbourhood of ~1,000 active users:</p>
<ul>
<li><strong>Hosting:</strong> $20–50/month for a VPS (Hetzner, DigitalOcean, or a local Australian provider like VentraIP)</li>
<li><strong>Domain and email:</strong> $50–100/year</li>
<li><strong>Development:</strong> This is the wild card. An initial build using AI coding tools and volunteer labour could be near-zero in cash terms, or a few thousand dollars for a contractor to set up and customize a Bonfire or Discourse instance.</li>
<li><strong>Ongoing maintenance:</strong> 5–10 hours/month of volunteer or modestly-compensated time</li>
</ul>
<p>At $5/month per household and 200 participating households, that’s $1,000/month—more than enough to cover hosting and a small maintenance stipend, with surplus for improvements.</p>
<p>The federation question is interesting: platforms like Bonfire speak <a href="../notebook/fediverse.html">ActivityPub</a>, which means a neighbourhood instance could connect to the wider Fediverse (Mastodon, etc.) for broader discovery while keeping local governance local. Whether this is desirable depends on whether the community wants to be a walled garden or a node in a network. I’d bet on starting closed and opening up deliberately, rather than starting open and trying to close down later.</p>
</section>
<section id="how-this-connects-to-the-other-projects" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="how-this-connects-to-the-other-projects"><span class="header-section-number">6</span> How this connects to the other projects</h2>
<p>This platform is the social layer that the other mutual-aid projects need.</p>
<p>The <a href="../notebook/neo_friendly_societies.html">neo-friendly society</a> needs a communication channel that isn’t Facebook, isn’t email, and isn’t yet another WhatsApp group. It needs a place where members can discuss proposals, vote on decisions, and coordinate activities. A local social platform provides this.</p>
<p>The <a href="../notebook/aus_sovereign_llm.html">compute collective</a> needs a community of users. The local platform’s members are the natural user base, and the AI infrastructure can in turn power features of the platform—content moderation assistance, search, translation for multilingual neighbourhoods, smart matching for the marketplace.</p>
<p>Together, these three projects form something like a stack: financial resilience (the friendly society), computational sovereignty (the compute collective), and social infrastructure (the local platform). Each is useful on its own; together, they’re the skeleton of a genuinely self-reliant local community.</p>
</section>
<section id="prior-art" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="prior-art"><span class="header-section-number">7</span> Prior art</h2>
<section id="platform-cooperatives" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="platform-cooperatives"><span class="header-section-number">7.1</span> Platform cooperatives</h3>
<p>Nathan Schneider’s work at the <a href="https://platform.coop/?ref=danmackinlay.name">Platform Cooperativism Consortium</a> is the most developed theory of community-owned digital platforms. His argument is that community ownership won’t emerge from open protocols alone—you need to deliberately invest in ownership ecosystems with real governance structures. The Platform Co-op School has highlighted <a href="https://www.hylo.com/?ref=danmackinlay.name">Hylo</a> as a working example of community-owned social infrastructure.</p>
</section>
<section id="broodfonds-style-social-models" class="level3" data-number="7.2">
<h3 data-number="7.2" class="anchored" data-anchor-id="broodfonds-style-social-models"><span class="header-section-number">7.2</span> Broodfonds-style social models</h3>
<p>The <a href="https://en.wikipedia.org/wiki/Broodfonds?ref=danmackinlay.name">Broodfonds</a> model from the Netherlands, discussed in the <a href="../notebook/neo_friendly_societies.html">friendly society post</a>, is relevant here too—not for its financial mechanics, but for its social architecture. Groups of 25–50 people, self-selected, with shared obligations. The social platform is the digital home for groups like this.</p>
</section>
<section id="radicalxchange-governance" class="level3" data-number="7.3">
<h3 data-number="7.3" class="anchored" data-anchor-id="radicalxchange-governance"><span class="header-section-number">7.3</span> RadicalxChange governance</h3>
<p>The <a href="https://www.radicalxchange.org/?ref=danmackinlay.name">RadicalxChange</a> movement—associated with Glen Weyl, Audrey Tang, and the <em>Plurality</em> project—offers interesting governance mechanisms that could apply to a community platform: quadratic funding for deciding which features to build, data dignity frameworks for how member data is handled, Harberger-style mechanisms for allocating scarce resources (like prominent placement on the marketplace). These are more speculative, but they point toward forms of community governance that go beyond simple majority voting.</p>
</section>
<section id="existing-local-platforms" class="level3" data-number="7.4">
<h3 data-number="7.4" class="anchored" data-anchor-id="existing-local-platforms"><span class="header-section-number">7.4</span> Existing local platforms</h3>
<p>There are precedents, though none have broken out. <a href="https://nextdoor.com/?ref=danmackinlay.name">Nextdoor</a> started with a local-trust model and then seems to have enshittified in exactly the way Doctorow’s theory predicted. Various attempts at “local social” have launched and died—but the ones that survived longest were the ones attached to a concrete use case (marketplace, events) rather than generic “local social networking.” The lesson is: wedge in with utility, grow into community.</p>
</section>
</section>
<section id="whats-still-missing-from-the-literature" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="whats-still-missing-from-the-literature"><span class="header-section-number">8</span> What’s still missing from the literature</h2>
<p>Geek time! I would also like to research how and why people join these things, and learn how to make local platform development easier. How do users actually migrate from an enshittified platform to a community alternative when network effects penalize first movers? This is a coordination game with heterogeneous switching costs, and there’s almost no formal modelling of it. The RadicalxChange <em>Plurality</em> book gestures at this but doesn’t model it. If someone wants a research problem that matters, hit me up.</p>
</section>
<section id="open-questions" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="open-questions"><span class="header-section-number">9</span> Open questions</h2>
<ul>
<li>What’s the right first wedge for a specific neighbourhood? Marketplace? Events? Something else? This probably varies by community and the only way to find out is to ask.</li>
<li>If we want to be local, how do we verify “local” without being creepy about it? Postcode checks? Vouching? Physical meetups?</li>
<li>What’s the right governance structure for a platform that’s too big for consensus but too small for representative democracy?</li>
<li>How do we handle the transition when someone abuses the platform—doxxing, harassment, commercial spam? The norms for a 500-person neighbourhood platform are different from the norms for Twitter.</li>
<li>All the questions about legal liability. What if groomers use the platform? What if someone posts a defamatory listing? What if someone posts a listing for a stolen item? How do we handle takedown requests? Who bears the risk?</li>
<li>Is federation (<a href="../notebook/fediverse.html">ActivityPub</a>, etc.) an asset or a liability at this scale? Does connecting to the global Fediverse dilute the local trust that makes the platform valuable?</li>
<li>Can this platform model be replicated—published as a template, the way the <a href="../notebook/neo_friendly_societies.html#replicating-to-other-mutual-aid-groups">friendly society template</a> is meant to be?</li>
</ul>
<p>As with the other posts in this series: if you know about any of this, if you’ve tried something like this, especially if you’re in a shared geographical (Melbourne) or interest community in Melbourne and want to help build it, I’d <a href="../contact.html">love to hear from you</a>.</p>
<p>The technology is the easy part. The institutional design is the interesting part. The actual community is the hard part. Let’s start.</p>
</section>
<section id="incoming" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="incoming"><span class="header-section-number">10</span> Incoming</h2>
<ul>
<li>New Public <a href="https://newpublic.substack.com/p/exploring-a-new-social-product-for?ref=danmackinlay.name">🧭🏘️📲 Exploring a new social product for local communities</a></li>
<li><a href="https://idealistscollective.org/join?ref=danmackinlay.name">Join Us — The Idealists Collective</a></li>
<li><a href="https://docs.google.com/document/d/17Y4JxFnutA1iMN_317ftuAoP_FoJw4ldNTd1ol_aH14/edit?tab=t.0&amp;ref=danmackinlay.name#heading=h.gmdf8b2ng7lm">A widely adopted social media platform for healthy communities - Google Docs</a></li>
<li><a href="https://www.versobooks.com/en-gb/products/2839-own-this?ref=danmackinlay.name">Own This!: How Platform Cooperatives Help Workers Build a Democratic Internet | Verso Books</a></li>
</ul>


</section>

 ]]></description>
  <category>communicating</category>
  <category>community project</category>
  <category>cooperation</category>
  <category>culture</category>
  <category>DIY</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>institutions</category>
  <category>networks</category>
  <category>P2P</category>
  <category>resilient tech</category>
  <category>straya</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/local_social_platforms.html</guid>
  <pubDate>Wed, 25 Mar 2026 01:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/friednly_market_RP-P-1979-116.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Quis computat?</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/who_computes.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Surely this analysis has been done before in the bowels of <a href="https://www.lesswrong.com/?ref=danmackinlay.name">LessWrong</a>. I gave up searching because it was too irritating trying to disambiguate the terms <em>survival</em> and <em>hazard</em> in the technical mathematical sense that I needed, against the more colloquial sense that they are used in AI safety discourse. Feel free to point me to prior work in the comments.</p>
<p>AI usage: much! So many diagrams and calculations were assisted by AI. There are likely to be some residual AI text chunks in this prose but it should be mostly mine by now.</p>
</div>
</div>
</div>
<div id="fig-werelt" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-werelt-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/werelt_RP-P-BI-2384.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/werelt_RP-P-BI-2384.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-werelt-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>I recently had a discussion where the contested point was whether getting more compute was, <em>ceteris paribus</em>, a “good” or “bad” thing for AI safety research. What follows is my attempt to formalize goodness and badness for AI dooms in terms of hazard models. This seems to me a nice way of making our assumptions and disagreements explicit, and scenario-independent besides. Slaughterbots? Matrix-style VR capture? Engineered plagues? Meatballs falling from the sky? Doesn’t matter. If we disagree about what ASI disasters or utopias look like, but agree that compute is a forcing term, then we can still talk about things like pauses, slowdowns, and accelerations without sweating the details.</p>
<p>The model setup is as follows. I assume two things can happen — an AI catastrophe (“doom”), or a breakthrough that guarantees alignment (“deliverance”). Both become more likely as more compute accumulates in the world, but they draw on different, potentially overlapping, pools of compute. The question becomes: given a trajectory of total compute and a policy for splitting it between capability and safety, what is the probability that doom arrives before deliverance?</p>
<p>The motivation for expressing everything in terms of <em>compute</em> as the forcing term comes from thinking about <a href="../notebook/ai_economics_of_cognition.html">the economics of cognition</a>: compute <a href="https://gwern.net/scaling-hypothesis?ref=danmackinlay.name">certainly seems to be the fundamental currency of intelligence</a>, so we should be able to express both the problem and the solution in terms of how compute is allocated.</p>
<p>The machinery I use is <a href="../notebook/survival_analysis.html">survival analysis</a> of <a href="https://en.wikipedia.org/wiki/Competing_risks?ref=danmackinlay.name">competing risks</a> — the branch of statistics built for this kind of “which stochastic event comes first?” problem. I am always faintly annoyed when people <em>don’t</em> use this formalism, which is simple and elegant for reasoning about the timing of stochastic events, and really well developed by the insurance industry; it is generally the obviously correct baseline against which any other model should need to prove itself.</p>
<div class="callout callout-style-simple callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Translation guide
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you haven’t seen <a href="https://en.wikipedia.org/wiki/Survival_analysis?ref=danmackinlay.name">survival analysis</a> or <a href="../notebook/point_processes.html">point processes</a> before, the key concepts are:</p>
<ul>
<li>A <em>hazard rate</em> is the instantaneous probability per unit time that an event fires <em>right now</em>, given that it hasn’t fired yet.</li>
<li>A <em>survival function</em> <img src="https://latex.codecogs.com/png.latex?S(t)"> is the probability that <em>nothing has happened yet</em> by time <img src="https://latex.codecogs.com/png.latex?t">. It starts at 1 and decays toward 0 as hazard accumulates.</li>
<li><a href="https://en.wikipedia.org/wiki/Competing_risks?ref=danmackinlay.name"><em>Competing risks</em></a> means two or more events are racing to fire first. We observe whichever wins; the loser is never realized.</li>
</ul>
<p>If you are coming from LessWrong, some terms might be handy: the safety fraction <img src="https://latex.codecogs.com/png.latex?%5Calpha"> is a compute-denominated cousin of the “<a href="https://www.lesswrong.com/tag/alignment-tax?ref=danmackinlay.name">alignment tax</a>”, which usually means the capability cost of aligning one system rather than a fraction of the world’s compute, and “<a href="https://www.lesswrong.com/tag/p-doom?ref=danmackinlay.name">P (doom)</a>” is here derived from a competing-risks integral.</p>
</div>
</div>
<div id="abad7c0b" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> plotly.graph_objects <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> go</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> plotly.subplots <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> make_subplots</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> plotly.io <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pio</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy.integrate <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cumulative_trapezoid</span>
<span id="cb1-6"></span>
<span id="cb1-7">pio.renderers.default <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plotly_mimetype+notebook_connected"</span></span>
<span id="cb1-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> livingthing.plotly_style <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> set_livingthing_style</span>
<span id="cb1-9">set_livingthing_style()</span>
<span id="cb1-10"></span>
<span id="cb1-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Shared palette</span></span>
<span id="cb1-12">C_DOOM <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#c0392b'</span></span>
<span id="cb1-13">C_SALV <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#2471a3'</span></span>
<span id="cb1-14">C_SURV <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#27ae60'</span></span>
<span id="cb1-15">C_RUIN <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#8e44ad'</span></span>
<span id="cb1-16">C_NEUT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#7f8c8d'</span></span>
<span id="cb1-17"></span>
<span id="cb1-18">LAYOUT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(</span>
<span id="cb1-19">    paper_bgcolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rgba(0,0,0,0)'</span>,</span>
<span id="cb1-20">    plot_bgcolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rgba(0,0,0,0)'</span>,</span>
<span id="cb1-21">    hovermode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x unified'</span>,</span>
<span id="cb1-22">    legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(bgcolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rgba(255,255,255,0.5)'</span>, bordercolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#ccc'</span>, borderwidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb1-23">)</span>
<span id="cb1-24"></span>
<span id="cb1-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Response-function zoo</span></span>
<span id="cb1-26"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> g_lin(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.016</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x</span>
<span id="cb1-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> h_lin(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x</span>
<span id="cb1-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> g_pess(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb1-29"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> h_pess(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(x)</span>
<span id="cb1-30"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> g_opt(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sqrt(x)</span>
<span id="cb1-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> h_opt(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb1-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sigmoid(x, k, x0, amp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> amp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> x0)))</span>
<span id="cb1-33">X0_DOOM, X0_SAFE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.0</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sigmoid centres: doom's in capability spend, safety's in safety spend</span></span>
<span id="cb1-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> g_sig(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> sigmoid(x, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, X0_DOOM, amp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)</span>
<span id="cb1-35"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> h_sig(x): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> sigmoid(x, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, X0_SAFE, amp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)</span></code></pre></div></div>
</details>
</div>
<section id="compute-trajectories" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="compute-trajectories"><span class="header-section-number">1</span> Compute trajectories</h2>
<p>I distinguish between the <em>rate</em> at which compute is performed and the aggregate of computation that has been done: throughput versus spend.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?c(t)"> denote the compute <em>throughput</em> — the FLOP/s of AI-related computation happening in the world at time <img src="https://latex.codecogs.com/png.latex?t%20%5Cgeq%200">. This represents capacity: hardware, data centres, and investment. It is non-negative and right-continuous. I make no distinction between training and inference — <img src="https://latex.codecogs.com/png.latex?c(t)"> is <em>unstructured</em> compute, all of it. The more of it running, the more things are happening; I don’t model the internal structure of the workloads for this level of analysis.</p>
<p>The <em>compute spend</em> — total FLOPs performed by time <img src="https://latex.codecogs.com/png.latex?t"> — is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BC%7D(t)%20=%20%5Cint_0%5Et%20c(u)%5C,%20du.%0A"></p>
<p>A fraction <img src="https://latex.codecogs.com/png.latex?%5Calpha(t)%20%5Cin%20%5B0,1%5D"> of the throughput goes to safety and the rest to capability:</p>
<ul>
<li><em>Capability throughput</em> <img src="https://latex.codecogs.com/png.latex?c_c(t)%20=%20(1-%5Calpha(t))%5C,%20c(t)">: compute aimed at expanding what AI systems can do.</li>
<li><em>Safety throughput</em> <img src="https://latex.codecogs.com/png.latex?c_s(t)%20=%20%5Calpha(t)%5C,%20c(t)">: compute focused on ensuring AI systems behave safely.</li>
</ul>
<p>The spend in each pool is then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BC%7D_c(t)%20=%20%5Cint_0%5Et%20(1-%5Calpha(u))%5C,%20c(u)%5C,%20du,%20%5Cqquad%20%5Cmathcal%7BC%7D_s(t)%20=%20%5Cint_0%5Et%20%5Calpha(u)%5C,%20c(u)%5C,%20du,%0A"></p>
<p>with <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_c(t)%20+%20%5Cmathcal%7BC%7D_s(t)%20=%20%5Cmathcal%7BC%7D(t)"> at all times. To calibrate this to the actually-existing world, where a lot of compute has already been spent, we would start the clock mid-history with legacy spends <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_c(0)%20=%20%5Cmathcal%7BC%7D_%7Bc,0%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_s(0)%20=%20%5Cmathcal%7BC%7D_%7Bs,0%7D"> added to the integrals above. For simplicity I do not do that yet, but I do return to it later.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>The spend/throughput distinction seems important because we, or at least I, imagine that capabilities <em>persist</em>. We can’t necessarily “un-train” a frontier model by switching off the data centre; the weights, the papers, the algorithmic insights are already in the world, at least while DeepSeek et al are out there reverse-engineering the western labs and adding their own tricks. Safety progress persists too, ideally — proved theorems, verified architectures, and alignment techniques don’t evaporate (unless these also languish in Anthropic’s file drawers). So the timing of doom should depend on the capability spend <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_c(t)">, not the instantaneous throughput <img src="https://latex.codecogs.com/png.latex?c_c(t)">. Throughput sets how fast the spend grows; the spend sets the hazard. Doubling <img src="https://latex.codecogs.com/png.latex?c(t)"> doesn’t double the hazard at time <img src="https://latex.codecogs.com/png.latex?t">, but it does reach any given spend sooner. Empirically we know <span class="citation" data-cites="Sweeney2000Bathtub">(Sweeney and Sterman 2000)</span> that people have <em>really bad</em> intuitions about such stock-and-flow systems.</p>
<p><strong>Update 2026-10-06</strong>: Apparently there is empirical support for this pattern of growth from the Wright curve <span class="citation" data-cites="Nagy2013Statistical">(Nagy et al. 2013)</span>. I suspect it also occurs in other <a href="../notebook/learning_curve.html">learning curve models</a>, but I am done for now.</p>
</div>
</div>
</div>
<p>The function <img src="https://latex.codecogs.com/png.latex?%5Calpha(%5Ccdot)"> is the <em>allocation</em> control, which a civilization sets by doing whatever governance is necessary (tax incentives, setting research agendas, etc.). The trajectory <img src="https://latex.codecogs.com/png.latex?c(t)"> is the <em>pacing</em> control: exogenous in the base model, but it is what compute governance acts on — licensing or reporting thresholds for large training runs, chip export controls, caps on data-centre build-out, nuking compute-heavy facilities. So the model has two controls, allocation and pacing, and the pause is an intervention on the second rather than a third control. There is a lot of texture in those choices, and some deep governance problems, which I will skate over here while hoping that my colleagues in the Credible Theory of Change department have this one in the bag.</p>
</section>
<section id="two-competing-events" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="two-competing-events"><span class="header-section-number">2</span> Two competing events</h2>
<p>I model doom and deliverance as the first arrivals of <a href="../notebook/point_processes.html">inhomogeneous point processes</a> — random events whose chance of firing at any moment depends on how much compute we have spent so far:</p>
<dl>
<dt><em>Doom</em> (<img src="https://latex.codecogs.com/png.latex?T_d">)</dt>
<dd>
An X-risk catastrophe happens. This is an irreversible absorbing state, because everyone is dead and rendered into computronium or paperclips or whatever.
</dd>
<dt><em>Deliverance</em> (<img src="https://latex.codecogs.com/png.latex?T_s">)</dt>
<dd>
We achieve guaranteed alignment — a state after which X-risk from AI is effectively zero, and some kind of end-game high score is unlocked.
</dd>
</dl>
<p>Each event has a <em>latent</em> arrival time — <img src="https://latex.codecogs.com/png.latex?T_d"> and <img src="https://latex.codecogs.com/png.latex?T_s"> are the times at which doom and deliverance <em>would</em> fire if nothing else intervened. But the race ends at <img src="https://latex.codecogs.com/png.latex?T%5E*%20=%20%5Cmin(T_d,%20T_s)">: one of them fires first, or neither fires within any horizon we care about. When I write “<img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)">” below, I mean the <em>outcome</em> “doom fires first” — i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?P(T_d%20%3C%20T_s)"> — not the marginal probability that the doom process would eventually fire in isolation. These coincide only if no deliverance is possible.</p>
<p>Each event has a hazard rate (instantaneous arrival intensity given that neither event has yet occurred — i.e.&nbsp;while we are still in <em>limbo</em>) that depends on the spend in its respective pool:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda_d(t)%20=%20g%5Cbigl(%5Cmathcal%7BC%7D_c(t)%5Cbigr),%20%5Cqquad%20%5Clambda_s(t)%20=%20h%5Cbigl(%5Cmathcal%7BC%7D_s(t)%5Cbigr),%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?g,%20h%20:%20%5B0,%20%5Cinfty)%20%5Cto%20%5B0,%20%5Cinfty)"> are monotonically non-decreasing, with <img src="https://latex.codecogs.com/png.latex?g(0)%20=%200"> and <img src="https://latex.codecogs.com/png.latex?h(0)%20=%200">, or near enough in the sigmoidal case below. Monotonicity means that more capability spend never lowers the catastrophe hazard, and more safety spend never lowers the alignment-breakthrough hazard.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Monotonicity is agnostic about the <em>shape</em> of the response — linear, concave, convex, sigmoidal. That shape, for <img src="https://latex.codecogs.com/png.latex?g"> and for <img src="https://latex.codecogs.com/png.latex?h">, is where I think the non-trivial disagreements in AI safety discourse live.</p>
</div>
</div>
</div>
<div id="cell-fig-hazard-shapes" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">300</span>)</span>
<span id="cb2-2">scenarios <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb2-3">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Linear"</span>,              g_lin(x),  h_lin(x)),</span>
<span id="cb2-4">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Convex-doom (g convex, h concave)"</span>, g_pess(x), h_pess(x)),</span>
<span id="cb2-5">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Concave-doom (g concave, h convex)"</span>, g_opt(x),  h_opt(x)),</span>
<span id="cb2-6">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Overhang (sigmoidal)"</span>,  g_sig(x),  h_sig(x)),</span>
<span id="cb2-7">]</span>
<span id="cb2-8"></span>
<span id="cb2-9">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> go.Figure()</span>
<span id="cb2-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, gv, hv <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> scenarios:</span>
<span id="cb2-11">    vis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> name.startswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Convex-doom"</span>)</span>
<span id="cb2-12">    fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>gv, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"g(x)  doom"</span>, line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_DOOM, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>),</span>
<span id="cb2-13">                             visible<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vis, hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.2f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>))</span>
<span id="cb2-14">    fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>hv, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"h(x)  safety"</span>, line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_SALV, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>),</span>
<span id="cb2-15">                             visible<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>vis, hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.2f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>))</span>
<span id="cb2-16"></span>
<span id="cb2-17">buttons <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, (name, _, _) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(scenarios):</span>
<span id="cb2-19">    vis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(scenarios))</span>
<span id="cb2-20">    vis[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> vis[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb2-21">    buttons.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>name, method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"update"</span>, args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"visible"</span>: vis}]))</span>
<span id="cb2-22"></span>
<span id="cb2-23">fig.update_layout(</span>
<span id="cb2-24">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>LAYOUT,</span>
<span id="cb2-25">    updatemenus<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"buttons"</span>, direction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"down"</span>, x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, xanchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span>, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb2-26">                      buttons<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>buttons, bgcolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>)],</span>
<span id="cb2-27">    xaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Compute spend 𝒞"</span>,</span>
<span id="cb2-28">    yaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hazard rate"</span>,</span>
<span id="cb2-29">    height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">420</span>,</span>
<span id="cb2-30">)</span>
<span id="cb2-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The buttons occupy the top-right corner, where the legend would otherwise sit;</span></span>
<span id="cb2-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># every scenario's curves rise from the origin, so the top-left is empty.</span></span>
<span id="cb2-33">fig.update_layout(legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.98</span>, xanchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span>, yanchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>))</span>
<span id="cb2-34">fig.show()</span></code></pre></div></div>
</details>
<div id="fig-hazard-shapes" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-hazard-shapes-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div style="height:420px; width:100%;">            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-svg.min.js"></script><script>if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script>window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-4.0.0.min.js" integrity="sha256-FEYfO0yRyLtZCpnW0Dw/0DHKQO7Afrq3ml4+rBB818o=" crossorigin="anonymous"></script>                <div id="b1cc5a42-3878-4930-b622-368fe6088753" class="plotly-graph-div" style="height:100%; width:100%;"></div>            <script>                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("b1cc5a42-3878-4930-b622-368fe6088753")) {                    Plotly.newPlot(                        "b1cc5a42-3878-4930-b622-368fe6088753",                        [{"hovertemplate":"%{y:.2f}","line":{"color":"#c0392b","width":3},"name":"g(x)  doom","visible":false,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"AAAAAAAAAAD5CB4h44hBP\u002fkIHiHjiFE\u002fdQ2tsVRNWj\u002f5CB4h44hhPzeLZekb62U\u002fdQ2tsVRNaj+zj\u002fR5ja9uP\u002fkIHiHjiHE\u002fGMpBhf+5cz83i2XpG+t1P1ZMiU04HHg\u002fdQ2tsVRNej+VztAVcX58P7OP9HmNr34\u002faigM71RwgD\u002f5CB4h44iBP4jpL1NxoYI\u002fGMpBhf+5gz+oqlO3jdKEPzeLZekb64U\u002fx2t3G6oDhz9WTIlNOByIP+Ysm3\u002fGNIk\u002fdQ2tsVRNij8F7r7j4mWLP5XO0BVxfow\u002fJK\u002fiR\u002f+WjT+zj\u002fR5ja+OP0NwBqwbyI8\u002faigM71RwkD+xGBUInPyQP\u002fkIHiHjiJE\u002fQfkmOioVkj+I6S9TcaGSP9DZOGy4LZM\u002fGMpBhf+5kz9fukqeRkaUP6iqU7eN0pQ\u002f8Jpc0NRelT83i2XpG+uVP397bgJjd5Y\u002fx2t3G6oDlz8OXIA08Y+XP1ZMiU04HJg\u002fnjySZn+omD\u002fmLJt\u002fxjSZPy0dpJgNwZk\u002fdQ2tsVRNmj++\u002fbXKm9maPwXuvuPiZZs\u002fTd7H\u002fCnymz+VztAVcX6cP9y+2S64Cp0\u002fJK\u002fiR\u002f+WnT9sn+tgRiOeP7OP9HmNr54\u002f+3\u002f9ktQ7nz9DcAasG8ifP0awh2IxKqA\u002faigM71RwoD+NoJB7eLagP7EYFQic\u002fKA\u002f1ZCZlL9CoT\u002f5CB4h44ihPx2Boq0Gz6E\u002fQfkmOioVoj9kcavGTVuiP4jpL1NxoaI\u002frGG035Tnoj\u002fQ2ThsuC2jP\u002fRRvfjbc6M\u002fGMpBhf+5oz88QsYRIwCkP1+6Sp5GRqQ\u002fhDLPKmqMpD+oqlO3jdKkP8wi2EOxGKU\u002f8Jpc0NRepT8UE+Fc+KSlPzeLZekb66U\u002fWwPqdT8xpj9\u002fe24CY3emP6Pz8o6GvaY\u002fx2t3G6oDpz\u002fr4\u002funzUmnPw5cgDTxj6c\u002fMtQEwRTWpz9WTIlNOByoP3rEDdpbYqg\u002fnjySZn+oqD\u002fCtBbzou6oP+Ysm3\u002fGNKk\u002fCaUfDOp6qT8tHaSYDcGpP1GVKCUxB6o\u002fdQ2tsVRNqj+ahTE+eJOqP779tcqb2ao\u002f4XU6V78fqz8F7r7j4mWrPylmQ3AGrKs\u002fTd7H\u002fCnyqz9xVkyJTTisP5XO0BVxfqw\u002fuEZVopTErD\u002fcvtkuuAqtPwA3XrvbUK0\u002fJK\u002fiR\u002f+WrT9IJ2fUIt2tP2yf62BGI64\u002fkBdw7Wlprj+zj\u002fR5ja+uP9cHeQax9a4\u002f+3\u002f9ktQ7rz8f+IEf+IGvP0NwBqwbyK8\u002fM3RFnB8HsD9GsIdiMSqwP1jsyShDTbA\u002faigM71RwsD97ZE61ZpOwP42gkHt4trA\u002fn9zSQYrZsD+xGBUInPywP8NUV86tH7E\u002f1ZCZlL9CsT\u002fnzNta0WWxP\u002fkIHiHjiLE\u002fC0Vg5\u002fSrsT8dgaKtBs+xPy+95HMY8rE\u002fQfkmOioVsj9TNWkAPDiyP2Rxq8ZNW7I\u002fd63tjF9+sj+I6S9TcaGyP5slchmDxLI\u002frGG035Tnsj+\u002fnfalpgqzP9DZOGy4LbM\u002f4hV7MspQsz\u002f0Ub3423OzPwaO\u002f77tlrM\u002fGMpBhf+5sz8qBoRLEd2zPzxCxhEjALQ\u002fTn4I2DQjtD9fukqeRka0P3L2jGRYabQ\u002fhDLPKmqMtD+WbhHxe6+0P6iqU7eN0rQ\u002fueaVfZ\u002f1tD\u002fMIthDsRi1P91eGgrDO7U\u002f8Jpc0NRetT8B156W5oG1PxQT4Vz4pLU\u002fJU8jIwrItT83i2XpG+u1P0nHp68tDrY\u002fWwPqdT8xtj9tPyw8UVS2P397bgJjd7Y\u002fkbewyHSatj+j8\u002fKOhr22P7QvNVWY4LY\u002fx2t3G6oDtz\u002fYp7nhuya3P+vj+6fNSbc\u002f\u002fR8+bt9stz8OXIA08Y+3PyGYwvoCs7c\u002fMtQEwRTWtz9FEEeHJvm3P1ZMiU04HLg\u002faYjLE0o\u002fuD96xA3aW2K4P4wAUKBthbg\u002fnjySZn+ouD+weNQskcu4P8K0FvOi7rg\u002f1PBYubQRuT\u002fmLJt\u002fxjS5P\u002fho3UXYV7k\u002fCaUfDOp6uT8c4WHS+525Py0dpJgNwbk\u002fQFnmXh\u002fkuT9RlSglMQe6P2PRautCKro\u002fdQ2tsVRNuj+HSe93ZnC6P5qFMT54k7o\u002fq8FzBIq2uj++\u002fbXKm9m6P885+JCt\u002fLo\u002f4XU6V78fuz\u002fzsXwd0UK7PwXuvuPiZbs\u002fFyoBqvSIuz8pZkNwBqy7PzuihTYYz7s\u002fTd7H\u002fCnyuz9eGgrDOxW8P3FWTIlNOLw\u002fgpKOT19bvD+VztAVcX68P6YKE9yCobw\u002fuEZVopTEvD\u002fKgpdopue8P9y+2S64Cr0\u002f7vob9cktvT8AN16721C9PxNzoIHtc70\u002fJK\u002fiR\u002f+WvT826yQOEbq9P0gnZ9Qi3b0\u002fWmOpmjQAvj9sn+tgRiO+P37bLSdYRr4\u002fkBdw7Wlpvj+iU7Kze4y+P7OP9HmNr74\u002fxss2QJ\u002fSvj\u002fXB3kGsfW+P+pDu8zCGL8\u002f+3\u002f9ktQ7vz8NvD9Z5l6\u002fPx\u002f4gR\u002f4gb8\u002fMTTE5Qmlvz9DcAasG8i\u002fP1WsSHIt678\u002fM3RFnB8HwD89kmZ\u002fqBjAP0awh2IxKsA\u002fTs6oRbo7wD9Y7MkoQ03AP2AK6wvMXsA\u002faigM71RwwD9zRi3S3YHAP3tkTrVmk8A\u002fhIJvmO+kwD+NoJB7eLbAP5e+sV4ByMA\u002fn9zSQYrZwD+o+vMkE+vAP7EYFQic\u002fMA\u002fujY26yQOwT\u002fDVFfOrR\u002fBP8xyeLE2McE\u002f1ZCZlL9CwT\u002ferrp3SFTBP+fM21rRZcE\u002f8Or8PVp3wT\u002f5CB4h44jBPwInPwRsmsE\u002fC0Vg5\u002fSrwT8UY4HKfb3BPx2Boq0Gz8E\u002fJp\u002fDkI\u002fgwT8vveRzGPLBPzfbBVehA8I\u002fQfkmOioVwj9KF0gdsybCP1M1aQA8OMI\u002fW1OK48RJwj9kcavGTVvCP26PzKnWbMI\u002fd63tjF9+wj9\u002fyw5w6I\u002fCP4jpL1NxocI\u002fkgdRNvqywj+bJXIZg8TCP6NDk\u002fwL1sI\u002frGG035Tnwj+1f9XCHfnCP7+d9qWmCsM\u002fyLsXiS8cwz\u002fQ2ThsuC3DP9n3WU9BP8M\u002f4hV7MspQwz\u002fsM5wVU2LDP\u002fRRvfjbc8M\u002f\u002fW\u002fe22SFwz8Gjv++7ZbDPw+sIKJ2qMM\u002fGMpBhf+5wz8h6GJoiMvDPyoGhEsR3cM\u002fMySlLpruwz88QsYRIwDEP0Vg5\u002fSrEcQ\u002fTn4I2DQjxD9XnCm7vTTEP1+6Sp5GRsQ\u002fadhrgc9XxD9y9oxkWGnEP3sUrkfhesQ\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#2471a3","width":3},"name":"h(x)  safety","visible":false,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"AAAAAAAAAAA3i2XpG+s1PzeLZekb60U\u002faSgM71RwUD83i2XpG+tVPwXuvuPiZVs\u002faSgM71RwYD\u002fQ2ThsuC1jPzeLZekb62U\u002fnjySZn+oaD8F7r7j4mVrP2yf62BGI24\u002faSgM71RwcD8dgaKtBs9xP9DZOGy4LXM\u002fhDLPKmqMdD83i2XpG+t1P+rj+6fNSXc\u002fnjySZn+oeD9SlSglMQd6PwXuvuPiZXs\u002fuEZVopTEfD9sn+tgRiN+Px\u002f4gR\u002f4gX8\u002faSgM71RwgD\u002fDVFfOrR+BPx2Boq0Gz4E\u002fd63tjF9+gj\u002fQ2ThsuC2DPyoGhEsR3YM\u002fhDLPKmqMhD\u002feXhoKwzuFPzeLZekb64U\u002fkbewyHSahj\u002fq4\u002funzUmHP0QQR4cm+Yc\u002fnjySZn+oiD\u002f3aN1F2FeJP1KVKCUxB4o\u002frMFzBIq2ij8F7r7j4mWLP18aCsM7FYw\u002fuEZVopTEjD8Sc6CB7XONP2yf62BGI44\u002fxcs2QJ\u002fSjj8f+IEf+IGPPzySZn+oGJA\u002faSgM71RwkD+WvrFeAciQP8NUV86tH5E\u002f8Or8PVp3kT8dgaKtBs+RP0oXSB2zJpI\u002fd63tjF9+kj+jQ5P8C9aSP9DZOGy4LZM\u002f\u002fW\u002fe22SFkz8qBoRLEd2TP1ecKbu9NJQ\u002fhDLPKmqMlD+xyHSaFuSUP95eGgrDO5U\u002fCvW\u002feW+TlT83i2XpG+uVP2QhC1nIQpY\u002fkbewyHSalj++TVY4IfKWP+rj+6fNSZc\u002fF3qhF3qhlz9EEEeHJvmXP3Gm7PbSUJg\u002fnjySZn+omD\u002fK0jfWKwCZP\u002fdo3UXYV5k\u002fJf+CtYSvmT9SlSglMQeaP38rzpTdXpo\u002frMFzBIq2mj\u002fYVxl0Ng6bPwXuvuPiZZs\u002fMoRkU4+9mz9fGgrDOxWcP4ywrzLobJw\u002fuEZVopTEnD\u002fl3PoRQRydPxJzoIHtc50\u002fPwlG8ZnLnT9sn+tgRiOeP5g1kdDyep4\u002fxcs2QJ\u002fSnj\u002fyYdyvSyqfPx\u002f4gR\u002f4gZ8\u002fTI4nj6TZnz88kmZ\u002fqBigP1NdObd+RKA\u002faSgM71RwoD+A894mK5ygP5a+sV4ByKA\u002frYmEltfzoD\u002fDVFfOrR+hP9ofKgaES6E\u002f8Or8PVp3oT8Hts91MKOhPx2Boq0Gz6E\u002fM0x15dz6oT9KF0gdsyaiP2DiGlWJUqI\u002fd63tjF9+oj+NeMDENaqiP6NDk\u002fwL1qI\u002fug5mNOIBoz\u002fQ2ThsuC2jP+ekC6SOWaM\u002f\u002fW\u002fe22SFoz8TO7ETO7GjPyoGhEsR3aM\u002fQNFWg+cIpD9XnCm7vTSkP25n\u002fPKTYKQ\u002fhDLPKmqMpD+a\u002faFiQLikP7HIdJoW5KQ\u002fx5NH0uwPpT\u002feXhoKwzulP\u002fMp7UGZZ6U\u002fCvW\u002feW+TpT8gwJKxRb+lPzeLZekb66U\u002fTlY4IfIWpj9kIQtZyEKmP3vs3ZCebqY\u002fkbewyHSapj+ogoMAS8amP75NVjgh8qY\u002f1RgpcPcdpz\u002fq4\u002funzUmnPwGvzt+jdac\u002fF3qhF3qhpz8uRXRPUM2nP0QQR4cm+ac\u002fW9sZv\u002fwkqD9xpuz20lCoP4hxvy6pfKg\u002fnjySZn+oqD+1B2WeVdSoP8rSN9YrAKk\u002f4Z0KDgIsqT\u002f3aN1F2FepPw40sH2ug6k\u002fJf+CtYSvqT87ylXtWtupP1KVKCUxB6o\u002faGD7XAczqj9\u002fK86U3V6qP5X2oMyziqo\u002frMFzBIq2qj\u002fBjEY8YOKqP9hXGXQ2Dqs\u002f7iLsqww6qz8F7r7j4mWrPxu5kRu5kas\u002fMoRkU4+9qz9ITzeLZemrP18aCsM7Faw\u002fdeXc+hFBrD+MsK8y6GysP6F7gmq+mKw\u002fuEZVopTErD\u002fOESjaavCsP+Xc+hFBHK0\u002f\u002fKfNSRdIrT8Sc6CB7XOtPyk+c7nDn60\u002fPwlG8ZnLrT9W1BgpcPetP2yf62BGI64\u002fg2q+mBxPrj+YNZHQ8nquP7AAZAjJpq4\u002fxcs2QJ\u002fSrj\u002fclgl4df6uP\u002fJh3K9LKq8\u002fCS2v5yFWrz8f+IEf+IGvPzbDVFfOra8\u002fTI4nj6TZrz+xLH1jvQKwPzySZn+oGLA\u002fyPdPm5MusD9TXTm3fkSwP97CItNpWrA\u002faSgM71RwsD\u002f1jfUKQIawP4Dz3iYrnLA\u002fC1nIQhaysD+WvrFeAciwPyEkm3rs3bA\u002frYmEltfzsD84722ywgmxP8NUV86tH7E\u002fTrpA6pg1sT\u002faHyoGhEuxP2WFEyJvYbE\u002f8Or8PVp3sT97UOZZRY2xPwe2z3Uwo7E\u002fkRu5kRu5sT8dgaKtBs+xP6jmi8nx5LE\u002fM0x15dz6sT++sV4ByBCyP0oXSB2zJrI\u002f1XwxOZ48sj9g4hpViVKyP+xHBHF0aLI\u002fd63tjF9+sj8CE9eoSpSyP414wMQ1qrI\u002fGN6p4CDAsj+jQ5P8C9ayPy+pfBj367I\u002fug5mNOIBsz9FdE9QzRezP9DZOGy4LbM\u002fXD8iiKNDsz\u002fnpAukjlmzP3IK9b95b7M\u002f\u002fW\u002fe22SFsz+I1cf3T5uzPxM7sRM7sbM\u002fn6CaLybHsz8qBoRLEd2zP7VrbWf88rM\u002fQNFWg+cItD\u002fMNkCf0h60P1ecKbu9NLQ\u002f4gET16hKtD9uZ\u002fzyk2C0P\u002fjM5Q5\u002fdrQ\u002fhDLPKmqMtD8PmLhGVaK0P5r9oWJAuLQ\u002fJWOLfivOtD+xyHSaFuS0PzwuXrYB+rQ\u002fx5NH0uwPtT9S+TDu1yW1P95eGgrDO7U\u002facQDJq5RtT\u002fzKe1BmWe1P3+P1l2EfbU\u002fCvW\u002feW+TtT+WWqmVWqm1PyDAkrFFv7U\u002frCV8zTDVtT83i2XpG+u1P8PwTgUHAbY\u002fTlY4IfIWtj\u002fYuyE93Sy2P2QhC1nIQrY\u002f74b0dLNYtj977N2Qnm62PwVSx6yJhLY\u002fkbewyHSatj8cHZrkX7C2P6iCgwBLxrY\u002fMuhsHDbctj++TVY4IfK2P0mzP1QMCLc\u002f1RgpcPcdtz9ffhKM4jO3P+rj+6fNSbc\u002fdknlw7hftz8Br87fo3W3P4wUuPuOi7c\u002fF3qhF3qhtz+j34ozZbe3Py5FdE9Qzbc\u002fuqpdazvjtz9EEEeHJvm3P9B1MKMRD7g\u002fW9sZv\u002fwkuD\u002fnQAPb5zq4P3Gm7PbSULg\u002f\u002fAvWEr5muD+Icb8uqXy4PxPXqEqUkrg\u002fnjySZn+ouD8ponuCar64P7UHZZ5V1Lg\u002fQG1OukDquD\u002fK0jfWKwC5P1Y4IfIWFrk\u002f4Z0KDgIsuT9tA\u002fQp7UG5P\u002fdo3UXYV7k\u002fg87GYcNtuT8ONLB9roO5P5qZmZmZmbk\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#c0392b","width":3},"name":"g(x)  doom","visible":true,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"AAAAAAAAAAAkjthGM3XnPiSO2EYzdQc\u002f6J+zr9ljGj8kjthGM3UnPw0vWQ+QUzI\u002f6J+zr9ljOj\u002fUzD1Cu\u002fVBPySO2EYzdUc\u002f5ROq5VSwTT8NL1kPkFNSP1+2+HjKLFY\u002f6J+zr9ljWj+t64mzvfheP9TMPUK79WE\u002f7lREEQKeZD8kjthGM3VnP3d4+uJOe2o\u002f5ROq5VSwbT86sHOnIopwPw0vWQ+QU3I\u002fb4aFqnI0dD9ftvh4yix2P9y+snqXPHg\u002f6J+zr9ljej+EWfsXkaJ8P63ribO9+H4\u002fMasvwS+zgD\u002fUzD1Cu\u002fWBP71a71wBRIM\u002f7lREEQKehD9nuzxfvQOGPySO2EYzdYc\u002fKs0XyGPyiD93ePriTnuKPwqQgJf0D4w\u002f5ROq5VSwjT8HBHfNb1yPPzqwc6ciipA\u002fkZT9tOprkT8NL1kPkFOSP6x\u002fhrYSQZM\u002fb4aFqnI0lD9VQ1brry2VP1+2+HjKLJY\u002fjN9sU8Ixlz\u002fcvrJ6lzyYP1FUyu5JTZk\u002f6J+zr9ljmj+loW69RoCbP4RZ+xeRopw\u002fh8dZv7jKnT+t64mzvfieP\u002fviRfpPFqA\u002fMasvwS+zoD96ToIu\u002flKhP9TMPUK79aE\u002fPyZi\u002fGaboj+9Wu9cAUSjP05q5WOK76M\u002f7lREEQKepD+hGgxlaE+lP2e7PF+9A6Y\u002fPDfW\u002fwC7pj8kjthGM3WnPx\u002fAQzRUMqg\u002fKs0XyGPyqD9ItVQCYrWpP3d4+uJOe6o\u002ftxYJaipEqz8KkICX9A+sP27kYGut3qw\u002f5ROq5VSwrT9tHlwG64SuPwcEd81vXK8\u002fW2J9nXEbsD86sHOnIoqwP6FrngRL+rA\u002fkZT9tOprsT8LK5G4Ad+xPw0vWQ+QU7I\u002fmKBVuZXJsj+sf4a2EkGzP0nM6wYHurM\u002fb4aFqnI0tD8erlOhVbC0P1VDVuuvLbU\u002fFUaNiIGstT9ftvh4yiy2PzGUmLyKrrY\u002fjN9sU8Ixtz9vmHU9cba3P9y+snqXPLg\u002f0lIkCzXEuD9RVMruSU25P1fDpCXW17k\u002f6J+zr9ljuj8D6vaMVPG6P6Whbr1GgLs\u002f0cYaQbAQvD+EWfsXkaK8P8FZEELpNb0\u002fh8dZv7jKvT\u002fVoteP\u002f2C+P63ribO9+L4\u002fDaJwKvORvz\u002f74kX6TxbAP7Sr7QhiZMA\u002fMasvwS+zwD9z4QsjuQLBP3pOgi7+UsE\u002fRfKS4\u002f6jwT\u002fUzD1Cu\u002fXBPyfegkozSMI\u002fPyZi\u002fGabwj8cpdtXVu\u002fCP71a71wBRMM\u002fIkedC2iZwz9OauVjiu\u002fDPzzEx2VoRsQ\u002f7lREEQKexD9kHFtmV\u002fbEP6EaDGVoT8U\u002foE9XDTWpxT9nuzxfvQPGP+5dvFoBX8Y\u002fPDfW\u002fwC7xj9NR4pOvBfHPySO2EYzdcc\u002fwAvB6GXTxz8fwEM0VDLIP0SrYCn+kcg\u002fKs0XyGPyyD\u002fYJWkQhVPJP0i1VAJitck\u002ff3vanfoXyj93ePriTnvKPzastNFe38o\u002ftxYJaipEyz8AuPersanLPwqQgJf0D8w\u002f3J6jLPN2zD9u5GBrrd7MP8lguFMjR80\u002f5ROq5VSwzT\u002fI\u002fTUhQhrOP20eXAbrhM4\u002f2nUclU\u002fwzj8HBHfNb1zPP\u002fzIa69Lyc8\u002fW2J9nXEb0D+Y+xE4m1LQPzqwc6ciitA\u002fO4Ci6wfC0D+ha54ES\u002frQP2dyZ\u002fLrMtE\u002fkZT9tOpr0T8c0mBMR6XRPwsrkbgB39E\u002fWp+O+RkZ0j8NL1kPkFPSPyHa8PljjtI\u002fmKBVuZXJ0j9wgodNJQXTP6x\u002fhrYSQdM\u002fSJhS9F190z9JzOsGB7rTP6obUu4N99M\u002fb4aFqnI01D+UDIY7NXLUPx6uU6FVsNQ\u002fCWvu29Pu1D9VQ1brry3VPwU3i8\u002fpbNU\u002fFUaNiIGs1T+JcFwWd+zVP1+2+HjKLNY\u002flxdisHtt1j8xlJi8iq7WPy4snJ3379Y\u002fjN9sU8Ix1z9Nrgre6nPXP2+YdT1xttc\u002f9p2tcVX51z\u002fcvrJ6lzzYPyf7hFg3gNg\u002f0lIkCzXE2D\u002fhxZCSkAjZP1FUyu5JTdk\u002fJP7QH2GS2T9Xw6Ql1tfZP\u002fCjRQCpHdo\u002f6J+zr9lj2j9Et+4zaKraPwPq9oxU8do\u002fIzjMup442z+loW69RoDbP4km3pRMyNs\u002f0cYaQbAQ3D94giTCcVncP4RZ+xeRotw\u002f8UufQg7s3D\u002fBWRBC6TXdP\u002fKCThYigN0\u002fh8dZv7jK3T97JzI9rRXeP9Wi14\u002f\u002fYN4\u002fjjlKt6+s3j+t64mzvfjePyq5loQpRd8\u002fDaJwKvOR3z9QphelGt\u002ffP\u002fviRfpPFuA\u002ffoBmjEE94D+0q+0IYmTgP5tk22+xi+A\u002fMasvwS+z4D96f+r83NrgP3PhCyO5AuE\u002fH9GTM8Qq4T96ToIu\u002flLhP4dZ1xNne+E\u002fRfKS4\u002f6j4T+0GLWdxczhP9TMPUK79eE\u002fpQ4t0d8e4j8n3oJKM0jiP1w7P661ceI\u002fPyZi\u002fGab4j\u002fWnus0R8XiPxyl21dW7+I\u002fFTkyZZQZ4z+9Wu9cAUTjPxgKEz+dbuM\u002fIkedC2iZ4z\u002ffEY7CYcTjP05q5WOK7+M\u002fbFCj7+Ea5D88xMdlaEbkP7zFUsYdcuQ\u002f7lREEQKe5D\u002fScZxGFcrkP2QcW2ZX9uQ\u002fqlSAcMgi5T+hGgxlaE\u002flP0lu\u002fkM3fOU\u002foE9XDTWp5T+qvhbBYdblP2e7PF+9A+Y\u002f0kXJ50cx5j\u002fuXbxaAV\u002fmP7wDFrjpjOY\u002fPDfW\u002fwC75j9t+PwxR+nmP01Hik68F+c\u002f4CN+VWBG5z8kjthGM3XnPxqGmSI1pOc\u002fwAvB6GXT5z8WH0+ZxQLoPx\u002fAQzRUMug\u002f2O6euRFi6D9Eq2Ap\u002fpHoP1z1iIMZwug\u002fKs0XyGPy6D+oMg333CLpP9glaRCFU+k\u002ftqYrFFyE6T9ItVQCYrXpP4pR5NqW5uk\u002ff3vanfoX6j8gMzdLjUnqP3d4+uJOe+o\u002ffkskZT+t6j82rLTRXt\u002fqP56aqyitEes\u002ftxYJaipE6z+DIM2V1nbrPwC496uxqes\u002fLt2IrLvc6z8KkICX9A\u002fsP5vQ3mxcQ+w\u002f3J6jLPN27D\u002fO+s7WuKrsP27kYGut3uw\u002fxFtZ6tAS7T\u002fJYLhTI0ftP4Dzfaeke+0\u002f5ROq5VSw7T\u002f+wTwONOXtP8j9NSFCGu4\u002fRMeVHn9P7j9tHlwG64TuP0oDidiFuu4\u002f2nUclU\u002fw7j8ZdhY8SCbvPwcEd81vXO8\u002fqR8+ScaS7z\u002f8yGuvS8nvPwAAAAAAAPA\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#2471a3","width":3},"name":"h(x)  safety","visible":true,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"AAAAAAAAAAAD1l7ggvZdP6UkwtneL2U\u002f6fkMi9vyaT8D1l7ggvZtPw4HATbyv3A\u002fYnOJqzVZcj9hkV\u002f4kNFzP6UkwtneL3U\u002fgyBHKOJ4dj+IIyJTErB3P5OWSksN2Hg\u002f6fkMi9vyeT96VgT7GgJ7P\u002fJNpDMbB3w\u002fZYsayfACfT8D1l7ggvZ9P6Bpbf6U4n4\u002f9zajRs7Hfz808WJ3X1OAPw4HATbyv4A\u002f8\u002f\u002fzWNYpgT940pBgPJGBPzb7xFhP9oE\u002fYnOJqzVZgj\u002fCRTvMEbqCP15hYcQCGYM\u002fb7tJqCR2gz9hkV\u002f4kNGDP6Gn\u002fPJeK4Q\u002f5V6h2aODhD\u002fSl94rc9qEP6UkwtneL4U\u002fXMQ4b\u002feDhT\u002fMC5A5zNaFP9byCWhrKIY\u002fgyBHKOJ4hj+bsSm\u002fPMiGP2j3tZ6GFoc\u002fv\u002fhfecpjhz+IIyJTErCHP0OtqpBn+4c\u002fCungBNNFiD96zfn8XI+IP5OWSksN2Ig\u002flYoBUesfiT\u002fgJ+cG\u002fmaJP4EwRQVMrYk\u002f6fkMi9vyiT9D+1KEsjeKP86tMpDWe4o\u002ftlorBk2\u002fij96VgT7GgKLPxZcRUVFRIs\u002fEy1OgdCFiz\u002faRRcVwcaLP\u002fJNpDMbB4w\u002f2+sv4OJGjD96xRfxG4aMP1W0jhLKxIw\u002fZYsayfACjT8bOOJzk0CNP\u002f+F0E61fY0\u002fEVqPdFm6jT8D1l7ggvaNP5Z6y280Mo4\u002fRxFG5HBtjj\u002fK35\u002fkOqiOP6Bpbf6U4o4\u002fv8xRp4Ecjz8glTQ+A1aPP9O2Ywwcj48\u002f9zajRs7Hjz9K9BUHDgCQP1VBzbgDHJA\u002fMhzoNsk3kD808WJ3X1OQP9NYKWjHbpA\u002fTKdz7wGKkD+zGh\u002fsD6WQPw4HATbyv5A\u002fYlg1nqnakD98u2jvNvWQP++3Hu6aD5E\u002f8\u002f\u002fzWNYpkT9qNd3o6UORP51eYlHWXZE\u002fx0DXQJx3kT940pBgPJGRP0X0F1W3qpE\u002fpppZvg3EkT8PkdQ3QN2RPzb7xFhP9pE\u002fCrdNtDsPkj9kv5\u002fZBSiSP0GtH1SuQJI\u002fYnOJqzVZkj8ubhJknHGSPxHgif7iiZI\u002f7\u002fB3+Amikj\u002fCRTvMEbqSPyhEJfH60ZI\u002fURSV28Xpkj+ochH9cgGTP15hYcQCGZM\u002fD8mjnXUwkz\u002fWFmbyy0eTPwzluSkGX5M\u002fb7tJqCR2kz9s8mzQJ42TP6\u002fEOgIQpJM\u002fcZmcm926kz9hkV\u002f4kNGTP2VfRXIq6JM\u002fBXYUYar+kz+vkacaERWUP6Gn\u002fPJeK5Q\u002f20BDPJRBlD8ASOpGsVeUP71QrWG2bZQ\u002f5V6h2aODlD8hM0H6eZmUP80ieQ05r5Q\u002fHoCyW+HElD\u002fSl94rc9qUP9FIgcPu75Q\u002fdjq7ZlQFlT+htVNYpBqVP6UkwtneL5U\u002f2D03KwRFlT973KWLFFqVP1WLyzgQb5U\u002fXMQ4b\u002feDlT+M6FhqypiVP7zyeWSJrZU\u002fjOjTljTClT\u002fMC5A5zNaVPyrPz4NQ65U\u002fcpCzq8H\u002flT+\u002fGmHmHxSWP9byCWhrKJY\u002fxW\u002fxY6Q8lj\u002fJoXIMy1CWP2QJBpPfZJY\u002fgyBHKOJ4lj9ot\u002fn70oyWPxInDz2yoJY\u002frFqrGYC0lj+bsSm\u002fPMiWP5e7IVro25Y\u002fO9BrFoPvlj9YgyUfDQOXP2j3tZ6GFpc\u002fNw\u002fSvu8plz8kgICoSD2XP9fFHYSRUJc\u002fv\u002fhfecpjlz81iFqv83aXP1jZgUwNipc\u002fjsuudhedlz+IIyJTErCXP8Ddhwb+wpc\u002fOmn6tNrVlz9JywWCqOiXP0OtqpBn+5c\u002frVRhAxgOmD\u002fXhhz8uSCYP01YTJxNM5g\u002fCungBNNFmD\u002fjDU1WSliYP9XniLCzapg\u002fzmkUMw99mD96zfn8XI+YP6X3zyydoZg\u002ftcy84M+zmD+4dXc29cWYP5OWSksN2Jg\u002fqHUWPBjqmD+MFVMlFvyYPyNBEiMHDpk\u002flYoBUesfmT9\u002fPWzKwjGZP8hEPaqNQ5k\u002fdwQBC0xVmT\u002fgJ+cG\u002fmaZP5hkxLejeJk\u002fWjIUNz2KmT9oePqdypuZP4EwRQVMrZk\u002f7f9thcG+mT+uxps2K9CZP2UlpDCJ4Zk\u002f6fkMi9vymT8Q0w1dIgSaP7hbkb1dFZo\u002fg702w40mmj9D+1KEsjeaP5ZD8hbMSJo\u002ftTvZkNpZmj\u002fAQoYH3mqaP86tMpDWe5o\u002fzPzTP8SMmj+ACB0rp52aP94pf2Z\u002frpo\u002ftlorBk2\u002fmj8lUBMeENCaP8WO6sHI4Jo\u002f0XgnBXfxmj96VgT7GgKbP35YgLa0Eps\u002fLJVgSkQjmz8FADHJyTObPxZcRUVFRJs\u002fISm60LZUmz\u002ffi3V9HmWbP0MxKF18dZs\u002fEy1OgdCFmz\u002fb0y\u002f7GpabP1GQ4ttbpps\u002fXrRJNJO2mz\u002faRRcVwcabPxrHzI7l1ps\u002fYfu7sQDnmz9ppweOEvebP\u002fJNpDMbB5w\u002fo+hYshoXnD8hnb8ZESecP59uRnn+Npw\u002f2+sv4OJGnD++2ZNdvlacP4naXwCRZpw\u002f3hFY11p2nD96xRfxG4acP+T6EVzUlZw\u002fDBKSJoSlnD\u002fuXLxeK7WcP1W0jhLKxJw\u002fwwnhT2DUnD+b9mUk7uOcP4xIq51z85w\u002fZYsayfACnT9JkPmzZRKdP2PyamvSIZ0\u002fJ5lu\u002fDYxnT8bOOJzk0CdP1jMgd7nT50\u002fshfoSDRfnT+jGY+\u002feG6dP\u002f+F0E61fZ0\u002fijnmAuqMnT9qrOrnFpydP41i2Qk8q50\u002fEVqPdFm6nT+od8szb8mdPxzxLlN92J0\u002f6rU93oPnnT8D1l7ggvadP8Dm3GR6BZ4\u002fGmbmdmoUnj8VHI4hUyOeP5Z6y280Mp4\u002fc\u002ft6bA5Bnj\u002f2fF4i4U+eP8ecHZysXp4\u002fRxFG5HBtnj9hAUwFLnyeP+haignkip4\u002fbidD+5KZnj\u002fK35\u002fkOqiePxK+sc\u002fbtp4\u002fVw5yxnXFnj\u002f2fcLSCNSeP6Bpbf6U4p4\u002fFSomUxrxnj+aX4namP+ePzc8HZ4QDp8\u002fv8xRp4Ecnz+ZQIH\u002f6yqfP3Aw8K9POZ8\u002fvOPNwaxHnz8glTQ+A1afP8C1KS5TZJ8\u002fcC+empxynz\u002fjpW6M34CfP9O2Ywwcj58\u002fGjkyI1Kdnz\u002fNenvZgaufP2d+zTeruZ8\u002f9zajRs7Hnz9jw2QO69WfP7OoZ5cB5J8\u002fhwvv6RHynz9K9BUHDgCgPy2mHgYQB6A\u002f9kQY9g4OoD92uADbChWgP1VBzbgDHKA\u002fRpNqk\u002fkioD\u002fB7rxu7CmgP3M6oE7cMKA\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#c0392b","width":3},"name":"g(x)  doom","visible":false,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"AAAAAAAAAAAD1l7ggvZdP6UkwtneL2U\u002f6fkMi9vyaT8D1l7ggvZtPw4HATbyv3A\u002fYnOJqzVZcj9hkV\u002f4kNFzP6UkwtneL3U\u002fgyBHKOJ4dj+IIyJTErB3P5OWSksN2Hg\u002f6fkMi9vyeT96VgT7GgJ7P\u002fJNpDMbB3w\u002fZYsayfACfT8D1l7ggvZ9P6Bpbf6U4n4\u002f9zajRs7Hfz808WJ3X1OAPw4HATbyv4A\u002f8\u002f\u002fzWNYpgT940pBgPJGBPzb7xFhP9oE\u002fYnOJqzVZgj\u002fCRTvMEbqCP15hYcQCGYM\u002fb7tJqCR2gz9hkV\u002f4kNGDP6Gn\u002fPJeK4Q\u002f5V6h2aODhD\u002fSl94rc9qEP6UkwtneL4U\u002fXMQ4b\u002feDhT\u002fMC5A5zNaFP9byCWhrKIY\u002fgyBHKOJ4hj+bsSm\u002fPMiGP2j3tZ6GFoc\u002fv\u002fhfecpjhz+IIyJTErCHP0OtqpBn+4c\u002fCungBNNFiD96zfn8XI+IP5OWSksN2Ig\u002flYoBUesfiT\u002fgJ+cG\u002fmaJP4EwRQVMrYk\u002f6fkMi9vyiT9D+1KEsjeKP86tMpDWe4o\u002ftlorBk2\u002fij96VgT7GgKLPxZcRUVFRIs\u002fEy1OgdCFiz\u002faRRcVwcaLP\u002fJNpDMbB4w\u002f2+sv4OJGjD96xRfxG4aMP1W0jhLKxIw\u002fZYsayfACjT8bOOJzk0CNP\u002f+F0E61fY0\u002fEVqPdFm6jT8D1l7ggvaNP5Z6y280Mo4\u002fRxFG5HBtjj\u002fK35\u002fkOqiOP6Bpbf6U4o4\u002fv8xRp4Ecjz8glTQ+A1aPP9O2Ywwcj48\u002f9zajRs7Hjz9K9BUHDgCQP1VBzbgDHJA\u002fMhzoNsk3kD808WJ3X1OQP9NYKWjHbpA\u002fTKdz7wGKkD+zGh\u002fsD6WQPw4HATbyv5A\u002fYlg1nqnakD98u2jvNvWQP++3Hu6aD5E\u002f8\u002f\u002fzWNYpkT9qNd3o6UORP51eYlHWXZE\u002fx0DXQJx3kT940pBgPJGRP0X0F1W3qpE\u002fpppZvg3EkT8PkdQ3QN2RPzb7xFhP9pE\u002fCrdNtDsPkj9kv5\u002fZBSiSP0GtH1SuQJI\u002fYnOJqzVZkj8ubhJknHGSPxHgif7iiZI\u002f7\u002fB3+Amikj\u002fCRTvMEbqSPyhEJfH60ZI\u002fURSV28Xpkj+ochH9cgGTP15hYcQCGZM\u002fD8mjnXUwkz\u002fWFmbyy0eTPwzluSkGX5M\u002fb7tJqCR2kz9s8mzQJ42TP6\u002fEOgIQpJM\u002fcZmcm926kz9hkV\u002f4kNGTP2VfRXIq6JM\u002fBXYUYar+kz+vkacaERWUP6Gn\u002fPJeK5Q\u002f20BDPJRBlD8ASOpGsVeUP71QrWG2bZQ\u002f5V6h2aODlD8hM0H6eZmUP80ieQ05r5Q\u002fHoCyW+HElD\u002fSl94rc9qUP9FIgcPu75Q\u002fdjq7ZlQFlT+htVNYpBqVP6UkwtneL5U\u002f2D03KwRFlT973KWLFFqVP1WLyzgQb5U\u002fXMQ4b\u002feDlT+M6FhqypiVP7zyeWSJrZU\u002fjOjTljTClT\u002fMC5A5zNaVPyrPz4NQ65U\u002fcpCzq8H\u002flT+\u002fGmHmHxSWP9byCWhrKJY\u002fxW\u002fxY6Q8lj\u002fJoXIMy1CWP2QJBpPfZJY\u002fgyBHKOJ4lj9ot\u002fn70oyWPxInDz2yoJY\u002frFqrGYC0lj+bsSm\u002fPMiWP5e7IVro25Y\u002fO9BrFoPvlj9YgyUfDQOXP2j3tZ6GFpc\u002fNw\u002fSvu8plz8kgICoSD2XP9fFHYSRUJc\u002fv\u002fhfecpjlz81iFqv83aXP1jZgUwNipc\u002fjsuudhedlz+IIyJTErCXP8Ddhwb+wpc\u002fOmn6tNrVlz9JywWCqOiXP0OtqpBn+5c\u002frVRhAxgOmD\u002fXhhz8uSCYP01YTJxNM5g\u002fCungBNNFmD\u002fjDU1WSliYP9XniLCzapg\u002fzmkUMw99mD96zfn8XI+YP6X3zyydoZg\u002ftcy84M+zmD+4dXc29cWYP5OWSksN2Jg\u002fqHUWPBjqmD+MFVMlFvyYPyNBEiMHDpk\u002flYoBUesfmT9\u002fPWzKwjGZP8hEPaqNQ5k\u002fdwQBC0xVmT\u002fgJ+cG\u002fmaZP5hkxLejeJk\u002fWjIUNz2KmT9oePqdypuZP4EwRQVMrZk\u002f7f9thcG+mT+uxps2K9CZP2UlpDCJ4Zk\u002f6fkMi9vymT8Q0w1dIgSaP7hbkb1dFZo\u002fg702w40mmj9D+1KEsjeaP5ZD8hbMSJo\u002ftTvZkNpZmj\u002fAQoYH3mqaP86tMpDWe5o\u002fzPzTP8SMmj+ACB0rp52aP94pf2Z\u002frpo\u002ftlorBk2\u002fmj8lUBMeENCaP8WO6sHI4Jo\u002f0XgnBXfxmj96VgT7GgKbP35YgLa0Eps\u002fLJVgSkQjmz8FADHJyTObPxZcRUVFRJs\u002fISm60LZUmz\u002ffi3V9HmWbP0MxKF18dZs\u002fEy1OgdCFmz\u002fb0y\u002f7GpabP1GQ4ttbpps\u002fXrRJNJO2mz\u002faRRcVwcabPxrHzI7l1ps\u002fYfu7sQDnmz9ppweOEvebP\u002fJNpDMbB5w\u002fo+hYshoXnD8hnb8ZESecP59uRnn+Npw\u002f2+sv4OJGnD++2ZNdvlacP4naXwCRZpw\u002f3hFY11p2nD96xRfxG4acP+T6EVzUlZw\u002fDBKSJoSlnD\u002fuXLxeK7WcP1W0jhLKxJw\u002fwwnhT2DUnD+b9mUk7uOcP4xIq51z85w\u002fZYsayfACnT9JkPmzZRKdP2PyamvSIZ0\u002fJ5lu\u002fDYxnT8bOOJzk0CdP1jMgd7nT50\u002fshfoSDRfnT+jGY+\u002feG6dP\u002f+F0E61fZ0\u002fijnmAuqMnT9qrOrnFpydP41i2Qk8q50\u002fEVqPdFm6nT+od8szb8mdPxzxLlN92J0\u002f6rU93oPnnT8D1l7ggvadP8Dm3GR6BZ4\u002fGmbmdmoUnj8VHI4hUyOeP5Z6y280Mp4\u002fc\u002ft6bA5Bnj\u002f2fF4i4U+eP8ecHZysXp4\u002fRxFG5HBtnj9hAUwFLnyeP+haignkip4\u002fbidD+5KZnj\u002fK35\u002fkOqiePxK+sc\u002fbtp4\u002fVw5yxnXFnj\u002f2fcLSCNSeP6Bpbf6U4p4\u002fFSomUxrxnj+aX4namP+ePzc8HZ4QDp8\u002fv8xRp4Ecnz+ZQIH\u002f6yqfP3Aw8K9POZ8\u002fvOPNwaxHnz8glTQ+A1afP8C1KS5TZJ8\u002fcC+empxynz\u002fjpW6M34CfP9O2Ywwcj58\u002fGjkyI1Kdnz\u002fNenvZgaufP2d+zTeruZ8\u002f9zajRs7Hnz9jw2QO69WfP7OoZ5cB5J8\u002fhwvv6RHynz9K9BUHDgCgPy2mHgYQB6A\u002f9kQY9g4OoD92uADbChWgP1VBzbgDHKA\u002fRpNqk\u002fkioD\u002fB7rxu7CmgP3M6oE7cMKA\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#2471a3","width":3},"name":"h(x)  safety","visible":false,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"AAAAAAAAAAAkjthGM3XnPiSO2EYzdQc\u002f6J+zr9ljGj8kjthGM3UnPw0vWQ+QUzI\u002f6J+zr9ljOj\u002fUzD1Cu\u002fVBPySO2EYzdUc\u002f5ROq5VSwTT8NL1kPkFNSP1+2+HjKLFY\u002f6J+zr9ljWj+t64mzvfheP9TMPUK79WE\u002f7lREEQKeZD8kjthGM3VnP3d4+uJOe2o\u002f5ROq5VSwbT86sHOnIopwPw0vWQ+QU3I\u002fb4aFqnI0dD9ftvh4yix2P9y+snqXPHg\u002f6J+zr9ljej+EWfsXkaJ8P63ribO9+H4\u002fMasvwS+zgD\u002fUzD1Cu\u002fWBP71a71wBRIM\u002f7lREEQKehD9nuzxfvQOGPySO2EYzdYc\u002fKs0XyGPyiD93ePriTnuKPwqQgJf0D4w\u002f5ROq5VSwjT8HBHfNb1yPPzqwc6ciipA\u002fkZT9tOprkT8NL1kPkFOSP6x\u002fhrYSQZM\u002fb4aFqnI0lD9VQ1brry2VP1+2+HjKLJY\u002fjN9sU8Ixlz\u002fcvrJ6lzyYP1FUyu5JTZk\u002f6J+zr9ljmj+loW69RoCbP4RZ+xeRopw\u002fh8dZv7jKnT+t64mzvfieP\u002fviRfpPFqA\u002fMasvwS+zoD96ToIu\u002flKhP9TMPUK79aE\u002fPyZi\u002fGaboj+9Wu9cAUSjP05q5WOK76M\u002f7lREEQKepD+hGgxlaE+lP2e7PF+9A6Y\u002fPDfW\u002fwC7pj8kjthGM3WnPx\u002fAQzRUMqg\u002fKs0XyGPyqD9ItVQCYrWpP3d4+uJOe6o\u002ftxYJaipEqz8KkICX9A+sP27kYGut3qw\u002f5ROq5VSwrT9tHlwG64SuPwcEd81vXK8\u002fW2J9nXEbsD86sHOnIoqwP6FrngRL+rA\u002fkZT9tOprsT8LK5G4Ad+xPw0vWQ+QU7I\u002fmKBVuZXJsj+sf4a2EkGzP0nM6wYHurM\u002fb4aFqnI0tD8erlOhVbC0P1VDVuuvLbU\u002fFUaNiIGstT9ftvh4yiy2PzGUmLyKrrY\u002fjN9sU8Ixtz9vmHU9cba3P9y+snqXPLg\u002f0lIkCzXEuD9RVMruSU25P1fDpCXW17k\u002f6J+zr9ljuj8D6vaMVPG6P6Whbr1GgLs\u002f0cYaQbAQvD+EWfsXkaK8P8FZEELpNb0\u002fh8dZv7jKvT\u002fVoteP\u002f2C+P63ribO9+L4\u002fDaJwKvORvz\u002f74kX6TxbAP7Sr7QhiZMA\u002fMasvwS+zwD9z4QsjuQLBP3pOgi7+UsE\u002fRfKS4\u002f6jwT\u002fUzD1Cu\u002fXBPyfegkozSMI\u002fPyZi\u002fGabwj8cpdtXVu\u002fCP71a71wBRMM\u002fIkedC2iZwz9OauVjiu\u002fDPzzEx2VoRsQ\u002f7lREEQKexD9kHFtmV\u002fbEP6EaDGVoT8U\u002foE9XDTWpxT9nuzxfvQPGP+5dvFoBX8Y\u002fPDfW\u002fwC7xj9NR4pOvBfHPySO2EYzdcc\u002fwAvB6GXTxz8fwEM0VDLIP0SrYCn+kcg\u002fKs0XyGPyyD\u002fYJWkQhVPJP0i1VAJitck\u002ff3vanfoXyj93ePriTnvKPzastNFe38o\u002ftxYJaipEyz8AuPersanLPwqQgJf0D8w\u002f3J6jLPN2zD9u5GBrrd7MP8lguFMjR80\u002f5ROq5VSwzT\u002fI\u002fTUhQhrOP20eXAbrhM4\u002f2nUclU\u002fwzj8HBHfNb1zPP\u002fzIa69Lyc8\u002fW2J9nXEb0D+Y+xE4m1LQPzqwc6ciitA\u002fO4Ci6wfC0D+ha54ES\u002frQP2dyZ\u002fLrMtE\u002fkZT9tOpr0T8c0mBMR6XRPwsrkbgB39E\u002fWp+O+RkZ0j8NL1kPkFPSPyHa8PljjtI\u002fmKBVuZXJ0j9wgodNJQXTP6x\u002fhrYSQdM\u002fSJhS9F190z9JzOsGB7rTP6obUu4N99M\u002fb4aFqnI01D+UDIY7NXLUPx6uU6FVsNQ\u002fCWvu29Pu1D9VQ1brry3VPwU3i8\u002fpbNU\u002fFUaNiIGs1T+JcFwWd+zVP1+2+HjKLNY\u002flxdisHtt1j8xlJi8iq7WPy4snJ3379Y\u002fjN9sU8Ix1z9Nrgre6nPXP2+YdT1xttc\u002f9p2tcVX51z\u002fcvrJ6lzzYPyf7hFg3gNg\u002f0lIkCzXE2D\u002fhxZCSkAjZP1FUyu5JTdk\u002fJP7QH2GS2T9Xw6Ql1tfZP\u002fCjRQCpHdo\u002f6J+zr9lj2j9Et+4zaKraPwPq9oxU8do\u002fIzjMup442z+loW69RoDbP4km3pRMyNs\u002f0cYaQbAQ3D94giTCcVncP4RZ+xeRotw\u002f8UufQg7s3D\u002fBWRBC6TXdP\u002fKCThYigN0\u002fh8dZv7jK3T97JzI9rRXeP9Wi14\u002f\u002fYN4\u002fjjlKt6+s3j+t64mzvfjePyq5loQpRd8\u002fDaJwKvOR3z9QphelGt\u002ffP\u002fviRfpPFuA\u002ffoBmjEE94D+0q+0IYmTgP5tk22+xi+A\u002fMasvwS+z4D96f+r83NrgP3PhCyO5AuE\u002fH9GTM8Qq4T96ToIu\u002flLhP4dZ1xNne+E\u002fRfKS4\u002f6j4T+0GLWdxczhP9TMPUK79eE\u002fpQ4t0d8e4j8n3oJKM0jiP1w7P661ceI\u002fPyZi\u002fGab4j\u002fWnus0R8XiPxyl21dW7+I\u002fFTkyZZQZ4z+9Wu9cAUTjPxgKEz+dbuM\u002fIkedC2iZ4z\u002ffEY7CYcTjP05q5WOK7+M\u002fbFCj7+Ea5D88xMdlaEbkP7zFUsYdcuQ\u002f7lREEQKe5D\u002fScZxGFcrkP2QcW2ZX9uQ\u002fqlSAcMgi5T+hGgxlaE\u002flP0lu\u002fkM3fOU\u002foE9XDTWp5T+qvhbBYdblP2e7PF+9A+Y\u002f0kXJ50cx5j\u002fuXbxaAV\u002fmP7wDFrjpjOY\u002fPDfW\u002fwC75j9t+PwxR+nmP01Hik68F+c\u002f4CN+VWBG5z8kjthGM3XnPxqGmSI1pOc\u002fwAvB6GXT5z8WH0+ZxQLoPx\u002fAQzRUMug\u002f2O6euRFi6D9Eq2Ap\u002fpHoP1z1iIMZwug\u002fKs0XyGPy6D+oMg333CLpP9glaRCFU+k\u002ftqYrFFyE6T9ItVQCYrXpP4pR5NqW5uk\u002ff3vanfoX6j8gMzdLjUnqP3d4+uJOe+o\u002ffkskZT+t6j82rLTRXt\u002fqP56aqyitEes\u002ftxYJaipE6z+DIM2V1nbrPwC496uxqes\u002fLt2IrLvc6z8KkICX9A\u002fsP5vQ3mxcQ+w\u002f3J6jLPN27D\u002fO+s7WuKrsP27kYGut3uw\u002fxFtZ6tAS7T\u002fJYLhTI0ftP4Dzfaeke+0\u002f5ROq5VSw7T\u002f+wTwONOXtP8j9NSFCGu4\u002fRMeVHn9P7j9tHlwG64TuP0oDidiFuu4\u002f2nUclU\u002fw7j8ZdhY8SCbvPwcEd81vXO8\u002fqR8+ScaS7z\u002f8yGuvS8nvPwAAAAAAAPA\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#c0392b","width":3},"name":"g(x)  doom","visible":false,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"D4D6cndBVD9TKsxiPKdVP0rdKJGeJVc\u002fjF+B9Uy+WD94nr+jE3NaP1iHXrbdRVw\u002fuuvTVrc4Xj\u002f\u002fvfLy5yZgP2dOxiK+Q2E\u002fqfwKoxx0Yj\u002f2ZBgcV7ljP2Yu09LXFGU\u002f+fk5GSGIZj8Rcv\u002fTzhRoP8clChaYvGk\u002fwvWs0lCBaz8C2WGn62RtP+glxL17aW8\u002fPlw7Y5vIcD9lTMaGO+9xP1e0d9TeKXM\u002fBISOYNp5dD9STDb6mOB1PzWnbGqcX3c\u002fsA8LwH74eD9zl7Oo86x6P6+tNdbJfnw\u002f2BLQb+xvfj\u002fLqT5HMkGAPyHvm2EtXIE\u002fW4nsUgyKgj8dTHE1BcyDP5DrED9gI4U\u002fJ9XYj3iRhj99nTIAvReIPznDD+2wt4k\u002fzDECAO1yiz9nl+TwH0uNP431WT4PQo8\u002frs0D7cuskD\u002f+8X9i18mRP35fL8ww+ZI\u002fYcnNeus7lD87jMdsJ5OVPwQ2UGYRAJc\u002f4xP+9OKDmD8efZJb4h+aP0umTGJi1Zs\u002fpUXcB8KlnT\u002f7P8QObJKfP+g0XzFrzqA\u002fz4e9qEDjoT+ob\u002fZIewijP9\u002fymwrkPqQ\u002fA4kqTkeHpT\u002fuA8gGdOKmP9ctnMI6Uag\u002fyaubjmzUqT\u002f\u002fBN+z2WyrP0BH+UxQG60\u002fwHU2sprgrj9nKh7evl6wP0gonW5bWbE\u002fC03UCH1gsj\u002fYzUXVd3SzP5NsI\u002fqYlbQ\u002fJn4fXyXEtT91Hm1kWAC3P01KaZBiSrg\u002fSRMfOGiiuT+ox5IngAi7P8hldE+yfLw\u002fHaeBfvb+vT+mE3MtM4+\u002fP\u002f58YjKelsA\u002fqdP9YWlswT\u002f5YRBZ0UjCPxP8jt+hK8M\u002ff\u002fOfHpwUxD\u002fngseMdgPFP+OjY\u002fzc98U\u002fAT3GznDxxj9ov4VNye\u002fHPwkFyCp08sg\u002fMkJfKPb4yT8+oJTkywLLP7CNeMpqD8w\u002fKgiQIkIezT84vcI\u002fvC7OP7N4ksM\u002fQM8\u002fI3X1eRgp0D94BSWO+bHQP3sY0vl0OtE\u002fZdwfgD3C0T+yf2p5B0nSP8e1Q3mJztI\u002fVvTj6XxS0z9IQm2antTTP5fqwz2vVNQ\u002feew22HPS1D84XrAatk3VP93Pp6pExtU\u002fr+GQVvM71j+g\u002f\u002fk2m67WPz55\u002frwaHtc\u002fFsUQr1WK1z8BWXMVNfPXP2XC9henWNg\u002fHxzCzp662D9Y\u002fgcIFBnZP9eZlgQDdNk\u002fH3MvLWzL2T+zGYTCUx\u002faP8F6m4nBb9o\u002fS1hBdsC82j8AQ\u002flUXgbbP39awnWrTNs\u002fyv3KWLqP2z\u002fdLAVen8\u002fbP0UOX3hwDNw\u002f1\u002fg35URG3D+zYoLoNH3cPwfE3o1Zsdw\u002fHTDbbszi3D8+TWd+pxHdP7hodNkEPt0\u002f1YilnP5n3T9OVOO+ro\u002fdP\u002fAjm\u002fAutd0\u002f7zxnf5jY3T93rdc9BPrdP9AvD2+KGd4\u002flnfltUI33j+0BkAHRFPePwzLU5+kbd4\u002frweE+XmG3j\u002fJKpbK2J3eP5Dx9fzUs94\u002fIGzJroHI3j+W6Zgx8dveP7dtUws17t4\u002fL\u002fF8+F3\u002f3j8KPFjvew\u002ffPxyX4iOeHt8\u002fIM57DNMs3z\u002fcABlnKDrfP8WG5D6rRt8\u002fcqow8mdS3z\u002fWP6Y4al3fP5QQmym9Z98\u002fv85+Qmtx3z\u002fFsk9tfnrfP2oeCgcAg98\u002f\u002fJkI5viK3z8cRUtgcZLfP\u002flkn1Fxmd8\u002fcRyhIQCg3z\u002f6kJLJJKbfP2DPBNrlq98\u002fTq9PgEmx3z\u002f0wNaLVbbfP8T\u002fGXMPu98\u002f3ZqRWHy\u002f3z8Lo1QPocPfPwHZih+Cx98\u002fjy+qyiPL3z\u002f63oAPis7fP04iDa640d8\u002fKucjK7PU3z8X3ufTfNffP8x1EsEY2t8\u002fdGAQ2onc3z9RUPPX0t7fP5qhOkj24N8\u002fMqt0j\u002fbi3z\u002fub7rr1eTfP1tmB3eW5t8\u002fJAdvKTro3z+4yTHbwunfP5Ess0Yy698\u002fPVtSCors3z+C9yaqy+3fP5V9o5H47t8\u002fk60eFRLw3z8QVkVzGfHfPwLNddYP8t8\u002fJFYGVvby3z8Bp3f3zfPfPzqrlK+X9N8\u002fAJ2AY1T13z+teLTpBPbfP4TE7Aqq9t8\u002fWJkIg0T33z\u002fQy9oB1fffP5QK7itc+N8\u002fqbk8m9r43z+TSN3fUPnfPwK2o4C\u002f+d8\u002f8Om4+yb63z9DhSjHh\u002frfP\u002fm8ZVHi+t8\u002fNs7HATf73z8ekP44hvvfP9agf1HQ+98\u002fNaPsnxX83z8g\u002fHJzVvzfP2F3JRaT\u002fN8\u002f8jVQzcv83z+NQcfZAP3fP8AbMHgy\u002fd8\u002fc5lG4WD93z\u002f8Vh1KjP3fPwoMWuS0\u002fd8\u002fRgJu3tr93z9x7cpj\u002fv3fP+pfFJ0f\u002ft8\u002frBJOsD7+3z+iNAfBW\u002f7fP67xgvB2\u002ft8\u002f5l7eXZD+3z+r9jMmqP7fP0nMvGS+\u002ft8\u002f0JzvMtP+3z\u002fe352o5v7fP1r5Dtz4\u002ft8\u002fuKoZ4gn\u002f3z+U4DvOGf\u002ffP6L3sLIo\u002f98\u002fBJKGoDb\u002f3z\u002fIFLCnQ\u002f\u002ffP5njGNdP\u002f98\u002fVG+1PFv\u002f3z\u002ffKpPlZf\u002ffP2V4591v\u002f98\u002f7J8dMXn\u002f3z843+Ppgf\u002ffP7qhNxKK\u002f98\u002fhe5ws5H\u002f3z9cGU3WmP\u002ffP+nC+IKf\u002f98\u002fnDMZwaX\u002f3z\u002fEG9WXq\u002f\u002ffPwfD3A2x\u002f98\u002fW7BxKbb\u002f3z9i023wuv\u002ffP0w3Smi\u002f\u002f98\u002f4kYllsP\u002f3z\u002feqMh+x\u002f\u002ffP1C7ribL\u002f98\u002fRLQHks7\u002f3z+Rbb7E0f\u002ffP0LhfMLU\u002f98\u002fx1uwjtf\u002f3z+jaI0s2v\u002ffPzZ+E5\u002fc\u002f98\u002ftmwQ6d7\u002f3z9PlCMN4f\u002ffPyrmwA3j\u002f98\u002fnbMz7eT\u002f3z\u002f2TqGt5v\u002ffP6eAC1Ho\u002f98\u002f3dNS2en\u002f3z\u002fnvDhI6\u002f\u002ffPx+cYZ\u002fs\u002f98\u002fcp9W4O3\u002f3z\u002fOhIcM7\u002f\u002ffP2k\u002fTCXw\u002f98\u002fx4HmK\u002fH\u002f3z9BLoMh8v\u002ffP7+vOwfz\u002f98\u002fHDwX3vP\u002f3z+wAQyn9P\u002ffP19BAGP1\u002f98\u002fYVbLEvb\u002f3z\u002f+rDa39v\u002ffP1Kp\u002flD3\u002f98\u002fKX\u002fT4Pf\u002f3z\u002fX+1ln+P\u002ffPxFDLOX4\u002f98\u002fgX\u002faWvn\u002f3z\u002f3h+vI+f\u002ffP+N53S\u002f6\u002f98\u002f10kmkPr\u002f3z+xSjTq+v\u002ffP\u002fOrbj77\u002f98\u002fDvA1jfv\u002f3z\u002fuWuTW+\u002f\u002ffP2VZzhv8\u002f98\u002f6eFCXPz\u002f3z8Mz4uY\u002fP\u002ffPx007tD8\u002f98\u002fR6yqBf3\u002f3z+hpP02\u002ff\u002ffP2ahH2X9\u002f98\u002frX5FkP3\u002f3z8LraC4\u002ff\u002ffPy9qX979\u002f98\u002f3PWsAf7\u002f3z99w7Ei\u002fv\u002ffP3iok0H+\u002f98\u002f"},"type":"scatter"},{"hovertemplate":"%{y:.2f}","line":{"color":"#2471a3","width":3},"name":"h(x)  safety","visible":false,"x":{"dtype":"f8","bdata":"AAAAAAAAAADDVFfOrR+hP8NUV86tH7E\u002fJP+CtYSvuT\u002fDVFfOrR\u002fBP\u002fQp7UGZZ8U\u002fJP+CtYSvyT9V1BgpcPfNP8NUV86tH9E\u002fWz8iiKND0z\u002f0Ke1BmWfVP4wUuPuOi9c\u002fJP+CtYSv2T+96U1vetPbP1XUGClw990\u002fd99x8bIN4D\u002fDVFfOrR\u002fhPw\u002fKPKuoMeI\u002fWz8iiKND4z+otAdlnlXkP\u002fQp7UGZZ+U\u002fQJ\u002fSHpR55j+MFLj7jovnP9iJndiJneg\u002fJP+CtYSv6T9xdGiSf8HqP73pTW960+s\u002fCV8zTHXl7D9V1BgpcPftP6FJ\u002fgVrCe8\u002fd99x8bIN8D8dmuRfsJbwP8NUV86tH\u002fE\u002faQ\u002fKPKuo8T8PyjyrqDHyP7WErxmmuvI\u002fWz8iiKND8z8B+pT2oMzzP6i0B2WeVfQ\u002fTm9605ve9D\u002f0Ke1BmWf1P5rkX7CW8PU\u002fQJ\u002fSHpR59j\u002fmWUWNkQL3P4wUuPuOi\u002fc\u002fMs8qaowU+D\u002fYiZ3YiZ34P35EEEeHJvk\u002fJP+CtYSv+T\u002fLufUjgjj6P3F0aJJ\u002fwfo\u002fFy\u002fbAH1K+z+96U1vetP7P2OkwN13XPw\u002fCV8zTHXl\u002fD+vGaa6cm79P1XUGClw9\u002f0\u002f+46Ll22A\u002fj+hSf4Fawn\u002fP0gEcXRokv8\u002fd99x8bINAEDKPKuoMVIAQB2a5F+wlgBAcPcdFy\u002fbAEDDVFfOrR8BQBaykIUsZAFAaQ\u002fKPKuoAUC8bAP0Ke0BQA\u002fKPKuoMQJAYid2Yid2AkC1hK8ZproCQAji6NAk\u002fwJAWz8iiKNDA0CunFs\u002fIogDQAH6lPagzANAVVfOrR8RBECotAdlnlUEQPsRQRwdmgRATm9605veBEChzLOKGiMFQPQp7UGZZwVAR4cm+ResBUCa5F+wlvAFQO1BmWcVNQZAQJ\u002fSHpR5BkCT\u002fAvWEr4GQOZZRY2RAgdAObd+RBBHB0CMFLj7josHQN9x8bIN0AdAMs8qaowUCECFLGQhC1kIQNiJndiJnQhAK+fWjwjiCEB+RBBHhyYJQNGhSf4FawlAJP+CtYSvCUB4XLxsA\u002fQJQMu59SOCOApAHhcv2wB9CkBxdGiSf8EKQMTRoUn+BQtAFy\u002fbAH1KC0BqjBS4+44LQL3pTW960wtAEEeHJvkXDEBjpMDdd1wMQLYB+pT2oAxACV8zTHXlDEBcvGwD9CkNQK8Zprpybg1AAnffcfGyDUBV1BgpcPcNQKgxUuDuOw5A+46Ll22ADkBO7MRO7MQOQKFJ\u002fgVrCQ9A9KY3velND0BIBHF0aJIPQJthqivn1g9Ad99x8bINEEAgjg5N8i8QQMo8q6gxUhBAc+tHBHF0EEAdmuRfsJYQQMZIgbvvuBBAcPcdFy\u002fbEEAZprpybv0QQMNUV86tHxFAbQP0Ke1BEUAWspCFLGQRQMBgLeFrhhFAaQ\u002fKPKuoEUATvmaY6soRQLxsA\u002fQp7RFAZhugT2kPEkAPyjyrqDESQLl42QboUxJAYid2Yid2EkAM1hK+ZpgSQLWErxmmuhJAXzNMdeXcEkAI4ujQJP8SQLKQhSxkIRNAWz8iiKNDE0AF7r7j4mUTQK6cWz8iiBNAWEv4mmGqE0AB+pT2oMwTQKuoMVLg7hNAVVfOrR8RFED+BWsJXzMUQKi0B2WeVRRAUWOkwN13FED7EUEcHZoUQKTA3XdcvBRATm9605veFED3HRcv2wAVQKHMs4oaIxVASntQ5llFFUD0Ke1BmWcVQJ3YiZ3YiRVAR4cm+ResFUDwNcNUV84VQJrkX7CW8BVAQ5P8C9YSFkDtQZlnFTUWQJbwNcNUVxZAQJ\u002fSHpR5FkDpTW9605sWQJP8C9YSvhZAPauoMVLgFkDmWUWNkQIXQJAI4ujQJBdAObd+RBBHF0DjZRugT2kXQIwUuPuOixdANsNUV86tF0DfcfGyDdAXQIkgjg5N8hdAMs8qaowUGEDcfcfFyzYYQIUsZCELWRhAL9sAfUp7GEDYiZ3YiZ0YQII4OjTJvxhAK+fWjwjiGEDVlXPrRwQZQH5EEEeHJhlAKPOsosZIGUDRoUn+BWsZQHtQ5llFjRlAJP+CtYSvGUDOrR8RxNEZQHhcvGwD9BlAIQtZyEIWGkDLufUjgjgaQHRokn\u002fBWhpAHhcv2wB9GkDHxcs2QJ8aQHF0aJJ\u002fwRpAGiMF7r7jGkDE0aFJ\u002fgUbQG2APqU9KBtAFy\u002fbAH1KG0DA3XdcvGwbQGqMFLj7jhtAEzuxEzuxG0C96U1vetMbQGaY6sq59RtAEEeHJvkXHEC59SOCODocQGOkwN13XBxADFNdObd+HEC2AfqU9qAcQGCwlvA1wxxACV8zTHXlHECzDdCntAcdQFy8bAP0KR1ABmsJXzNMHUCvGaa6cm4dQFnIQhaykB1AAnffcfGyHUCsJXzNMNUdQFXUGClw9x1A\u002f4K1hK8ZHkCoMVLg7jseQFLg7jsuXh5A+46Ll22AHkClPSjzrKIeQE7sxE7sxB5A+JphqivnHkChSf4FawkfQEv4mmGqKx9A9KY3velNH0CeVdQYKXAfQEgEcXRokh9A8bIN0Ke0H0CbYaor59YfQEQQR4cm+R9Ad99x8bINIEDMNkCf0h4gQCCODk3yLyBAdeXc+hFBIEDKPKuoMVIgQB+UeVZRYyBAc+tHBHF0IEDIQhaykIUgQB2a5F+wliBAcvGyDdCnIEDGSIG777ggQBugT2kPyiBAcPcdFy\u002fbIEDFTuzETuwgQBmmunJu\u002fSBAbv2III4OIUDDVFfOrR8hQBisJXzNMCFAbQP0Ke1BIUDBWsLXDFMhQBaykIUsZCFAawlfM0x1IUDAYC3ha4YhQBS4+46LlyFAaQ\u002fKPKuoIUC+ZpjqyrkhQBO+ZpjqyiFAZxU1RgrcIUC8bAP0Ke0hQBHE0aFJ\u002fiFAZhugT2kPIkC6cm79iCAiQA\u002fKPKuoMSJAZCELWchCIkC5eNkG6FMiQA3Qp7QHZSJAYid2Yid2IkC3fkQQR4ciQAzWEr5mmCJAYS3ha4apIkC1hK8ZproiQArcfcfFyyJAXzNMdeXcIkC0ihojBe4iQAji6NAk\u002fyJAXTm3fkQQI0CykIUsZCEjQAfoU9qDMiNAWz8iiKNDI0CwlvA1w1QjQAXuvuPiZSNAWkWNkQJ3I0CunFs\u002fIogjQAP0Ke1BmSNAWEv4mmGqI0CtosZIgbsjQAH6lPagzCNAVlFjpMDdI0CrqDFS4O4jQAAAAAAAACRA"},"y":{"dtype":"f8","bdata":"cSlxeMXmmz72SS7I49SdPlosNaIw5Z8+V8KDrgQNoT7EHKGX+jqiPk6lmArUfaM+OBjY9ALXpD4CkPHaEkimPrs2yp2q0qc+rD0iX454qT7QOKGHoTurPqRjuPDoHa0+nV7VNI0hrz6f7UWUbqSwPgnLxT8oy7E+FxY1UEUGsz6q\u002fD7VLle0PjpEoNhmv7U+mjV2GIpAtz5Z7yXgUdy4Pq\u002fZ+QGWlLo+EnC4805rvD5Kn58QmGK+PthlrQFZPsA+2KxbrQJewT5XQgamkpHCPqLSP1Np2sM+q\u002fxCff85xT7wZ6D857HGPmZ1yIfRQ8g+XySBoIjxyT7kTH2j+bzLPqxkcfwyqM0+zsQrgWe1zz7WhzF7ePPQPomHjmCpH9I+rbbhY55g0z4MGxlDx7fUPjkwIyytJtY+bXxYf\u002fSu1z6eFAuxXlLZPoxNY0zMEts+++vWGT\u002fy3D6o27Br3PLePg18pcl3i+A+zfrkQHaw4T51mK5LueniPiWpHNKnOOQ+rr9ej8Ge5T7gxzfJoB3nPgdP3iX8tug+YlhYoqhs6j4OK5Grm0DsPukoj1ztNO4+kwktcu0l8D4OsyeL5EPxPtpn6ZOjdfI+cTs+z4i88z4yAQ+5Chr1PgwrGbO5j\u002fY+pIVFz0Ef+D5hB6m5bMr5PsrmXsQjk\u002fs+yKKRF3J7\u002fT5evzEIh4X\u002fPqlvAkzc2QA\u002ff2PzEEMEAj8A9qOaTUMDP5xaFVJpmAQ\u002fdtmM4xwFBj\u002fEDWf9CYsHP1KBvy3vKwk\u002fRaoL4anpCj9\u002fdO2DOMYMP6omqcq8ww4\u002f6WJqED9yED\u002fNIwIidpURP3gMMwbRzBI\u002fbnc7M7QZFD9AXZXAnH0VP4CRERoi+hY\u002fDS3l0PeQGD\u002fn2rWM70MaP0Y71R77FBw\u002fdGYBui4GHj8DdpSo4QwgP8PQ7Y8MKSE\u002fVKufL91YIj+UIEgVr50jP29qNMv1+CQ\u002fQfohfz5sJj8ck\u002fPFMfknP354UH6VoSk\u002fAftE1E5nKz8z9h5oZEwtP9hP5poAUy8\u002fZKp8Abq+MD8Fhb8FHOcxP28ox+D4IzM\u002fUBkMabp2ND8w9upd4+A1P2NCWBwRZDc\u002fbvsncf0BOT96td2KgLw6Px1ZGQ2TlTw\u002fgK\u002fVR1CPPj+dX2hK\u002fFVAP0VQze75dkE\u002ft\u002fZ2JuqrQj+WqA6XLPZDP98Axv44V0U\u002fa46p1qDQRj9l\u002fLEQEWRIP+e3U\u002fNTE0o\u002fL\u002fplFFPgSz\u002fa4VN1Gc1NPwbZpMLV208\u002fQmuCXG4HUT\u002fBfQjZVTRSP9PCpK31dVM\u002fim\u002fTTLnNVD+LRzbOJD1WPyPb+JDWxVc\u002fT6WZ+IhpWT9nEZBFFCpbP3LNVItwCV0\u002fe2ZexbcJXz8G11cGlJZgP73Fz\u002fcSu2E\u002fuBnFdp\u002fzYj9p9BAMlkFkPyRrDG5qpmU\u002frdhu+agjZz\u002fPgpA\u002f+LpoP\u002fk46Koabmo\u002fmX+TOvA+bD8beq5VeC9uP5hImdvpIHA\u002f9g6AOSM8cT8Dw08KnWpyP4QsFOyfrXM\u002f1yfMfokGdT+twVubzXZ2P+3juJf3\u002f3c\u002fisEmmKujeT8DuS7tp2N7P+iA2X3GQX0\u002fzGV4Pf4\u002ffz9PpwlWMrCAP0MoJrCX0oE\u002fn+2dzFoIgz8RBTjruFKEP9Ox6rkBs4U\u002fSCdvI5gqhz9b5N8c87qIPxc8hnCeZYo\u002fkhytgzssjD\u002fJfvgUghCOPxDtMHggCpA\u002ffyTJSq8ckT+gZnHm7ECSP\u002fI9aRPld5M\u002f\u002feagMLDClD9XU51WcyKWPwiFv2dgmJc\u002fO0\u002fDCrYlmT+zlP+Lv8uaP4+GqKHUi5w\u002f\u002fgIND1lnnj9FlEIR3i+gP44YxgQ8O6E\u002fgeAme4hWoj+BXeUIioKjP1250xoLwKQ\u002fNbpcNNkPpj8RlsUMxHKnPw1\u002fJomc6ag\u002f0d8JkTN1qj9T6uy7WBasP0I5QNbYza0\u002fyY0HPXycrz+KTOuHgsGwP84I1JwWwbE\u002fApb2KFLNsj\u002f28xqshuazP8ye+ib+DLU\u002f8xSS2flAtj8wme\u002f1sIK3P3czQktP0rg\u002fopil7fMvuj+YBuDfr5u7P3uP98SEFb0\u002fX\u002fYkoGOdvj\u002f6TRnVlRnAP9R13qFU68A\u002fgQ0DhsrDwT+\u002fAAySyaLCP5083lIZiMM\u002fRmCwqnZzxD+NQ5\u002fKk2TFPw4nkk8YW8Y\u002fzyp4hKFWxz9weyXLwlbIP00XOCwGW8k\u002fb5R4De1iyj+VACsN8W3LPzJHwP+Ee8w\u002fAs9bDBaLzT8M9bXjDJzOP7IVFQzPrc8\u002fpcM2HuBf0D9goR7goejQP+n7t+7ecNE\u002fJLnDmkr40T\u002ffr7UNmn7SP+Ze0OuEA9M\u002fev2b6sWG0z9MID1ZGwjUP37hnJhHh9Q\u002fDi7OgREE1T+LPpy5RH7VP0EGsPCx9dU\u002f9YE4EC9q1j8Ez3dTl9vWPyoWAU\u002fLSdc\u002ff8HO5rC01z8UO6M0MxzYPzeWX2BCgNg\u002fjOYibNPg2D8UThv23z3ZPys7+PFll9k\u002fa63lW2ft2T9huOTm6T\u002faP3UgOKj2jto\u002f2SR3wZna2j+JjK4K4iLbP77syr3gZ9s\u002f7rVYJKmp2z9mdXhIUOjbP0cxuansI9w\u002fGNdg9pVc3D9hH4TJZJLcP4WKLG5yxdw\u002fQ3qsp9j13D+C\u002fyZ\u002fsSPdP+GuOhYXT90\u002fooGsfiN43T8KMuGW8J7dPwZP6OqXw90\u002fYxnUmTLm3T\u002f\u002fuxM\u002f2QbeP6U7gt+jJd4\u002fnDXb2alC3j8u2EbaAV7eP8IesNDBd94\u002fn\u002fya6f6P3j88hzOJzabeP2ojU0hBvN4\u002fCQo982zQ3j\u002ffANiJYuPePyXDL0Ez9d4\u002fVDANhu8F3z9E23gApxXfP3Hu\u002f5doJN8\u002f5oGXeEIy3z8Ua\u002f4XQj\u002ffP1Y5gTt0S98\u002fo3gH\u002fuRW3z+fdVTWn2HfP7WbaJ2va98\u002fiCDzlB513z+yCsZt9n3fP7W\u002fQE5Aht8\u002fSCmn2ASO3z+iMV0xTJXfP8LT\u002fwQenN8\u002fYk1WjoGi3z\u002fPJhecfajfP\u002fHFfZYYrt8\u002fMCGuhFiz3z\u002fP5uQRQ7jfP48Sc5LdvN8\u002ftHeECC3B3z9PPLEoNsXfPxSmWl79yN8\u002ftOvTz4bM3z8bAVhi1s\u002ffPwyMzb3v0t8\u002fo1laUNbV3z82z8dRjdjfPwbqucYX298\u002fZHO6g3jd3z+EGhowst\u002ffPxUsqUjH4d8\u002feKFJIrrj3z9iQFzsjOXfP6F+CrNB598\u002fItduYdro3z\u002fVM53DWOrfP4EFjYi+698\u002f"},"type":"scatter"}],                        {"template":{"layout":{"font":{"color":"#333333","family":"Alegreya, serif","size":14},"legend":{"font":{"size":12}},"title":{"font":{"size":20}},"xaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}},"yaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}}}},"legend":{"bgcolor":"rgba(255,255,255,0.5)","bordercolor":"#ccc","borderwidth":1,"x":0.02,"y":0.98,"xanchor":"left","yanchor":"top"},"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified","updatemenus":[{"bgcolor":"white","buttons":[{"args":[{"visible":[true,true,false,false,false,false,false,false]}],"label":"Linear","method":"update"},{"args":[{"visible":[false,false,true,true,false,false,false,false]}],"label":"Convex-doom (g convex, h concave)","method":"update"},{"args":[{"visible":[false,false,false,false,true,true,false,false]}],"label":"Concave-doom (g concave, h convex)","method":"update"},{"args":[{"visible":[false,false,false,false,false,false,true,true]}],"label":"Overhang (sigmoidal)","method":"update"}],"direction":"down","type":"buttons","x":1.0,"xanchor":"left","y":1.0}],"xaxis":{"title":{"text":"Compute spend 𝒞"}},"yaxis":{"title":{"text":"Hazard rate"}},"height":420},                        {"responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('b1cc5a42-3878-4930-b622-368fe6088753');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-hazard-shapes-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Response functions g (doom hazard, red) and h (safety hazard, blue) as functions of spend under four shape assumptions. Use the buttons to switch scenarios.
</figcaption>
</figure>
</div>
</div>
</section>
<section id="three-outcomes-of-the-race" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="three-outcomes-of-the-race"><span class="header-section-number">3</span> Three outcomes of the race</h2>
<p>The probability that the race remains in limbo at time <img src="https://latex.codecogs.com/png.latex?t"> — because neither event has occurred — is the <em>overall survival function</em>, the survival of the minimum:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(t)%20=%20%5Cexp%5C!%5Cleft%5B-%5CLambda_d(t)%20-%20%5CLambda_s(t)%5Cright%5D,%0A"></p>
<p>In words: the probability of still being in limbo decays exponentially as the hazard accumulates. Here <img src="https://latex.codecogs.com/png.latex?%5CLambda_d(t)%20=%20%5Cint_0%5Et%20g(%5Cmathcal%7BC%7D_c(u))%5C,%20du"> and <img src="https://latex.codecogs.com/png.latex?%5CLambda_s(t)%20=%20%5Cint_0%5Et%20h(%5Cmathcal%7BC%7D_s(u))%5C,%20du"> are cumulative hazard functions. Note the nested structure: each cumulative hazard is an integral over time of a function that itself contains an integral over time.</p>
<p>The race has three mutually exclusive <em>outcomes</em>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(%5Ctext%7Bdoom%7D)%20+%20P(%5Ctext%7Bdeliverance%7D)%20+%20P(%5Ctext%7Blimbo%7D)%20=%201,%0A"></p>
<p>where:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)%20=%20%5Cint_0%5E%5Cinfty%20%5Clambda_d(t)%5C,%20S(t)%5C,%20dt"> — the doom event fires first,</li>
<li><img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdeliverance%7D)%20=%20%5Cint_0%5E%5Cinfty%20%5Clambda_s(t)%5C,%20S(t)%5C,%20dt"> — the deliverance event fires first,</li>
<li><img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Blimbo%7D)%20=%20%5Clim_%7Bt%5Cto%5Cinfty%7D%20S(t)"> — neither event ever fires.</li>
</ul>
<p>In the model <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(t)"> grows without bound, and the spend in each pool grows too as long as <img src="https://latex.codecogs.com/png.latex?%5Calpha"> stays away from 0 and 1. So whichever of <img src="https://latex.codecogs.com/png.latex?g">, <img src="https://latex.codecogs.com/png.latex?h"> is not identically zero is eventually bounded below by a positive constant, <img src="https://latex.codecogs.com/png.latex?%5CLambda_d(t)%20+%20%5CLambda_s(t)%20%5Cto%20%5Cinfty">, and <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Blimbo%7D)%20=%200">: the race always resolves eventually. But “eventually” can be a very long time. Over any finite horizon there is residual probability mass on “neither yet” — the green curve <img src="https://latex.codecogs.com/png.latex?S(t)"> in the plots below — and a world where <img src="https://latex.codecogs.com/png.latex?S(t)"> stays large at human-relevant timescales is one where we just <em>muddle through</em> indefinitely, which is arguably closer to the standard baseline expectation than either doom or deliverance.</p>
<p>At each instant <img src="https://latex.codecogs.com/png.latex?t">, while we’re still in limbo, the probability that <em>some</em> event fires within <img src="https://latex.codecogs.com/png.latex?%5Bt,%20t+dt)"> is <img src="https://latex.codecogs.com/png.latex?(%5Clambda_d(t)%20+%20%5Clambda_s(t))%5C,%20dt">, and the conditional probability that the firing event is doom rather than deliverance is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi_d(t)%20=%20%5Cfrac%7B%5Clambda_d(t)%7D%7B%5Clambda_d(t)%20+%20%5Clambda_s(t)%7D.%0A"></p>
<p>So the probability of doom can also be written</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(%5Ctext%7Bdoom%7D)%20=%20%5Cint_0%5E%5Cinfty%20%5Cpi_d(t)%20%5Cbigl%5B%5Clambda_d(t)%20+%20%5Clambda_s(t)%5Cbigr%5D%20S(t)%5C,%20dt,%0A"></p>
<p>which decomposes into, “probability we’re still waiting at time <img src="https://latex.codecogs.com/png.latex?t">” × “probability <em>something</em> happens right now” × “probability that the something is doom rather than deliverance.” The middle two factors, <img src="https://latex.codecogs.com/png.latex?%5B%5Clambda_d(t)%20+%20%5Clambda_s(t)%5D%5C,%20S(t)">, represent the probability density of the resolution time <img src="https://latex.codecogs.com/png.latex?T%5E*">, so the integral is an expectation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(%5Ctext%7Bdoom%7D)%20=%20%5Cmathbb%7BE%7D%5Cbigl%5B%5Cpi_d(T%5E*)%5Cbigr%5D.%0A"></p>
<p>Doom probability is the conditional ratio <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> averaged over the moment the race resolves. For constant <img src="https://latex.codecogs.com/png.latex?%5Calpha">, both hazards are functions of the total spend <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(t)"> alone, so we can equally write <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)%20=%20%5Cmathbb%7BE%7D%5B%5Cpi_d(%5Cmathcal%7BC%7D(T%5E*))%5D">, an average over the spend at which the race resolves. Everything below is a corollary of this identity. The split <img src="https://latex.codecogs.com/png.latex?%5Calpha"> sets the shape of <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> as a function of spend. The distribution of the resolution spend <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)"> depends on the growth rate of <img src="https://latex.codecogs.com/png.latex?c(t)">, the overall scale of the hazards, any pause, and also on <img src="https://latex.codecogs.com/png.latex?%5Calpha">, since the split changes the total hazard at each spend too. So pacing can only move the distribution of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)"> that we take the expectation over; allocation moves both that distribution and the <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> being averaged.</p>
<div id="cell-fig-race-dynamics" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1">alpha_race <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb3-2">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">35</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">800</span>)</span>
<span id="cb3-3">c_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.exp(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t)                          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute throughput c(t) in FLOP/s</span></span>
<span id="cb3-4">Cc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha_race) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c_rate, t, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 𝒞_c(t)</span></span>
<span id="cb3-5">Cs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid(alpha_race <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c_rate, t, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 𝒞_s(t)</span></span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Response functions applied to spends</span></span>
<span id="cb3-8">ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.005</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> Cc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># superlinear doom</span></span>
<span id="cb3-9">ls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.015</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> Cs           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># linear safety</span></span>
<span id="cb3-10"></span>
<span id="cb3-11">cumhaz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid(ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> ls, t, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb3-12">S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>cumhaz)</span>
<span id="cb3-13">doom_density <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S</span>
<span id="cb3-14">deliv_density <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S</span>
<span id="cb3-15">P_doom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.trapezoid(doom_density, t)</span>
<span id="cb3-16">P_deliv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.trapezoid(deliv_density, t)</span>
<span id="cb3-17">P_limbo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> P_doom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> P_deliv</span>
<span id="cb3-18"></span>
<span id="cb3-19">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> make_subplots(specs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[[{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"secondary_y"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>}]])</span>
<span id="cb3-20">fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>deliv_density, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"λ_s S(t)  deliverance density"</span>,</span>
<span id="cb3-21">                         fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tozeroy'</span>, line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_SALV, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb3-22">                         fillcolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rgba(36,113,163,0.35)'</span>,</span>
<span id="cb3-23">                         hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.1f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  deliv=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.4f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb3-24">              secondary_y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-25">fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>doom_density, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"λ_d S(t)  doom density"</span>,</span>
<span id="cb3-26">                         fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tozeroy'</span>, line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_DOOM, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb3-27">                         fillcolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rgba(192,57,43,0.35)'</span>,</span>
<span id="cb3-28">                         hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.1f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  doom=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.4f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb3-29">              secondary_y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-30">fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>S, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S(t)  limbo"</span>,</span>
<span id="cb3-31">                         line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_SURV, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>),</span>
<span id="cb3-32">                         hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.1f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  S=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.3f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb3-33">              secondary_y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-34"></span>
<span id="cb3-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Annotate all three outcomes</span></span>
<span id="cb3-36">fig.add_annotation(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t[np.argmax(doom_density)], y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(doom_density) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.15</span>,</span>
<span id="cb3-37">                   text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"P(doom) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>P_doom<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, showarrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb3-38">                   font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_DOOM))</span>
<span id="cb3-39">fig.add_annotation(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t[np.argmax(deliv_density)], y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(deliv_density) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.15</span>,</span>
<span id="cb3-40">                   text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"P(deliverance) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>P_deliv<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, showarrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb3-41">                   font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_SALV))</span>
<span id="cb3-42">fig.add_annotation(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span>, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, yref<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y2"</span>,</span>
<span id="cb3-43">                   text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"P(limbo) = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>P_limbo<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, showarrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb3-44">                   font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>C_SURV))</span>
<span id="cb3-45"></span>
<span id="cb3-46">fig.update_xaxes(title_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Time t"</span>)</span>
<span id="cb3-47">fig.update_yaxes(title_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Event density"</span>, secondary_y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-48">fig.update_yaxes(title_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"S(t) — limbo"</span>, secondary_y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.05</span>])</span>
<span id="cb3-49">fig.update_layout(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>LAYOUT, height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">440</span>)</span>
<span id="cb3-50">fig.show()</span></code></pre></div></div>
</details>
<div id="fig-race-dynamics" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-race-dynamics-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div style="height:440px; width:100%;">            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-svg.min.js"></script><script>if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script>window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-4.0.0.min.js" integrity="sha256-FEYfO0yRyLtZCpnW0Dw/0DHKQO7Afrq3ml4+rBB818o=" crossorigin="anonymous"></script>                <div id="74e314b4-6b8d-43c4-8305-b1b19087c814" class="plotly-graph-div" style="height:100%; width:100%;"></div>            <script>                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("74e314b4-6b8d-43c4-8305-b1b19087c814")) {                    Plotly.newPlot(                        "74e314b4-6b8d-43c4-8305-b1b19087c814",                        [{"fill":"tozeroy","fillcolor":"rgba(36,113,163,0.35)","hovertemplate":"t=%{x:.1f}  deliv=%{y:.4f}","line":{"color":"#2471a3","width":1},"name":"λ_s S(t)  deliverance density","x":{"dtype":"f8","bdata":"AAAAAAAAAADwrmK0k22mP\u002fCuYrSTbbY\u002fNANKxy7SwD\u002fwrmK0k23GP6xae6H4CMw\u002fNANKxy7S0D8SWdY94Z\u002fTP\u002fCuYrSTbdY\u002fzgTvKkY72T+sWnuh+AjcP4qwBxir1t4\u002fNANKxy7S4D8jLpACCDniPxJZ1j3hn+M\u002fAYQceboG5T\u002fwrmK0k23mP9\u002fZqO9s1Oc\u002fzgTvKkY76T+9LzVmH6LqP6xae6H4COw\u002fm4XB3NFv7T+KsAcYq9buP7ztpinCHvA\u002fNANKxy7S8D+sGO1km4XxPyMukAIIOfI\u002fmkMzoHTs8j8SWdY94Z\u002fzP4puedtNU\u002fQ\u002fAYQceboG9T94mb8WJ7r1P\u002fCuYrSTbfY\u002faMQFUgAh9z\u002ff2ajvbNT3P1bvS43Zh\u002fg\u002fzgTvKkY7+T9GGpLIsu75P70vNWYfovo\u002fNEXYA4xV+z+sWnuh+Aj8PyRwHj9lvPw\u002fm4XB3NFv\u002fT8Sm2R6PiP+P4qwBxir1v4\u002fAsaqtReK\u002fz+87aYpwh4AQHh4eHh4eABANANKxy7SAEDwjRsW5SsBQKwY7WSbhQFAZ6O+s1HfAUAjLpACCDkCQN+4YVG+kgJAmkMzoHTsAkBWzgTvKkYDQBJZ1j3hnwNAzuOnjJf5A0CKbnnbTVMEQEX5SioErQRAAYQceboGBUC9Du7HcGAFQHiZvxYnugVANCSRZd0TBkDwrmK0k20GQKw5NANKxwZAaMQFUgAhB0AjT9egtnoHQN\u002fZqO9s1AdAm2R6PiMuCEBW70uN2YcIQBJ6HdyP4QhAzgTvKkY7CUCKj8B5\u002fJQJQEYaksiy7glAAaVjF2lICkC9LzVmH6IKQHm6BrXV+wpANEXYA4xVC0Dwz6lSQq8LQKxae6H4CAxAaOVM8K5iDEAkcB4\u002fZbwMQN\u002f6740bFg1Am4XB3NFvDUBXEJMriMkNQBKbZHo+Iw5AziU2yfR8DkCKsAcYq9YOQEY72WZhMA9AAsaqtReKD0C9UHwEzuMPQLztpinCHhBAGrMPUZ1LEEB4eHh4eHgQQNY94Z9TpRBANANKxy7SEECSyLLuCf8QQPCNGxblKxFATlOEPcBYEUCsGO1km4URQAneVYx2shFAZ6O+s1HfEUDFaCfbLAwSQCMukAIIORJAgfP4KeNlEkDfuGFRvpISQD1+yniZvxJAmkMzoHTsEkD4CJzHTxkTQFbOBO8qRhNAtJNtFgZzE0ASWdY94Z8TQHAeP2W8zBNAzuOnjJf5E0AsqRC0ciYUQIpuedtNUxRA5zPiAimAFEBF+UoqBK0UQKO+s1Hf2RRAAYQceboGFUBfSYWglTMVQL0O7sdwYBVAG9RW70uNFUB4mb8WJ7oVQNZeKD4C5xVANCSRZd0TFkCS6fmMuEAWQPCuYrSTbRZATnTL226aFkCsOTQDSscWQAr\u002fnCol9BZAaMQFUgAhF0DFiW55200XQCNP16C2ehdAgRRAyJGnF0Df2ajvbNQXQD2fERdIARhAm2R6PiMuGED5KeNl\u002floYQFbvS43ZhxhAtLS0tLS0GEASeh3cj+EYQHA\u002fhgNrDhlAzgTvKkY7GUAsyldSIWgZQIqPwHn8lBlA6FQpodfBGUBGGpLIsu4ZQKPf+u+NGxpAAaVjF2lIGkBfasw+RHUaQL0vNWYfohpAG\u002fWdjfrOGkB5uga11fsaQNd\u002fb9ywKBtANEXYA4xVG0CSCkErZ4IbQPDPqVJCrxtATpUSeh3cG0CsWnuh+AgcQAog5MjTNRxAaOVM8K5iHEDGqrUXio8cQCRwHj9lvBxAgTWHZkDpHEDf+u+NGxYdQD3AWLX2Qh1Am4XB3NFvHUD5SioErZwdQFcQkyuIyR1AtdX7UmP2HUASm2R6PiMeQHBgzaEZUB5AziU2yfR8HkAs657wz6keQIqwBxir1h5A6HVwP4YDH0BGO9lmYTAfQKQAQo48XR9AAsaqtReKH0BfixPd8rYfQL1QfATO4x9ADovylVQIIEC87aYpwh4gQGtQW70vNSBAGrMPUZ1LIEDJFcTkCmIgQHh4eHh4eCBAJ9ssDOaOIEDWPeGfU6UgQIWglTPBuyBANANKxy7SIEDjZf5anOggQJLIsu4J\u002fyBAQStngncVIUDwjRsW5SshQJ\u002fwz6lSQiFATlOEPcBYIUD9tTjRLW8hQKwY7WSbhSFAWnuh+AicIUAJ3lWMdrIhQLhACiDkyCFAZ6O+s1HfIUAWBnNHv\u002fUhQMVoJ9ssDCJAdMvbbpoiIkAjLpACCDkiQNKQRJZ1TyJAgfP4KeNlIkAwVq29UHwiQN+4YVG+kiJAjhsW5SupIkA9fsp4mb8iQOzgfgwH1iJAmkMzoHTsIkBJpucz4gIjQPgInMdPGSNAp2tQW70vI0BWzgTvKkYjQAUxuYKYXCNAtJNtFgZzI0Bj9iGqc4kjQBJZ1j3hnyNAwbuK0U62I0BwHj9lvMwjQB+B8\u002fgp4yNAzuOnjJf5I0B9RlwgBRAkQCypELRyJiRA2wvFR+A8JECKbnnbTVMkQDjRLW+7aSRA5zPiAimAJECWlpaWlpYkQEX5SioErSRA9Fv\u002fvXHDJECjvrNR39kkQFIhaOVM8CRAAYQceboGJUCw5tAMKB0lQF9JhaCVMyVADqw5NANKJUC9Du7HcGAlQGxxolvediVAG9RW70uNJUDKNguDuaMlQHiZvxYnuiVAJ\u002fxzqpTQJUDWXig+AuclQIXB3NFv\u002fSVANCSRZd0TJkDjhkX5SiomQJLp+Yy4QCZAQUyuICZXJkDwrmK0k20mQJ8RF0gBhCZATnTL226aJkD91n9v3LAmQKw5NANKxyZAW5zolrfdJkAK\u002f5wqJfQmQLlhUb6SCidAaMQFUgAhJ0AWJ7rlbTcnQMWJbnnbTSdAdOwiDUlkJ0AjT9egtnonQNKxizQkkSdAgRRAyJGnJ0Awd\u002fRb\u002f70nQN\u002fZqO9s1CdAjjxdg9rqJ0A9nxEXSAEoQOwBxqq1FyhAm2R6PiMuKEBKxy7SkEQoQPkp42X+WihAqIyX+WtxKEBW70uN2YcoQAVSACFHnihAtLS0tLS0KEBjF2lIIssoQBJ6HdyP4ShAwdzRb\u002f33KEBwP4YDaw4pQB+iOpfYJClAzgTvKkY7KUB9Z6O+s1EpQCzKV1IhaClA2ywM5o5+KUCKj8B5\u002fJQpQDnydA1qqylA6FQpodfBKUCXt900RdgpQEYaksiy7ilA9HxGXCAFKkCj3\u002frvjRsqQFJCr4P7MSpAAaVjF2lIKkCwBxir1l4qQF9qzD5EdSpADs2A0rGLKkC9LzVmH6IqQGyS6fmMuCpAG\u002fWdjfrOKkDKV1IhaOUqQHm6BrXV+ypAKB27SEMSK0DXf2\u002fcsCgrQIbiI3AePytANEXYA4xVK0Djp4yX+WsrQJIKQStngitAQW31vtSYK0Dwz6lSQq8rQJ8yXuavxStATpUSeh3cK0D998YNi\u002fIrQKxae6H4CCxAW70vNWYfLEAKIOTI0zUsQLmCmFxBTCxAaOVM8K5iLEAXSAGEHHksQMaqtReKjyxAdQ1qq\u002felLEAkcB4\u002fZbwsQNLS0tLS0ixAgTWHZkDpLEAwmDv6rf8sQN\u002f6740bFi1Ajl2kIYksLUA9wFi19kItQOwiDUlkWS1Am4XB3NFvLUBK6HVwP4YtQPlKKgStnC1AqK3elxqzLUBXEJMriMktQAZzR7\u002f13y1AtdX7UmP2LUBkOLDm0AwuQBKbZHo+Iy5Awf0YDqw5LkBwYM2hGVAuQB\u002fDgTWHZi5AziU2yfR8LkB9iOpcYpMuQCzrnvDPqS5A201ThD3ALkCKsAcYq9YuQDkTvKsY7S5A6HVwP4YDL0CX2CTT8xkvQEY72WZhMC9A9Z2N+s5GL0CkAEKOPF0vQFNj9iGqcy9AAsaqtReKL0CwKF9JhaAvQF+LE93yti9ADu7HcGDNL0C9UHwEzuMvQGyzMJg7+i9ADovylVQIMEBlvMxfixMwQLztpinCHjBAFB+B8\u002fgpMEBrUFu9LzUwQMOBNYdmQDBAGrMPUZ1LMEBy5Oka1FYwQMkVxOQKYjBAIUeerkFtMEB4eHh4eHgwQNCpUkKvgzBAJ9ssDOaOMEB\u002fDAfWHJowQNY94Z9TpTBALm+7aYqwMECFoJUzwbswQN3Rb\u002f33xjBANANKxy7SMECLNCSRZd0wQONl\u002flqc6DBAOpfYJNPzMECSyLLuCf8wQOn5jLhACjFAQStngncVMUCYXEFMriAxQPCNGxblKzFAR7\u002f13xs3MUCf8M+pUkIxQPYhqnOJTTFATlOEPcBYMUClhF4H92MxQP21ONEtbzFAVOcSm2R6MUCsGO1km4UxQANKxy7SkDFAWnuh+AicMUCyrHvCP6cxQAneVYx2sjFAYQ8wVq29MUC4QAog5MgxQBBy5Oka1DFAZ6O+s1HfMUC\u002f1Jh9iOoxQBYGc0e\u002f9TFAbjdNEfYAMkDFaCfbLAwyQB2aAaVjFzJAdMvbbpoiMkDM\u002fLU40S0yQCMukAIIOTJAel9qzD5EMkDSkESWdU8yQCnCHmCsWjJAgfP4KeNlMkDYJNPzGXEyQDBWrb1QfDJAh4eHh4eHMkDfuGFRvpIyQDbqOxv1nTJAjhsW5SupMkDlTPCuYrQyQD1+yniZvzJAlK+kQtDKMkDs4H4MB9YyQEMSWdY94TJAmkMzoHTsMkDydA1qq\u002fcyQEmm5zPiAjNAodfB\u002fRgOM0D4CJzHTxkzQFA6dpGGJDNAp2tQW70vM0D\u002fnCol9DozQFbOBO8qRjNArv\u002feuGFRM0AFMbmCmFwzQF1ik0zPZzNAtJNtFgZzM0AMxUfgPH4zQGP2IapziTNAuyf8c6qUM0ASWdY94Z8zQGmKsAcYqzNAwbuK0U62M0AY7WSbhcEzQHAeP2W8zDNAx08ZL\u002fPXM0AfgfP4KeMzQHayzcJg7jNAzuOnjJf5M0AlFYJWzgQ0QH1GXCAFEDRA1Hc26jsbNEAsqRC0ciY0QIPa6n2pMTRA2wvFR+A8NEAyPZ8RF0g0QIpuedtNUzRA4Z9TpYReNEA40S1vu2k0QJACCDnydDRA5zPiAimANEA\u002fZbzMX4s0QJaWlpaWljRA7sdwYM2hNEBF+UoqBK00QJ0qJfQ6uDRA9Fv\u002fvXHDNEBMjdmHqM40QKO+s1Hf2TRA+++NGxblNEBSIWjlTPA0QKpSQq+D+zRAAYQceboGNUBYtfZC8RE1QLDm0AwoHTVABxir1l4oNUBfSYWglTM1QLZ6X2rMPjVADqw5NANKNUBl3RP+OVU1QL0O7sdwYDVAFEDIkadrNUBscaJb3nY1QMOifCUVgjVAG9RW70uNNUByBTG5gpg1QMo2C4O5ozVAIWjlTPCuNUB4mb8WJ7o1QNDKmeBdxTVAJ\u002fxzqpTQNUB\u002fLU50y9s1QNZeKD4C5zVALpACCDnyNUCFwdzRb\u002f01QN3ytpumCDZANCSRZd0TNkCMVWsvFB82QOOGRflKKjZAO7gfw4E1NkCS6fmMuEA2QOoa1FbvSzZAQUyuICZXNkCZfYjqXGI2QPCuYrSTbTZAR+A8fsp4NkCfERdIAYQ2QPZC8RE4jzZATnTL226aNkClpaWlpaU2QP3Wf2\u002fcsDZAVAhaORO8NkCsOTQDSsc2QANrDs2A0jZAW5zolrfdNkCyzcJg7ug2QAr\u002fnCol9DZAYTB39Fv\u002fNkC5YVG+kgo3QBCTK4jJFTdAaMQFUgAhN0C\u002f9d8bNyw3QBYnuuVtNzdAbliUr6RCN0DFiW552003QB27SEMSWTdAdOwiDUlkN0DMHf3Wf283QCNP16C2ejdAe4Cxau2FN0DSsYs0JJE3QCrjZf5anDdAgRRAyJGnN0DZRRqSyLI3QDB39Fv\u002fvTdAiKjOJTbJN0Df2ajvbNQ3QDYLg7mj3zdAjjxdg9rqN0DlbTdNEfY3QD2fERdIAThAlNDr4H4MOEDsAcaqtRc4QEMzoHTsIjhAm2R6PiMuOEDylVQIWjk4QErHLtKQRDhAofgInMdPOED5KeNl\u002flo4QFBbvS81ZjhAqIyX+WtxOED\u002fvXHDonw4QFbvS43ZhzhAriAmVxCTOEAFUgAhR544QF2D2up9qThAtLS0tLS0OEAM5o5+6784QGMXaUgiyzhAu0hDElnWOEASeh3cj+E4QGqr96XG7DhAwdzRb\u002f33OEAZDqw5NAM5QHA\u002fhgNrDjlAyHBgzaEZOUAfojqX2CQ5QHfTFGEPMDlAzgTvKkY7OUAlNsn0fEY5QH1no76zUTlA1Jh9iOpcOUAsyldSIWg5QIP7MRxYczlA2ywM5o5+OUAyXuavxYk5QIqPwHn8lDlA4cCaQzOgOUA58nQNaqs5QJAjT9egtjlA6FQpodfBOUA\u002fhgNrDs05QJe33TRF2DlA7ui3\u002fnvjOUBGGpLIsu45QJ1LbJLp+TlA9HxGXCAFOkBMriAmVxA6QKPf+u+NGzpA+xDVucQmOkBSQq+D+zE6QKpziU0yPTpAAaVjF2lIOkBZ1j3hn1M6QLAHGKvWXjpACDnydA1qOkBfasw+RHU6QLebpgh7gDpADs2A0rGLOkBm\u002flqc6JY6QL0vNWYfojpAFGEPMFatOkBskun5jLg6QMPDw8PDwzpAG\u002fWdjfrOOkByJnhXMdo6QMpXUiFo5TpAIYks657wOkB5uga11fs6QNDr4H4MBztAKB27SEMSO0B\u002fTpUSeh07QNd\u002fb9ywKDtALrFJpuczO0CG4iNwHj87QN0T\u002fjlVSjtANEXYA4xVO0CMdrLNwmA7QOOnjJf5aztAO9lmYTB3O0CSCkErZ4I7QOo7G\u002fWdjTtAQW31vtSYO0CZns+IC6Q7QPDPqVJCrztASAGEHHm6O0CfMl7mr8U7QPdjOLDm0DtATpUSeh3cO0CmxuxDVOc7QP33xg2L8jtAVSmh18H9O0CsWnuh+Ag8QAOMVWsvFDxAW70vNWYfPECy7gn\u002fnCo8QAog5MjTNTxAYVG+kgpBPEC5gphcQUw8QBC0ciZ4VzxAaOVM8K5iPEC\u002fFie65W08QBdIAYQceTxAbnnbTVOEPEDGqrUXio88QB3cj+HAmjxAdQ1qq\u002felPEDMPkR1LrE8QCRwHj9lvDxAe6H4CJzHPEDS0tLS0tI8QCoErZwJ3jxAgTWHZkDpPEDZZmEwd\u002fQ8QDCYO\u002fqt\u002fzxAiMkVxOQKPUDf+u+NGxY9QDcsyldSIT1Ajl2kIYksPUDmjn7rvzc9QD3AWLX2Qj1AlfEyfy1OPUDsIg1JZFk9QERU5xKbZD1Am4XB3NFvPUDytpumCHs9QErodXA\u002fhj1AoRlQOnaRPUD5SioErZw9QFB8BM7jpz1AqK3elxqzPUD\u002f3rhhUb49QFcQkyuIyT1ArkFt9b7UPUAGc0e\u002f9d89QF2kIYks6z1AtdX7UmP2PUAMB9YcmgE+QGQ4sObQDD5Au2mKsAcYPkASm2R6PiM+QGrMPkR1Lj5Awf0YDqw5PkAZL\u002fPX4kQ+QHBgzaEZUD5AyJGna1BbPkAfw4E1h2Y+QHf0W\u002f+9cT5AziU2yfR8PkAmVxCTK4g+QH2I6lxikz5A1bnEJpmePkAs657wz6k+QIQceboGtT5A201ThD3APkAzfy1OdMs+QIqwBxir1j5A4eHh4eHhPkA5E7yrGO0+QJBElnVP+D5A6HVwP4YDP0A\u002fp0oJvQ4\u002fQJfYJNPzGT9A7gn\u002fnColP0BGO9lmYTA\u002fQJ1sszCYOz9A9Z2N+s5GP0BMz2fEBVI\u002fQKQAQo48XT9A+zEcWHNoP0BTY\u002fYhqnM\u002fQKqU0Ovgfj9AAsaqtReKP0BZ94R\u002fTpU\u002fQLAoX0mFoD9ACFo5E7yrP0BfixPd8rY\u002fQLe87aYpwj9ADu7HcGDNP0BmH6I6l9g\u002fQL1QfATO4z9AFYJWzgTvP0BsszCYO\u002fo\u002fQGJyBTG5AkBADovylVQIQEC5o9\u002f67w1AQGW8zF+LE0BAEdW5xCYZQEC87aYpwh5AQGgGlI5dJEBAFB+B8\u002fgpQEDAN25YlC9AQGtQW70vNUBAF2lIIss6QEDDgTWHZkBAQG+aIuwBRkBAGrMPUZ1LQEDGy\u002fy1OFFAQHLk6RrUVkBAHv3Wf29cQEDJFcTkCmJAQHUusUmmZ0BAIUeerkFtQEDNX4sT3XJAQHh4eHh4eEBAJJFl3RN+QEDQqVJCr4NAQHvCP6dKiUBAJ9ssDOaOQEDT8xlxgZRAQH8MB9YcmkBAKiX0OrifQEDWPeGfU6VAQIJWzgTvqkBALm+7aYqwQEDZh6jOJbZAQIWglTPBu0BAMbmCmFzBQEDd0W\u002f998ZAQIjqXGKTzEBANANKxy7SQEDgGzcsytdAQIs0JJFl3UBAN00R9gDjQEDjZf5anOhAQI9+67837kBAOpfYJNPzQEDmr8WJbvlAQJLIsu4J\u002f0BAPuGfU6UEQUDp+Yy4QApBQJUSeh3cD0FAQStngncVQUDtQ1TnEhtBQJhcQUyuIEFARHUusUkmQUDwjRsW5StBQJumCHuAMUFAR7\u002f13xs3QUDz1+JEtzxBQJ\u002fwz6lSQkFASgm9Du5HQUD2IapziU1BQKI6l9gkU0FATlOEPcBYQUD5a3GiW15BQKWEXgf3Y0FAUZ1LbJJpQUD9tTjRLW9BQKjOJTbJdEFAVOcSm2R6QUAAAAAAAIBBQA=="},"y":{"dtype":"f8","bdata":"AAAAAAAAAAA2emH\u002f+JM1P5I2rAH6n0U\u002fPH\u002fpDfNAUD9Og\u002fGv1rdVP2s+TKudNFs\u002fm1axnp5bYD9i\u002fxcN1R9jP+xJ4jTs5mU\u002fcXDi4t2waD+yMnKbo31rP+J2Lpk2TW4\u002f6JXY5cePcD\u002fBxCTrU\u002fpxP3pnWYc7ZnM\u002fi87kvXrTdD89CCBqDUJ2P21eqD7vsXc\u002feFe4xBsjeT9eTYBbjpV6P++ufjdCCXw\u002fyv\u002fXYTJ+fT9xrK63WfR+P4lkvXTZNYA\u002fAOkwPRzygD\u002f+Qsdf8q6BP7Z5xO9YbII\u002fI2nH6Ewqgz\u002fCInYuy+iDP\u002faPKozQp4Q\u002ff2SftFlnhT\u002fab51BYyeGP9lcqbPp54Y\u002fbe+xcemohz\u002fk0L7IXmqIP6X7n+tFLIk\u002fsted8pruiT8GGirbWbGKPz95kYd+dIs\u002fsEquvgQ4jD9qG5wr6PuMP21ZbF0kwI0\u002fvyHcxrSEjj\u002fMSAu+lEmPP1xZGr5fB5A\u002fNAm0DhhqkD8JkyLQ8MyQP5RE3HLnL5E\u002fxxldWPmSkT+23wbTI\u002faRPzxHAiZkWZI\u002fofQghbe8kj9XmcAUGyCTP5slr+mLg5M\u002fvR8QCQfnkz9nL0NoiUqUPzrry+wPrpQ\u002fxPc6bJcRlT+thhisHHWVP7lF0GGc2JU\u002fKM2eMhM8lj9pnYCzfZ+WP4S8ImnYApc\u002fowPVxx9mlz+OLX4zUMmXPz23kf9lLJg\u002fzKMHb12PmD9nNVa0MvKYPxmtbfHhVJk\u002ffSO2N2e3mT\u002fJjA+IvhmaP6L609Lje5o\u002fji7c99Ldmj8IkIbGhz+bP1yZwP39oJs\u002foM4STDECnD92Uq9PHWOcPyEsg5a9w5w\u002f3lJKng0knT+CkqbUCISdP2RdOZeq450\u002fwp\u002fAM+5Cnj8IqTbozqGePx0\u002f9eJHAJ8\u002fRfDbQlRenz\u002fjt3kX77ufP0MFnbCJDKA\u002f3q7KCN46oD\u002fqn6IF8migPwMKPhHDlqA\u002ffkOQjk7EoD\u002f9Z4bZkfGgPzbpKEeKHqE\u002f0hm\u002fJTVLoT8bvPS8j3ehPzWeAU6Xo6E\u002fSE3TE0nPoT8I6ThDovqhP78gEQugJaI\u002f62J6lD9Qoj8eSAUDfnqiP8VB6XRYpKI\u002fNpU7A8zNoj\u002fnqijC1faiP8W5L8FyH6M\u002fEdZgC6BHoz\u002fWap2nWm+jP+Qk25iflqM\u002fnVVp3mu9oz+w0zh0vOOjP01fJlOOCaQ\u002f\u002fY5Hcd4upD\u002fdSTrCqVOkP1TTdjftd6Q\u002fB2ykwKWbpD8Ri\u002fBL0L6kPzOyaMZp4aQ\u002fvN5WHG8DpT+nmKA53SSlP3qgKAqxRaU\u002f8jszeudlpT+3IM12fYWlP5n7NO5vpKU\u002fJZJH0LvCpT9ze+4OXuClP1RskZ5T\u002faU\u002fJhKKdpkZpj+rdpqRLDWmP03mZe4JUKY\u002fZFHsjy5qpj\u002f+Hwh+l4OmP7Ju7sVBnKY\u002f96mxeiq0pj9\u002ffMa1TsumP+QDi5er4aY\u002f7T3QRz73pj9xn2X2AwynP+zEptv5H6c\u002fUSsLOR0zpz\u002fQ3rdZa0WnP6MLE5PhVqc\u002fKlxZRX1npz+7DTXcO3enP\u002fCkVs8ahqc\u002fQSgPoxeUpz\u002fexuvoL6GnPxLQUkBhrac\u002fSt4hV6m4pz9zF03qBcOnP61if8Z0zKc\u002flnK7yPPUpz8rgf3egNynP6SZ3Qga46c\u002flEoyWL3opz+\u002fmbPxaO2nPyERng0b8ac\u002fxrtV+NHzpz8y5wgTjPWnP9p8UtRH9qc\u002fEcbbyAP2pz\u002fnav2TvvSnP7l7X\u002fB28qc\u002fPVOYsCvvpz8lHsq\u002f2+qnP7zTPiKG5ac\u002f02kC9infpz8qDXtzxtenPzsm\u002f+1az6c\u002fRfFo1ObFpz9jb6exabunP3N1TC3jr6c\u002fmpwXDFOjpz9Z130wuZWnP1FtLZsVh6c\u002fAh+Oa2h3pz85Mj3gsWanP0InhVfyVKc\u002fYtbQTypCpz+\u002ftBloWi6nP3v\u002fUGCDGac\u002fjYvDGaYDpz\u002fE+HeXw+ymP1oGh\u002f7c1KY\u002fgMdtlvO7pj+edlnJCKKmPyGmbCQeh6Y\u002feo79VzVrpj8KOc03UE6mP9NIN7twMKY\u002fbyJa\u002fZgRpj+\u002fNTc9y\u002fGlPxcty90J0aU\u002flMYdZlevpT8tHkmBtoylPzkweP4paaU\u002fGF7c0LREpT+MwJkPWh+lP08VqvUc+aQ\u002fpxe24QDSpD81FuVVCaqkP6Saovc5gaQ\u002fwPpZj5ZXpD8krScIIy2kPwM\u002fgG\u002fjAaQ\u002ficvM9NvVoz\u002fQ2PzoEKmjP\u002fSBDb6Ge6M\u002fmNmFBkJNoz\u002fSc+h0Rx6jP8UKGtub7qI\u002fSDW9KUS+oj80K4NvRY2iP6uXcdikW6I\u002fEH4drWcpoj9rO9tRk\u002fahP6yy40Utw6E\u002fYbduIjuPoT+Iz8KZwlqhP6FqOnbJJaE\u002fE7E+mVXwoD\u002ffFDj6bLqgP9zRdKUVhKA\u002fd5EFu1VNoD93a5BtMxagP2UGNQJqvZ8\u002fUwyRk8FNnz\u002farSxVet2eP8ppDCihbJ4\u002fzG6PBUP7nT9d8K38bImdP4ieKC8sF50\u002fVA+qzo2knD9Z9NoZnzGcP9wBalltvps\u002fjncI3QVLmz8rRlz4ddeaP7nW6P\u002fKY5o\u002fC4PvRRLwmT9a2EgXWXyZP1nGN7isCJk\u002f8+U3YRqVmD9aC8g7ryGYP0lfMl94rpc\u002fZEJTzYI7lz\u002fxRGBv28iWPxWDsBKPVpY\u002f2buHZarklT+5feXzOXOVP2jIWSRKApU\u002fZIbgNOeRlD+DRMU3HSKUP9+OkBD4spM\u002fc1wAcYNEkz+58gzWytaSPyOq+4TZaZI\u002fCfuAiLr9kT9AOPOteJKRP1BZj4IeKJE\u002fSjLRULa+kD+qceAdSlaQP4BuJ07H3Y8\u002f0x4WvxgRjz+cLMbbmkaOPyH7A1Rffo0\u002fBUr\u002fNXe4jD\u002fo11Pq8vSLP1apTDDiM4s\u002fOQ5kGlR1ij8LYwILV7mJPyxvfbH4\u002f4g\u002fgSFaB0ZJiD9GTNJNS5WHPyPenwsU5IY\u002f4PIOC6s1hj8+8FdYGoqFP7q8QkBr4YQ\u002fv\u002fUTT6Y7hD9C4cRP05iDP2Sshkv5+II\u002fEV2RiR5cgj9XsD6PSMKBP9bxcCB8K4E\u002fkatFQL2XgD9\u002f3hMyDweAP8SMavXo8n4\u002fQ\u002fkzxN3dfT+cPkvq\u002fs58P7PlqQ9Nxns\u002fg45Vb8fDej+2dsXca8d5P1aY0Mk20Xg\u002fleMgTSPhdz9dwCcpK\u002fd2P7Wvj9NGE3Y\u002feJMmfW01dT\u002fL0zsalV10PydObWuyi3M\u002feLHdBrm\u002fcj8tps5hm\u002flxP47hmNpKOXE\u002fIwb8wrd+cD8W+n\u002fVopNvPy2BQlUMNW4\u002fq8YC34bhbD+Y7KmL65hrP6ucj8oRW2o\u002fShchec8naT88Xdj6+P5nPwfodFFh4GY\u002fx01nNdrLZT8mImEuNMFkP7Rn+qs+wGM\u002fa\u002fJdHsjIYj+JNe4OntphPxse1DiN9WA\u002f6MtpoWEZYD+FdOZgzYtePy5lLpDO9Vw\u002fvXAWuVtwWz\u002fF+8oWCftZP0W4EGVqlVg\u002fHno2DRM\u002fVz+OzApSlvdVP0U0v3qHvlQ\u002fWiuk\u002fHmTUz9DFaqjAXZSP9WolLmyZVE\u002fgJvPKyJiUD9cZKlfy9VOP0J+Kcwn\u002f0w\u002fmAGt9og\u002fSz8Gkv+QIJZJP7qYfeEiAkg\u002fYO4V\u002fMaCRj\u002f+SOz3RhdFP9olkSHgvkM\u002fm+XGKdN4Qj+WyM5QZERBP0VpO47bIEA\u002fXGySagkbPj\u002fPg4ArXxM8P1cFyzJgKTo\u002fopIIvLxbOD\u002fCnDaYLak2P11hdlR0EDU\u002fNEJNWluQMz95co0JticyP780EMxg1TA\u002fFsbdRoIwLz8nWddhi94sP4y7fG\u002fMsio\u002fRUL5M0erKD+o5K+gD8YmP4Hs27lLASU\u002fAiPUcjNbIz9s1nGBENIhP+MVICk+ZCA\u002fPb0z\u002fFEgHj+hXb9CnagbP3cW3Ot6Xhk\u002fe\u002f59nyg\u002fFz+EAEE6BEgVP1nT0RWLdhM\u002fvQkjSFnIET+Yh3raKDsQPzklvvKhmQ0\u002fREXQPIr2Cj\u002fKRtdpKIkIP2Qd\u002fHTPTQY\u002f4XpiuAVBBD9XHusig18CP0FZ72ovpgA\u002fkBstf0Ak\u002fj5LQqP0LkH7PoK65aMCnvg+MwiFIeU19j6MYbOdTQT0PjEc7GP9BPI+OV\u002fDZ\u002fwz8D5r6cvBKxvtPo4TRfCpHOo+t8BODgNm5z6Vp+PYOfHkPrtQgOm9uOI+P5FUtmW34D7Gfm2D0dDdPi3i8\u002fSzjto+l\u002f3xnESg1z5Alrfg9v3UPgJVp6LVoNI+wrONnnmC0D57HoheADrNPrsjKPsE1sk+jJ68LxrPxj6X2zvjLxzEPtTABZP9tME+pVIR0ucjvz4xpPYvXli7PpsghfLT+rc+nFg4z+P\u002ftD59tHWiNl2yPjIhQ\u002fdtCbA+0NX2zB\u002f4qz6xGv2B6FqoPh3M7e9lLaU+1KABbCdjoj6hyDxv+uGfPv3WLEu4mZs+ylGZoIbblz5FX4iLR5eUPu00vB2UvpE++PCT3SWJjj5rOvjzozuKPllTqy5HgIY+uzG7vPxEgz4nUKOFv3mAPqtv4vPEIHw+DPz9Q7\u002f4dz56aFkqV2V0PiTmaA8aU3E+xbX6g+BhbT44R9RAtd9oPq8nxWJtBWU+JLcMTxG8YT7+0QVJK99dPl0F3BgIHVk+J\u002fRvO1oTVT5pDJcIxadRPug3y7Hihk0+emCo\u002fjylSD7YxbM1f4hEPrsOLhxIE0E+PCxNDKhYPD6MNyjf53s3PiV1BpcpazM+3ZfOrKoGMD5FJvd6o2YqPs6TtWsWtCU+iuaMEnXOIT7UjbyH9igdPhG1UMsB1Bc+dQz3IntuEz5KM5BElaAPPoqiOrNtrwk+4uClT+LQBD5jpE7Vh9UAPrxT6P94K\u002fs9P2p3Gubg9T22+CT4VJTxPaWpT04yMOw97zb4x7eM5j3qRasgrP\u002fhPZsh8MTvqtw93myMn07H1j2YbSEFwQ7SPZICqzVgkMw9uyGve+uJxj1i9C+lFb7BPWq6CVIJ3rs9GiJsNBXVtT0M29smFhCxPb4j3\u002f3ymqo9G\u002fuwtOCwpD3kaq1QHQ2gPe\u002fHF8xY15g9LiU0SW4skz24bbU0roWNPXrJSC1dq4Y9TSD7qqJcgT0n3UYqHYZ6PStTSBfgNHQ9CkSUxoC0bj3YhzAzF0RnPYV3Dby9lGE9jV3NIz9\u002fWj3kR4M0belTPSiJeE+k1009wGdMFaNMRj3+VyK+g51APUTR\u002fBpOsDg9Ogs7gv9JMj2pjUP9VAQrPT+5RlBP5SM9HkfA+SU3HT0cr2s8smIVPZhuKiB1Ng89EBnvszG1Bj0sy\u002fOQEHgAPZySqJtN0Pc8ajZKupgp8TzBQjZ9tKjoPKZtgCVuqOE8U24rMfo02TxqTt\u002fyv+7RPNVuGvA9bsk8IeAWyrz4wTz9buOnsFC5PENf4urixLE8h+bQD+7bqDwzYg1CLFShPPoA1DkeE5g8x4iwzRCqkDxlrYAL2vyGPAM2Lpe6mH88r39hp9KidTzKuk+iwIVtPMJ+tbtBEWQ8+KBODugtWzyjfmksLlZSPPFQ1qDcpUg8nGry7JSAQDydK7xSBwM2PMle+wsRPy08AMtDb1RaIzzsHhSttIIZPAA1O0oUvxA8TgC3Gr7lBTyKfNc7GoT8O99lWbS4ffI7oqW4K53h5zvM4Vi2o7beOyxJJemfqtM7NN9m7QgUyTvrCO6yZte\u002fO6UMV15XILQ7XONWB89UqTuoQolXu72fO3RBeO4wzJM7zyyfUcWViDsQMrJZKGR+O8JdQmq2snI7fVZmsLnmZjuK4xvtC+tbOx0OarjO71A7mKXwfadzRDvUuTwJ4pM4O74v1EOKZC07dwrkF1Z9ITt2rwhpArYUO7ZmKyZOZwg7EvJb+iOc\u002fDpApVABha\u002fwOqASpl7lXOM6oXvBeYla1jqA05EVA6zJOo4n4\u002fTlU706TUgCNcapsDqdXC7dbdWiOtZQnUoFLJU6BlDJFLarhzoN52FPoVF6OgPUFOnNGW06fHPnmx3\u002fXzrXtbPkpH1ROvXRiLl0A0M67ufPn8CMNDr0uH0UzxQmOh5WL4N9lhc6awUZV1gMCTqqAkH0t3D6Oep\u002f6fHhves5QwgMrS3u3DktsloNK\u002fzNORGmgSTK4r450KBGKoKdrzmXEwQiOxSgOQNJzEJLQJA5u4wA191RgDlsJU0mXkhwOarpdhy9I2A58aFwl+fITzkAsnACAxc\u002fOdVIldTKNC459lhC+ycmHTk7xOIc0e8LOR\u002fKaDsnl\u002fo4itmOSA0i6Th2SMpavJbXOBK0\u002fFOW+8U44tbQ0\u002fhWtDisshk\u002fEq+iOPGm+Hy6CZE4edMcmqHYfjjRNOCXP7drOHCrv8OQt1g4t1WR8mfgRTjgYGz5GzczOKYmJ4mIvyA4VvIsQzf4DDgkyMy029v4N4Z6K9DGKeU3SpEvI6ff0TewHZb30PK9N6FuulUd46g3K2XFkx+DlDcf3bzdVMSAN5IxX6TTLms3JWmcgo3ZVTfZ9LPRDWpBN3Rdn05lhSs3GjHoLi2PFTd\u002fvDiE9b0AN1fHqegmxuk2jo8yhuCp0zb8k+Y4RLy9NkyLm6DCR6Y2+BLP0tuKkDZm5cHwwlZ4Nv9gvSc2vWE2xd+Rsr2dSTaeToK9NFIyNgq0JFEx9Rk2W2q0mVY2AjZP5Rf6607pNYc5xVenadE12ahVYXW5tzUihy83UACgNexYmugjXoU1km96LLw+bDWtFkRpJ3pSNQgulC3e7Dc1f98Ov0+oHjVnrfICkm8DNYKuIgHhYeg0\u002fXcuI8tCzjQrQX7LcJOyNNTqeLTUjpY0bQTUScUXezQlrnH97BZgNH7sKfuG5UI0iRT2FnPxJTTqP4gv6zAJNFDtBxF\u002fluwz6rAQApUI0DNp2lO8U8axMxD840UFeZMzxT6cWEsUdTOpFMTNaItWM4f9AB\u002fg0TczkP0PkR3cGDPubyUEIaD5Mj2B5EcaFtoyAWyH8uo4ujKLum81ggaaMp3IL+wJgHkyGnoH8t+pWDJMBmtCW4s3MniigQ5hLhYyWBRGPNKe9DG8qxU13enSMcAkRxxCHbExbUiAfTCNjjH6rUB+ReVqMQt\u002fi6qAWUcxlO1def38IzF96q8krd4AMTw4ZI5BEtwwVzCSCkAFtzC4rR7vAJuSMNS\u002fYgu4om0wnZ+9d0lBRzAtkhaPKPohMEbVnjy9YPsvpTcGnDaI1C+KPRO6kVOuL52ZeT1HDYYvvc28VXmSXy8LiRdLTz82L8NsRIaP2w4viamA1B4P5S5IORcBE0i8LufVqj+vrpIu2216dUpHaC4wepMInAc\u002fLvbcXWmsfxMuLP6KPvMX6C2t\u002fN0NJES9LVYFL1txeJEtmn\u002fKKHl\u002fZC1lKrjFkaE3LRCZQTa2wwot89jeaKLG3SzCTyjqTkSwLM9M+BDjc4EssCGz4VNiUiyKoT+CcAIjLE8rPX\u002fBSvMrBBPisHI2wysucrSX18WSK3NYhPd2\u002fmEr91cMr5nqMCuLlXQr0jD\u002fKoHiF6BvMcwq+SXKLDX7mCq2eHtK9rFlKm\u002fgfrqbdjIqJ0y8WyzK\u002fikOLjnhBSbJKXZp\u002f+IjHpQpodCqz4iEXyn\u002fuhce0isoKSFiSOgbJfIoXMLLNDapuigNhlMuTiqDKGCgiO6Y9EooZ+qfUm6KEijWDkECIvHYJ29SCLYFZ6AnophCyIMWZSdTeYdVIH8qJ0Y\u002fGoTbQ\u002fAmp5Q4U+2BsyY0bip55dl2JmUh\u002fBt7IzomsIgo7Zow\u002fSW4KQfIP9G\u002fJX9R2lsR7IAlv3T3RdSPQSV5HqmoKccBJUW7GfH\u002fjMEkgiBocK\u002fkgCThKKxRX7M\u002fJAZYtfCZ\u002fPwjYJvuOCTTuSOZew2VdWl2Iwf1XNEp8TIjcfy5uscs7yI\u002fb4jBzvioInkgoEl2d2MiC+rLdmuHHSI1fbij9MjVIQQo4Q02Qo8hbChJa5nNRSG9awz6OJD9IEuT6zPsebMg3gXTERXuaCCMXwJhL\u002f4eIJjFdQHAtNIf\u002f6S66lrrhR\u002fqmkSBE+04H4vLgyMxgOse5a6pCvFtnR5+5uTj4IlOHnOIb5BkuP4d30Er0xfzrR0uGUDNF0pcHdmsTDPe4Qkdc5SymxHuthwi4UNrUapjHEQWY81kUhAcxHEeTXY2uhuEBHCeultkG5O6FHaYkw4b\u002fD105AEythpJ732dUyNfGiFxgt+YGQUa7tHijbqdqxnV7D07mnJRGRjrnnAnR\u002fUYZL5BwFMJmRiQijgv5Wk8GP50f9SkF98XX5ZcWstlgBd0iODCdKogF6OEix7xUMAWHTR8hYfDXhYTz1qvBev7FWcdGGpUYZgVPpkxHhZ7NBXIfh8kgIvQFKWBTGg6smkUsJ7zLu4sAxQ4pGU37H2bE86gpLOx7DITEdMt9ZYCyRJCCFa+Z7dfElg+XDJbSvMRCdrW8xaAhhGTMKRjzCcZEYVYTV1j8qoQPduaKqqlOxBJ1T1sXSjLDz+Squnsh1kPSpVCVTH25g7wgrf\u002flb9zDrn4YW9mPAAO9YNc+1uCiQ3ZF6dt\u002fCMTDQBYRYSVbJsMr0zflum\u002fIgwTwnrcJ3WoCz5uqx36ay4LnM7Yx64Isgpmk94J5F40CmIwqgrg6bUJrgBRNaxwNgn3lFU7dt21CFoPPbVEQzQILBRZ2PrZsQfi7IsymuMtBw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"fill":"tozeroy","fillcolor":"rgba(192,57,43,0.35)","hovertemplate":"t=%{x:.1f}  doom=%{y:.4f}","line":{"color":"#c0392b","width":1},"name":"λ_d S(t)  doom density","x":{"dtype":"f8","bdata":"AAAAAAAAAADwrmK0k22mP\u002fCuYrSTbbY\u002fNANKxy7SwD\u002fwrmK0k23GP6xae6H4CMw\u002fNANKxy7S0D8SWdY94Z\u002fTP\u002fCuYrSTbdY\u002fzgTvKkY72T+sWnuh+AjcP4qwBxir1t4\u002fNANKxy7S4D8jLpACCDniPxJZ1j3hn+M\u002fAYQceboG5T\u002fwrmK0k23mP9\u002fZqO9s1Oc\u002fzgTvKkY76T+9LzVmH6LqP6xae6H4COw\u002fm4XB3NFv7T+KsAcYq9buP7ztpinCHvA\u002fNANKxy7S8D+sGO1km4XxPyMukAIIOfI\u002fmkMzoHTs8j8SWdY94Z\u002fzP4puedtNU\u002fQ\u002fAYQceboG9T94mb8WJ7r1P\u002fCuYrSTbfY\u002faMQFUgAh9z\u002ff2ajvbNT3P1bvS43Zh\u002fg\u002fzgTvKkY7+T9GGpLIsu75P70vNWYfovo\u002fNEXYA4xV+z+sWnuh+Aj8PyRwHj9lvPw\u002fm4XB3NFv\u002fT8Sm2R6PiP+P4qwBxir1v4\u002fAsaqtReK\u002fz+87aYpwh4AQHh4eHh4eABANANKxy7SAEDwjRsW5SsBQKwY7WSbhQFAZ6O+s1HfAUAjLpACCDkCQN+4YVG+kgJAmkMzoHTsAkBWzgTvKkYDQBJZ1j3hnwNAzuOnjJf5A0CKbnnbTVMEQEX5SioErQRAAYQceboGBUC9Du7HcGAFQHiZvxYnugVANCSRZd0TBkDwrmK0k20GQKw5NANKxwZAaMQFUgAhB0AjT9egtnoHQN\u002fZqO9s1AdAm2R6PiMuCEBW70uN2YcIQBJ6HdyP4QhAzgTvKkY7CUCKj8B5\u002fJQJQEYaksiy7glAAaVjF2lICkC9LzVmH6IKQHm6BrXV+wpANEXYA4xVC0Dwz6lSQq8LQKxae6H4CAxAaOVM8K5iDEAkcB4\u002fZbwMQN\u002f6740bFg1Am4XB3NFvDUBXEJMriMkNQBKbZHo+Iw5AziU2yfR8DkCKsAcYq9YOQEY72WZhMA9AAsaqtReKD0C9UHwEzuMPQLztpinCHhBAGrMPUZ1LEEB4eHh4eHgQQNY94Z9TpRBANANKxy7SEECSyLLuCf8QQPCNGxblKxFATlOEPcBYEUCsGO1km4URQAneVYx2shFAZ6O+s1HfEUDFaCfbLAwSQCMukAIIORJAgfP4KeNlEkDfuGFRvpISQD1+yniZvxJAmkMzoHTsEkD4CJzHTxkTQFbOBO8qRhNAtJNtFgZzE0ASWdY94Z8TQHAeP2W8zBNAzuOnjJf5E0AsqRC0ciYUQIpuedtNUxRA5zPiAimAFEBF+UoqBK0UQKO+s1Hf2RRAAYQceboGFUBfSYWglTMVQL0O7sdwYBVAG9RW70uNFUB4mb8WJ7oVQNZeKD4C5xVANCSRZd0TFkCS6fmMuEAWQPCuYrSTbRZATnTL226aFkCsOTQDSscWQAr\u002fnCol9BZAaMQFUgAhF0DFiW55200XQCNP16C2ehdAgRRAyJGnF0Df2ajvbNQXQD2fERdIARhAm2R6PiMuGED5KeNl\u002floYQFbvS43ZhxhAtLS0tLS0GEASeh3cj+EYQHA\u002fhgNrDhlAzgTvKkY7GUAsyldSIWgZQIqPwHn8lBlA6FQpodfBGUBGGpLIsu4ZQKPf+u+NGxpAAaVjF2lIGkBfasw+RHUaQL0vNWYfohpAG\u002fWdjfrOGkB5uga11fsaQNd\u002fb9ywKBtANEXYA4xVG0CSCkErZ4IbQPDPqVJCrxtATpUSeh3cG0CsWnuh+AgcQAog5MjTNRxAaOVM8K5iHEDGqrUXio8cQCRwHj9lvBxAgTWHZkDpHEDf+u+NGxYdQD3AWLX2Qh1Am4XB3NFvHUD5SioErZwdQFcQkyuIyR1AtdX7UmP2HUASm2R6PiMeQHBgzaEZUB5AziU2yfR8HkAs657wz6keQIqwBxir1h5A6HVwP4YDH0BGO9lmYTAfQKQAQo48XR9AAsaqtReKH0BfixPd8rYfQL1QfATO4x9ADovylVQIIEC87aYpwh4gQGtQW70vNSBAGrMPUZ1LIEDJFcTkCmIgQHh4eHh4eCBAJ9ssDOaOIEDWPeGfU6UgQIWglTPBuyBANANKxy7SIEDjZf5anOggQJLIsu4J\u002fyBAQStngncVIUDwjRsW5SshQJ\u002fwz6lSQiFATlOEPcBYIUD9tTjRLW8hQKwY7WSbhSFAWnuh+AicIUAJ3lWMdrIhQLhACiDkyCFAZ6O+s1HfIUAWBnNHv\u002fUhQMVoJ9ssDCJAdMvbbpoiIkAjLpACCDkiQNKQRJZ1TyJAgfP4KeNlIkAwVq29UHwiQN+4YVG+kiJAjhsW5SupIkA9fsp4mb8iQOzgfgwH1iJAmkMzoHTsIkBJpucz4gIjQPgInMdPGSNAp2tQW70vI0BWzgTvKkYjQAUxuYKYXCNAtJNtFgZzI0Bj9iGqc4kjQBJZ1j3hnyNAwbuK0U62I0BwHj9lvMwjQB+B8\u002fgp4yNAzuOnjJf5I0B9RlwgBRAkQCypELRyJiRA2wvFR+A8JECKbnnbTVMkQDjRLW+7aSRA5zPiAimAJECWlpaWlpYkQEX5SioErSRA9Fv\u002fvXHDJECjvrNR39kkQFIhaOVM8CRAAYQceboGJUCw5tAMKB0lQF9JhaCVMyVADqw5NANKJUC9Du7HcGAlQGxxolvediVAG9RW70uNJUDKNguDuaMlQHiZvxYnuiVAJ\u002fxzqpTQJUDWXig+AuclQIXB3NFv\u002fSVANCSRZd0TJkDjhkX5SiomQJLp+Yy4QCZAQUyuICZXJkDwrmK0k20mQJ8RF0gBhCZATnTL226aJkD91n9v3LAmQKw5NANKxyZAW5zolrfdJkAK\u002f5wqJfQmQLlhUb6SCidAaMQFUgAhJ0AWJ7rlbTcnQMWJbnnbTSdAdOwiDUlkJ0AjT9egtnonQNKxizQkkSdAgRRAyJGnJ0Awd\u002fRb\u002f70nQN\u002fZqO9s1CdAjjxdg9rqJ0A9nxEXSAEoQOwBxqq1FyhAm2R6PiMuKEBKxy7SkEQoQPkp42X+WihAqIyX+WtxKEBW70uN2YcoQAVSACFHnihAtLS0tLS0KEBjF2lIIssoQBJ6HdyP4ShAwdzRb\u002f33KEBwP4YDaw4pQB+iOpfYJClAzgTvKkY7KUB9Z6O+s1EpQCzKV1IhaClA2ywM5o5+KUCKj8B5\u002fJQpQDnydA1qqylA6FQpodfBKUCXt900RdgpQEYaksiy7ilA9HxGXCAFKkCj3\u002frvjRsqQFJCr4P7MSpAAaVjF2lIKkCwBxir1l4qQF9qzD5EdSpADs2A0rGLKkC9LzVmH6IqQGyS6fmMuCpAG\u002fWdjfrOKkDKV1IhaOUqQHm6BrXV+ypAKB27SEMSK0DXf2\u002fcsCgrQIbiI3AePytANEXYA4xVK0Djp4yX+WsrQJIKQStngitAQW31vtSYK0Dwz6lSQq8rQJ8yXuavxStATpUSeh3cK0D998YNi\u002fIrQKxae6H4CCxAW70vNWYfLEAKIOTI0zUsQLmCmFxBTCxAaOVM8K5iLEAXSAGEHHksQMaqtReKjyxAdQ1qq\u002felLEAkcB4\u002fZbwsQNLS0tLS0ixAgTWHZkDpLEAwmDv6rf8sQN\u002f6740bFi1Ajl2kIYksLUA9wFi19kItQOwiDUlkWS1Am4XB3NFvLUBK6HVwP4YtQPlKKgStnC1AqK3elxqzLUBXEJMriMktQAZzR7\u002f13y1AtdX7UmP2LUBkOLDm0AwuQBKbZHo+Iy5Awf0YDqw5LkBwYM2hGVAuQB\u002fDgTWHZi5AziU2yfR8LkB9iOpcYpMuQCzrnvDPqS5A201ThD3ALkCKsAcYq9YuQDkTvKsY7S5A6HVwP4YDL0CX2CTT8xkvQEY72WZhMC9A9Z2N+s5GL0CkAEKOPF0vQFNj9iGqcy9AAsaqtReKL0CwKF9JhaAvQF+LE93yti9ADu7HcGDNL0C9UHwEzuMvQGyzMJg7+i9ADovylVQIMEBlvMxfixMwQLztpinCHjBAFB+B8\u002fgpMEBrUFu9LzUwQMOBNYdmQDBAGrMPUZ1LMEBy5Oka1FYwQMkVxOQKYjBAIUeerkFtMEB4eHh4eHgwQNCpUkKvgzBAJ9ssDOaOMEB\u002fDAfWHJowQNY94Z9TpTBALm+7aYqwMECFoJUzwbswQN3Rb\u002f33xjBANANKxy7SMECLNCSRZd0wQONl\u002flqc6DBAOpfYJNPzMECSyLLuCf8wQOn5jLhACjFAQStngncVMUCYXEFMriAxQPCNGxblKzFAR7\u002f13xs3MUCf8M+pUkIxQPYhqnOJTTFATlOEPcBYMUClhF4H92MxQP21ONEtbzFAVOcSm2R6MUCsGO1km4UxQANKxy7SkDFAWnuh+AicMUCyrHvCP6cxQAneVYx2sjFAYQ8wVq29MUC4QAog5MgxQBBy5Oka1DFAZ6O+s1HfMUC\u002f1Jh9iOoxQBYGc0e\u002f9TFAbjdNEfYAMkDFaCfbLAwyQB2aAaVjFzJAdMvbbpoiMkDM\u002fLU40S0yQCMukAIIOTJAel9qzD5EMkDSkESWdU8yQCnCHmCsWjJAgfP4KeNlMkDYJNPzGXEyQDBWrb1QfDJAh4eHh4eHMkDfuGFRvpIyQDbqOxv1nTJAjhsW5SupMkDlTPCuYrQyQD1+yniZvzJAlK+kQtDKMkDs4H4MB9YyQEMSWdY94TJAmkMzoHTsMkDydA1qq\u002fcyQEmm5zPiAjNAodfB\u002fRgOM0D4CJzHTxkzQFA6dpGGJDNAp2tQW70vM0D\u002fnCol9DozQFbOBO8qRjNArv\u002feuGFRM0AFMbmCmFwzQF1ik0zPZzNAtJNtFgZzM0AMxUfgPH4zQGP2IapziTNAuyf8c6qUM0ASWdY94Z8zQGmKsAcYqzNAwbuK0U62M0AY7WSbhcEzQHAeP2W8zDNAx08ZL\u002fPXM0AfgfP4KeMzQHayzcJg7jNAzuOnjJf5M0AlFYJWzgQ0QH1GXCAFEDRA1Hc26jsbNEAsqRC0ciY0QIPa6n2pMTRA2wvFR+A8NEAyPZ8RF0g0QIpuedtNUzRA4Z9TpYReNEA40S1vu2k0QJACCDnydDRA5zPiAimANEA\u002fZbzMX4s0QJaWlpaWljRA7sdwYM2hNEBF+UoqBK00QJ0qJfQ6uDRA9Fv\u002fvXHDNEBMjdmHqM40QKO+s1Hf2TRA+++NGxblNEBSIWjlTPA0QKpSQq+D+zRAAYQceboGNUBYtfZC8RE1QLDm0AwoHTVABxir1l4oNUBfSYWglTM1QLZ6X2rMPjVADqw5NANKNUBl3RP+OVU1QL0O7sdwYDVAFEDIkadrNUBscaJb3nY1QMOifCUVgjVAG9RW70uNNUByBTG5gpg1QMo2C4O5ozVAIWjlTPCuNUB4mb8WJ7o1QNDKmeBdxTVAJ\u002fxzqpTQNUB\u002fLU50y9s1QNZeKD4C5zVALpACCDnyNUCFwdzRb\u002f01QN3ytpumCDZANCSRZd0TNkCMVWsvFB82QOOGRflKKjZAO7gfw4E1NkCS6fmMuEA2QOoa1FbvSzZAQUyuICZXNkCZfYjqXGI2QPCuYrSTbTZAR+A8fsp4NkCfERdIAYQ2QPZC8RE4jzZATnTL226aNkClpaWlpaU2QP3Wf2\u002fcsDZAVAhaORO8NkCsOTQDSsc2QANrDs2A0jZAW5zolrfdNkCyzcJg7ug2QAr\u002fnCol9DZAYTB39Fv\u002fNkC5YVG+kgo3QBCTK4jJFTdAaMQFUgAhN0C\u002f9d8bNyw3QBYnuuVtNzdAbliUr6RCN0DFiW552003QB27SEMSWTdAdOwiDUlkN0DMHf3Wf283QCNP16C2ejdAe4Cxau2FN0DSsYs0JJE3QCrjZf5anDdAgRRAyJGnN0DZRRqSyLI3QDB39Fv\u002fvTdAiKjOJTbJN0Df2ajvbNQ3QDYLg7mj3zdAjjxdg9rqN0DlbTdNEfY3QD2fERdIAThAlNDr4H4MOEDsAcaqtRc4QEMzoHTsIjhAm2R6PiMuOEDylVQIWjk4QErHLtKQRDhAofgInMdPOED5KeNl\u002flo4QFBbvS81ZjhAqIyX+WtxOED\u002fvXHDonw4QFbvS43ZhzhAriAmVxCTOEAFUgAhR544QF2D2up9qThAtLS0tLS0OEAM5o5+6784QGMXaUgiyzhAu0hDElnWOEASeh3cj+E4QGqr96XG7DhAwdzRb\u002f33OEAZDqw5NAM5QHA\u002fhgNrDjlAyHBgzaEZOUAfojqX2CQ5QHfTFGEPMDlAzgTvKkY7OUAlNsn0fEY5QH1no76zUTlA1Jh9iOpcOUAsyldSIWg5QIP7MRxYczlA2ywM5o5+OUAyXuavxYk5QIqPwHn8lDlA4cCaQzOgOUA58nQNaqs5QJAjT9egtjlA6FQpodfBOUA\u002fhgNrDs05QJe33TRF2DlA7ui3\u002fnvjOUBGGpLIsu45QJ1LbJLp+TlA9HxGXCAFOkBMriAmVxA6QKPf+u+NGzpA+xDVucQmOkBSQq+D+zE6QKpziU0yPTpAAaVjF2lIOkBZ1j3hn1M6QLAHGKvWXjpACDnydA1qOkBfasw+RHU6QLebpgh7gDpADs2A0rGLOkBm\u002flqc6JY6QL0vNWYfojpAFGEPMFatOkBskun5jLg6QMPDw8PDwzpAG\u002fWdjfrOOkByJnhXMdo6QMpXUiFo5TpAIYks657wOkB5uga11fs6QNDr4H4MBztAKB27SEMSO0B\u002fTpUSeh07QNd\u002fb9ywKDtALrFJpuczO0CG4iNwHj87QN0T\u002fjlVSjtANEXYA4xVO0CMdrLNwmA7QOOnjJf5aztAO9lmYTB3O0CSCkErZ4I7QOo7G\u002fWdjTtAQW31vtSYO0CZns+IC6Q7QPDPqVJCrztASAGEHHm6O0CfMl7mr8U7QPdjOLDm0DtATpUSeh3cO0CmxuxDVOc7QP33xg2L8jtAVSmh18H9O0CsWnuh+Ag8QAOMVWsvFDxAW70vNWYfPECy7gn\u002fnCo8QAog5MjTNTxAYVG+kgpBPEC5gphcQUw8QBC0ciZ4VzxAaOVM8K5iPEC\u002fFie65W08QBdIAYQceTxAbnnbTVOEPEDGqrUXio88QB3cj+HAmjxAdQ1qq\u002felPEDMPkR1LrE8QCRwHj9lvDxAe6H4CJzHPEDS0tLS0tI8QCoErZwJ3jxAgTWHZkDpPEDZZmEwd\u002fQ8QDCYO\u002fqt\u002fzxAiMkVxOQKPUDf+u+NGxY9QDcsyldSIT1Ajl2kIYksPUDmjn7rvzc9QD3AWLX2Qj1AlfEyfy1OPUDsIg1JZFk9QERU5xKbZD1Am4XB3NFvPUDytpumCHs9QErodXA\u002fhj1AoRlQOnaRPUD5SioErZw9QFB8BM7jpz1AqK3elxqzPUD\u002f3rhhUb49QFcQkyuIyT1ArkFt9b7UPUAGc0e\u002f9d89QF2kIYks6z1AtdX7UmP2PUAMB9YcmgE+QGQ4sObQDD5Au2mKsAcYPkASm2R6PiM+QGrMPkR1Lj5Awf0YDqw5PkAZL\u002fPX4kQ+QHBgzaEZUD5AyJGna1BbPkAfw4E1h2Y+QHf0W\u002f+9cT5AziU2yfR8PkAmVxCTK4g+QH2I6lxikz5A1bnEJpmePkAs657wz6k+QIQceboGtT5A201ThD3APkAzfy1OdMs+QIqwBxir1j5A4eHh4eHhPkA5E7yrGO0+QJBElnVP+D5A6HVwP4YDP0A\u002fp0oJvQ4\u002fQJfYJNPzGT9A7gn\u002fnColP0BGO9lmYTA\u002fQJ1sszCYOz9A9Z2N+s5GP0BMz2fEBVI\u002fQKQAQo48XT9A+zEcWHNoP0BTY\u002fYhqnM\u002fQKqU0Ovgfj9AAsaqtReKP0BZ94R\u002fTpU\u002fQLAoX0mFoD9ACFo5E7yrP0BfixPd8rY\u002fQLe87aYpwj9ADu7HcGDNP0BmH6I6l9g\u002fQL1QfATO4z9AFYJWzgTvP0BsszCYO\u002fo\u002fQGJyBTG5AkBADovylVQIQEC5o9\u002f67w1AQGW8zF+LE0BAEdW5xCYZQEC87aYpwh5AQGgGlI5dJEBAFB+B8\u002fgpQEDAN25YlC9AQGtQW70vNUBAF2lIIss6QEDDgTWHZkBAQG+aIuwBRkBAGrMPUZ1LQEDGy\u002fy1OFFAQHLk6RrUVkBAHv3Wf29cQEDJFcTkCmJAQHUusUmmZ0BAIUeerkFtQEDNX4sT3XJAQHh4eHh4eEBAJJFl3RN+QEDQqVJCr4NAQHvCP6dKiUBAJ9ssDOaOQEDT8xlxgZRAQH8MB9YcmkBAKiX0OrifQEDWPeGfU6VAQIJWzgTvqkBALm+7aYqwQEDZh6jOJbZAQIWglTPBu0BAMbmCmFzBQEDd0W\u002f998ZAQIjqXGKTzEBANANKxy7SQEDgGzcsytdAQIs0JJFl3UBAN00R9gDjQEDjZf5anOhAQI9+67837kBAOpfYJNPzQEDmr8WJbvlAQJLIsu4J\u002f0BAPuGfU6UEQUDp+Yy4QApBQJUSeh3cD0FAQStngncVQUDtQ1TnEhtBQJhcQUyuIEFARHUusUkmQUDwjRsW5StBQJumCHuAMUFAR7\u002f13xs3QUDz1+JEtzxBQJ\u002fwz6lSQkFASgm9Du5HQUD2IapziU1BQKI6l9gkU0FATlOEPcBYQUD5a3GiW15BQKWEXgf3Y0FAUZ1LbJJpQUD9tTjRLW9BQKjOJTbJdEFAVOcSm2R6QUAAAAAAAIBBQA=="},"y":{"dtype":"f8","bdata":"AAAAAAAAAADZUaPdfTXEPrmnslwdTOQ+0kRMTR\u002fv9j4wMaTHeHkEPxm1XG2oEBA\u002fQCFLwtU7Fz9u5JyjkMIfP5HYU7WT1CQ\u002fm67nF\u002fV5Kj+TZI6bymkwP073G6nO8TM\u002fErR4fBvWNz8EP1DsxRc8P9GS8mHxW0A\u002fCFgDVMPbQj9U7pj94otFP3YobWDabEg\u002f4QnsPzN\u002fSz\u002fGd\u002fsRd8NOP3EZqXcXHVE\u002fy0quwfHxUj8uzNZ9juBUP3ZTfXsx6VY\u002fJWzQOR4MWT+QEtnel0lbPyuhRC7hoV0\u002fybP4P54KYD+hpB7b9VFhP87jChoYp2I\u002f5HBkaSUKZD\u002fMHcvrPXtlPw8CgHSB+mY\u002fY47tgQ+IaD\u002fYBA84ByRqP7get1qHzms\u002flq21R66HbT8DCtzwmU9vP\u002fcT8Oozk3A\u002f3RYPfxqGcT91OBz5joByP9\u002fgylqfgnM\u002fbvreX1mMdD+oUMp6yp11P78HOdH\u002ftnY\u002f4y6NOAbYdz8mc0ky6gB5PwL9aui3MXo\u002fSIayKXtqez8lu9xlP6t8P638yakP9H0\u002fm6CVm\u002fZEfz9DZ047\u002f06AP9qPuoOY\u002f4A\u002fJpbp00u0gT\u002fMmjCdHW2CP3URQxMSKoM\u002fNIYOKi3rgz+Fyo6TcrCEP4Ktmr3leYU\u002fbWapz4lHhj9n25CoYRmHPz\u002fyPNxv74c\u002fQhxgsbbJiD+yUR0fOKiJP1W1q8r1ioo\u002fUBz0BPFxiz9HuijIKl2MPwc2V7WjTI0\u002fvG71EVxAjj9gPmnFUziPPwTFRStFGpA\u002flnqTdH+akD\u002f1SLkd2ByRPyN2xFFOoZE\u002f5eltBuEnkj+spdL6jrCSP\u002fAFK7ZWO5M\u002fAwaBhjbIkz\u002fbwWV\u002fLFeUP8xzpng26JQ\u002fDS8BDVJ7lT9YmtmYfBCWP9vw7Tizp5Y\u002fZ5IMyfJAlz9absriN9yXPyiYOtx+eZg\u002fm1anxsMYmT85Ak1tArqZPwYKF1Q2XZo\u002fs3hftloCmz+RV7GFaqmbPxRPjmhgUpw\u002fUeg3uTb9nD9K1XyE56mdP16piohsWJ4\u002f6m3EM78Inz8ogp6j2LqfP00cwNFYN6A\u002ff9PVVSGSoD+HEBbwwe2gP27s5Yc2SqE\u002fi5GI2XqnoT\u002fY76F1igWiP4rFvcBgZKI\u002f+j7b8vjDoj+pcv0WTiSjPzz\u002fwApbhaM\u002fyxL3fRrnoz8JJEbyhkmkPwWn0LqarKQ\u002fkgji+08QpT9fPKKqoHSlP\u002fsq0IyG2aU\u002f+U2DOPs+pj9XyfQT+KSmP1RSUFV2C6c\u002ffTSNAm9ypz+1xk\u002fx2tmnP1+i08ayQag\u002fve\u002fe9+6pqD+\u002fGr\u002fIhxKpPxZET011e6k\u002fj8IIaa\u002fkqT8MCR7PLU6qPzRFoALot6o\u002fRwuwVtUhqz8aY7nu7IurP+aJu74l9qs\u002fT7yci3ZgrD9gW4rr1cqsP2K+ZUY6Na0\u002fogI+1pmfrT9YKden6gmuP1jSPpsidK4\u002fB+FuZDferj9zV\u002f6LHkivP7+x4G\u002fNsa8\u002f+AQaopwNsD8bJQ+KK0KwP7jYXOGNdrA\u002fgHUIBr6qsD88fBVDtt6wPzeUE9FwErE\u002fdam01udFsT8RSGtpFXmxP2NNEY7zq7E\u002fnwWXOXzesT+LyrpRqRCyP6A1ya10QrI\u002f9PVlF9hzsj9SV11LzaSyP\u002fWFfvpN1bI\u002fMZd+ylMFsz\u002fhXORW2DSzPyEG\u002fTHVY7M\u002fV43Z5UOSsz\u002fU71T1HcCzP0koI91c7bM\u002f8eHoFPoZtD+E1lsQ70W0P5nFa0A1cbQ\u002f\u002fe9zFMabtD\u002ft\u002fXT7msW0P90yV2Wt7rQ\u002fPc00xPYWtT+6bKyNcD61P5VUOzwUZbU\u002fm1ufUNuKtT+BVkBTv6+1P5fEoNW507U\u002fSILVc8T2tT+pPwTW2Bi2P6R06LHwObY\u002fHIdezAVatj910vT6EXm2P6Y6giUPl7Y\u002fye\u002fBR\u002feztj8QAvRyxM+2P2hggs9w6rY\u002fPtepnvYDtz8NnyY8UBy3P5cF5B94M7c\u002f0bau32hJtz\u002fvJekwHV63PwmRQeqPcbc\u002fbhVpBbyDtz\u002fjRMugnJS3P5WnRQEtpLc\u002fU5Hek2iytz\u002fPq3rvSr+3PyiTkNbPyrc\u002fJd3ZOPPUtz9y3wA1sd23P76EShoG5bc\u002fn3w7au7qtz97DjjaZu+3P8\u002fTHVVs8rc\u002fFpvW\u002fPvztz9mseMrE\u002fS3PwnO4Hav8rc\u002f19j9rc7vtz\u002f6wW7ebuu3P8+f0FOO5bc\u002fSEaDmSvetz\u002fcifZ7RdW3P4Vf6gnbyrc\u002fDguhleu+tz\u002fZjAK2drG3P9+AsEd8orc\u002fFKIJbvyRtz8\u002fJhyU93+3P+Aohm1ubLc\u002f211D92FXtz8FSGd400C3P90yw4LEKLc\u002fMDN38zYPtz\u002fUdm3zLPS2Pxgxv\u002feo17Y\u002fEHcCwq25tj+zZYBgPpq2PynyUi5eebY\u002f+8xp0xBXtj87yHVEWjO2Pws6usI+DrY\u002fJt7D28LntT++wgRp67+1PxTYVI+9lrU\u002frsRWvj5stT9arMCvdEC1P+GjiGZlE7U\u002fspj0LRfltD9ugI2YkLW0P9Sy9X7YhLQ\u002fPF6i\u002fvVStD8jFnh48B+0PySKSo\u002fP67M\u002fn4M\u002fJpu2sz9VWBVfW4CzP6sQTZgYSbM\u002f2ZA4a9sQsz90Je2prNeyPx\u002flGl2VnbI\u002fPGrJwZ5isj+IefpG0iayP+A7M4s56rE\u002fhcPsWd6ssT+ppuyoym6xPxuLhpUIMLE\u002fR5DHYaLwsD98loxxorCwP0pzhEcTcLA\u002fRTQfgv8usD9fQ9ew49qvPy2GyC3qVq8\u002fMG9aOCjSrj\u002fPhYirs0yuP2D9SHmixq0\u002fbVy+owpArT92flY2ArmsP74f2j6fMaw\u002frjlwxvepqz9+jJjKISKrPxvGIDYzmqo\u002fa8gX2kESqj9BoMJmY4qpP+DLlmStAqk\u002fE3s9LTV7qD\u002fBeqPkD\u002fSnP7qEGXJSbac\u002fP6+IeRHnpj+luL5UYWGmP1Hp1QxW3KU\u002fbEG8UwNYpT\u002fIn919fNSkP62E9HvUUaQ\u002fsAUF1R3Qoz\u002fkdoWgak+jP0s6uIDMz6I\u002fbw86nVRRoj\u002fmI8idE9ShP4oIQaUZWKE\u002fLZDjTHbdoD\u002fxec6fOGSgPx5Jhy3e2J8\u002fOcBiKE\u002fsnj\u002fXkgjB3gKeP4\u002fah02mHJ0\u002fVoX44r05nD\u002fNsutPPFqbP1rOfBc3fpo\u002fqk4GbcKlmT+dlHww8dCYP4jxcOvU\u002f5c\u002fEWi+zn0ylz9sQ+Gw+miWP7kn+gxZo5U\u002fQcN8AqXhlD\u002fTy4lV6SOUPx14828vapM\u002f6yXrYn+0kj9UY1bp3wKSP\u002fgTyWpWVZE\u002fE\u002fEh\u002f+arkD85KsZylAaQP47r8JbAyo4\u002fI9mOm5WQjT9PNh0Gpl6MP+qMCoDtNIs\u002fffGqV2UTij98D2uPBPqIP6fL++2\u002f6Ic\u002fkm1pD4rfhj\u002fOmRB3U96FPxXNYqIK5YQ\u002fw3lrHJzzgz8gbAaS8gmDP6Ont+b2J4I\u002fWI8USpBNgT\u002fu2K1NpHqAP2oR0\u002fYtXn8\u002flQV42JXVfT+nOl6+Qlt8PyIdTKH07no\u002fBiWaI2mQeT9WaUfAWz94P1eYIfqF+3Y\u002fe9Xeip\u002fEdT+iggiSXpp0Pz6elsN3fHM\u002fOiMclp5qcj+1uGVwhWRxPw38bNbdaXA\u002flpIHK7H0bj94MDjfSyttP1mdJIvrdms\u002fL5TCgO\u002fWaT8asFHytkpoP1m74T+h0WY\u002f7A0qQg5rZT9G\u002fYySXhZkPwbGJdDz0mI\u002fZNTC4TCgYT\u002fN27E0en1gP\u002fqIjfBr1F4\u002fLyYUrJjLXD9zGv5MT99aP\u002fPx6kRpDlk\u002fU9a8IcVXVz8GQD7iRrpVP52mwEPYNFQ\u002fZeK3CGnGUj\u002fKilw4721RP6f9ZVdnKlA\u002fvbnlM6n1TT9UV3kdgrxLP3CJcYd7p0k\u002fXkGcyMW0Rz8QmZiin+JFP5f4qGVWL0Q\u002foqrTCEaZQj8HsMM22R5BP8fmxJ8SfT8\u002fsApSxLztPD80flku3ow6P0Qq3zHBVzg\u002fZ\u002fj\u002f28xLNj+RJO6ahGY0P+5+y9aHpTI\u002fwYaFfJEGMT8wfrD37g4vP\u002flIOHNUTCw\u002fZaED72fBKT9EObVkcGonPwc\u002fEvDlQyU\u002fA3NdbHBKIz9aNEkF5nohP\u002f+FG3uTpB8\u002fNRo\u002f2pObHD8oX9xxfdUZP9tzAAxRTRc\u002fY5VT01z+FD+bJwoxOeQSP1ajd6vF+hA\u002fPzdgkEt8Dj9R4OToe1ULP5X9w+xgegg\u002fK9jF0rTkBT++rHCPoY4DP3SOoAa7cgE\u002f4+3YvfIX\u002fz4fCMzzZqv7PqKYviczl\u002fg+RMQFG2DT9T7aqVvClFjzPiqSRYsMIPE+\u002fUZh\u002fRtH7j7c7U+Fw7rqPl0\u002f3PiTkOc+qlZpJ\u002fS+5D6x90uuHT3iPmH78gMOA+A+1n9eh\u002fAS3D4fJ6lvb5PYPt61Xj6Ge9U+2BxBVEHA0j6icDVWtVfQPi5y2dbTccw+2buInIu3yD4WvUtJ+HHFPv8XHCj2k8I+carICbcRwD6bH6UhSMG7PrrB+FCD7rc+WNNzGSuatD6gGhORHbWxPtZlIUG+Y64+yI8Id+MIqj4cSVDQXkSmPtYPtsdPA6M+uco2ZQI1oD5VUD2ka5WbPlZ2tgjQbpc+UPoETUzfkz4VKgrLwtKQPnzvzkIfb4w+kuJL8oP8hz48g4xzuDKEPmYN6jg1+oA+LxdgXP58fD5XUmd\u002fltt3PnOI0kZX8XM+N97TCIyjcD7KqbwBR7ZrPjgxw9V1CGc+Vt3frmwbYz56MKmyk6NfPi4zYoe\u002fJFo+dLaHcUyPVT7mr4Sxeb5RPvE7S+rzJU0+SCnQH2XkRz7DW8c5JotDPgdYbR0L6D8+asx07IL9OT56l9TzVSA1PtIrC5fMIjE+NTdzgxW9Kz4x6J7h5WYmPlsZ9o1iDSI+3DGLKaYHHT7Rlej6DUoXPtY46lZGpBI+yKm3TnPGDT6k768kb7kHPtzqOqL62wI+9VxryuDp\u002fT2jNebE56r3PcStFGdervI9Hr6MvmVr7T1vaJut6hvnPcLkAu6QG+I9mom9K6lO3D0jk5yHHhLWPWXToGE4KtE97fVjW5Whyj2f7GToIZvEPZUET\u002f8rzr89yQcZ9U17uD36YvkFb8uyPShFANnZx6w9CV0lOx\u002f6pT04vcpVfLygPf3akX23a5k9zK3CoKZAkz1+eht4rRSNPZoFdnm\u002f5oU9ARHkPJVygD13HguGD6J4PSDWLD2NZHI9OS6sC\u002fliaz2+XXuZQ1RkPWZiomVVF149bGiENSM0Vj1luGEVh1VQPV\u002fUwwN+9Uc9XcbZ13eEQT2hExY+NIk5PfRvS1XWjTI9e8Jo1HngKj2W0kxXgmcjPc4RP\u002fKt7Rs95\u002fMl33wIFD3EYtwINaUMPet20I1+aQQ9aimYlUP+\u002fDxZWWa+fIX0PDdDlFeH8+w8Hp2XuBBa5DwkP7QfnoPcPIJBTyS259M84Vqf5UyxyzxxxpoM2THDPDm1Lhlvg7o8kvwvArA+sjzEJt29nASpPOTLedrjFqE8v+\u002fb\u002f4xClzw3c+oTF4qPPEimIMBBTYU8EScKGw6qfDwMA+grDjZzPOGpGk6Ppmk8hfcVFI4OYTybSBLVKphWPIuOwcRJz008rfRH+8OVQzxeGQMsM6E5PGSlfQVtszA8SZRtsMKsJTxn8\u002f\u002flHAMcPAktIBRJBhI8yFjSpcsYBzxxLcXgDXj9O7P19jy9t\u002fI7vQ82TGSs5zvBkhFM+c7dO8UHmxXQrtI7mZgZ3VVQxzsSLUDqz\u002fW8OxNhS5CI57E7WmPWHjMJpjun04SCc\u002f6aO2gFBJSpdJA7qOkWoq33gzsA0iDOvRx4O5CwEvfn+Ww7uKClgU9TYTs\u002fcVa2uZ1UO+X7wWPZaEg78jclIXbBPDtcUHo2FtowO8Dr+r2JpiM7R2t1marLFjt+iY+7XE4KOznSKWiTMv460iFTzYY98Tq0OowwiJTjOvyJS74QHtY6Z0EXf+LYyDq7iA93oMK7Ot1s\u002fCO11646fOpwrJ8JoTo4C9qRg7eSOnS9BCa+cYQ6o3xb4d8zdjpBVcaE3PhnOqhocBgcu1k6r4iDRJJ0Szr9bWfK2x49OlEpBJdhsy464GVwS8AVIDrJTh+UWsAQOmZTMWFkVgE6F3d8GADV8TleF7JNpDniOcQ82r4wgtI5IlVU3wCtwjl+a3oW+7iyOTCP8PKbpaI5VN0uu\u002fxykjl5EDL11CGCOe959rd2s3E5h0ZX1sUpYTkmw5swKodQOSxwaGn7nD85Ckahku0FLjnEWrIUIFAcOf2Girbcggo5KyAYx4yl+DiCUC4Uhb\u002fmOAG+1OzU19Q4fhiMBBr1wjjLku+9Wh2xOHc2mgTQq544VVzov45GizgivFlySxJ4OMZMHMKkFGU4lzxjQIRRUjhOmMIbZZY\u002fOObItsXuBCs4yHhg1oXtFjhW7Xh1u0wDOK27Szo5HfA3ggrgo1aw2jeGSlkKUevFN9u1l18o2rE3XrV8SNzVnDeQAvMlMxeHNx5\u002fAHcSVXI328oMF6nbXDfg50ohF4RGN44MRRvXaTE3Ze7gZiKyGjeqsyJzvUcEN9iB\u002fEb0iO423iaT0kDH1jagYtYyB9bANj7WvdNGqKg26njJ6CHjkTYnMrNyC7V5NkAsAiQTTGI2XrYu\u002fMfLSTbcOhxbBwIyNlm5diKC5Rg2FbB6uFUKATZS\u002f+zQKBjnNStj6vol\u002fM41Uo1j5D6TtDUHXlvOzAubNdsG49C9l4E1RYzPpCemZjVGwTkvOtpMNd3rXwHMLjI1l07EITOsFjX0yyaPYff7NAIDy6dyD+E0qZVPrxGWxDS9yyjPzZCoND35Yl2b\u002fIw07yCgpbDocDSlv3mKsYBTNENrVrJ1PDY0FVxsaCUPGTTscPIXHen7Mx+B7NZUuN4z9zLhWn+0wDPjC3CkK\u002fOhM7PW+1XPDYMzfmqzqZX6YzOfrJj+wbBEMwuYP8Y1KSUzqJ3nguReBTN5i0DAK0\u002flMrJdaNkG+sQysPwPOxlipDJ+LWlajYyDMuLGkm\u002fKgGIyfoXZtQZIQTKnGoovftkfMlebScc+9PwxGSN1YM722THIIRvArva2MaCSyoxlB5Qx9z4kFbQ5cTFed5dmHzZNMfEDpaG8aigxQ0CScegdBDH\u002fHKgMllXgMCNGEVSWI7owxBxrn0qblDBUytke\u002fgBwMCiRKW5nfEgwHm4ra9ZyIjBC7b18KGD7L8oGrc+k\u002f9MvrC9WI\u002fDDrC8wKOaEE12EL\u002fRsIwE9YFwvgfm7QIl0My8SIYuwsT8KL1yOl5SCa+EueaHv9yy+ti54znKTqTONLrI4bZpib2IueiaAQILiNi58bspLOO0LLnxuHHt+v+AtMyQGQMK9sy3UPb+0NN2GLe2zAjb7A1ot+j5gEZoTLS0aKsOInOr\u002fLF1\u002fDJdjM9Eslu9IndMzoix9OJ1Fn+hyLP+JGipAR0Msg7qprahJEywrfkx95O7iK4p+7sVCO7IrNTpPJgI4gSuwwV2rDeVPK9Yd+NJl9hwrAwuR9QjI6SoEOEVoR362KjXavUcVO4MqKMi7v9QbUCqqTxwQmG8aKkoGyDC6PuUpfMiJxfO3sCkK1zP148J5KXJrl4p8bUMp7IJBgVmtDCmf+ef6nrXUKGkd9HbpQp0olHoz5Ss4ZCjG5G8oRVMrKMsVZGBrDfIn7ejpaORQtydiBYYYRW59JzmFDvc0JkInYB2s5QneBSf3mzQErrvJJse9beIDko0m93U0+kGWUCYYiePYuykSJvHg+hilaNMldfM6tSI8lCWnNq4A9JNUJb1MDv98aBQlWneFM9a70yQJ2OhTrpmSJC2Km\u002ff8FVEk3k5oD3OVDiRT+Q7a56nKI5I1LxqHo4Yjdc0sYgG3QiPPBfhvlx7+IrRZ881BlrciWh7ke7D4cSJwJvLLYqMqInvWxb\u002fcMuMhbnFNzq3nmiG\u002fFbbS8lJSIYMUJ0kKQQghzb2dQgwwvyC6p3n+kHlzICR+YQSvnScgY5lBIpbM2x\u002fuloQrxcGPHwAoZuQ\u002fmUEfiDzDNT7r8h7XKfsku7ijHhlYJgr37VMegTZdU66EAx739x8WQYWyHeSPrfmfBWEdWPAXkFtMDh1dZRSaZhq6HKh5qK+Tw2UcX50f41uOERw4CW4I\u002fmS7Gy4bEid9qmQb9pK8obAjDhuJzo0\u002f3jy1GuIyM2Pv6VwaSlVtGPsBAxpfIU1\u002fmCCoGTA4Jv\u002fajk0Z78OOBwB48RgRJcCqWeqTGNCVjqdm5DUYjz3nAkMy1xddzJPz3K53Fw0up\u002fKrShcXvf+ywNEOthZQTHSkyRtUFtFLT6rko\u002fEVi9avHL7FjRWs3wbxCSkoFUcrhOrh2MIUCC0lO8RBXBQ2Z8JIFln0E4Qw3h8mJIwT8jJ7twOuIhMiHnpq1cu3Euf2ZPD3E00SN7RpaggJ4RH7034LyiFzEXZlFSlklgQRsCf1NOg3lRAtEQFU9u8kEHQYU0LGxbMPt+sWSwzdQQ\u002fLuM5t3N3ODs3Ck03ofVkOd01G+t0d5A1RXi5LWFNuDfUxEGzJ0vUMlMXA9Oj5fQw2rTp3u6MDDEOrkOQeiogLoGUpc8k5DQsaEKmSxJSQClBEshL26hEK0FLIEnVukglWYEMPXgoSCYBFy2dOy5AIeM9oU6y5DQjv3ea7Wf+IBw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"t=%{x:.1f}  S=%{y:.3f}","line":{"color":"#27ae60","width":3},"name":"S(t)  limbo","x":{"dtype":"f8","bdata":"AAAAAAAAAADwrmK0k22mP\u002fCuYrSTbbY\u002fNANKxy7SwD\u002fwrmK0k23GP6xae6H4CMw\u002fNANKxy7S0D8SWdY94Z\u002fTP\u002fCuYrSTbdY\u002fzgTvKkY72T+sWnuh+AjcP4qwBxir1t4\u002fNANKxy7S4D8jLpACCDniPxJZ1j3hn+M\u002fAYQceboG5T\u002fwrmK0k23mP9\u002fZqO9s1Oc\u002fzgTvKkY76T+9LzVmH6LqP6xae6H4COw\u002fm4XB3NFv7T+KsAcYq9buP7ztpinCHvA\u002fNANKxy7S8D+sGO1km4XxPyMukAIIOfI\u002fmkMzoHTs8j8SWdY94Z\u002fzP4puedtNU\u002fQ\u002fAYQceboG9T94mb8WJ7r1P\u002fCuYrSTbfY\u002faMQFUgAh9z\u002ff2ajvbNT3P1bvS43Zh\u002fg\u002fzgTvKkY7+T9GGpLIsu75P70vNWYfovo\u002fNEXYA4xV+z+sWnuh+Aj8PyRwHj9lvPw\u002fm4XB3NFv\u002fT8Sm2R6PiP+P4qwBxir1v4\u002fAsaqtReK\u002fz+87aYpwh4AQHh4eHh4eABANANKxy7SAEDwjRsW5SsBQKwY7WSbhQFAZ6O+s1HfAUAjLpACCDkCQN+4YVG+kgJAmkMzoHTsAkBWzgTvKkYDQBJZ1j3hnwNAzuOnjJf5A0CKbnnbTVMEQEX5SioErQRAAYQceboGBUC9Du7HcGAFQHiZvxYnugVANCSRZd0TBkDwrmK0k20GQKw5NANKxwZAaMQFUgAhB0AjT9egtnoHQN\u002fZqO9s1AdAm2R6PiMuCEBW70uN2YcIQBJ6HdyP4QhAzgTvKkY7CUCKj8B5\u002fJQJQEYaksiy7glAAaVjF2lICkC9LzVmH6IKQHm6BrXV+wpANEXYA4xVC0Dwz6lSQq8LQKxae6H4CAxAaOVM8K5iDEAkcB4\u002fZbwMQN\u002f6740bFg1Am4XB3NFvDUBXEJMriMkNQBKbZHo+Iw5AziU2yfR8DkCKsAcYq9YOQEY72WZhMA9AAsaqtReKD0C9UHwEzuMPQLztpinCHhBAGrMPUZ1LEEB4eHh4eHgQQNY94Z9TpRBANANKxy7SEECSyLLuCf8QQPCNGxblKxFATlOEPcBYEUCsGO1km4URQAneVYx2shFAZ6O+s1HfEUDFaCfbLAwSQCMukAIIORJAgfP4KeNlEkDfuGFRvpISQD1+yniZvxJAmkMzoHTsEkD4CJzHTxkTQFbOBO8qRhNAtJNtFgZzE0ASWdY94Z8TQHAeP2W8zBNAzuOnjJf5E0AsqRC0ciYUQIpuedtNUxRA5zPiAimAFEBF+UoqBK0UQKO+s1Hf2RRAAYQceboGFUBfSYWglTMVQL0O7sdwYBVAG9RW70uNFUB4mb8WJ7oVQNZeKD4C5xVANCSRZd0TFkCS6fmMuEAWQPCuYrSTbRZATnTL226aFkCsOTQDSscWQAr\u002fnCol9BZAaMQFUgAhF0DFiW55200XQCNP16C2ehdAgRRAyJGnF0Df2ajvbNQXQD2fERdIARhAm2R6PiMuGED5KeNl\u002floYQFbvS43ZhxhAtLS0tLS0GEASeh3cj+EYQHA\u002fhgNrDhlAzgTvKkY7GUAsyldSIWgZQIqPwHn8lBlA6FQpodfBGUBGGpLIsu4ZQKPf+u+NGxpAAaVjF2lIGkBfasw+RHUaQL0vNWYfohpAG\u002fWdjfrOGkB5uga11fsaQNd\u002fb9ywKBtANEXYA4xVG0CSCkErZ4IbQPDPqVJCrxtATpUSeh3cG0CsWnuh+AgcQAog5MjTNRxAaOVM8K5iHEDGqrUXio8cQCRwHj9lvBxAgTWHZkDpHEDf+u+NGxYdQD3AWLX2Qh1Am4XB3NFvHUD5SioErZwdQFcQkyuIyR1AtdX7UmP2HUASm2R6PiMeQHBgzaEZUB5AziU2yfR8HkAs657wz6keQIqwBxir1h5A6HVwP4YDH0BGO9lmYTAfQKQAQo48XR9AAsaqtReKH0BfixPd8rYfQL1QfATO4x9ADovylVQIIEC87aYpwh4gQGtQW70vNSBAGrMPUZ1LIEDJFcTkCmIgQHh4eHh4eCBAJ9ssDOaOIEDWPeGfU6UgQIWglTPBuyBANANKxy7SIEDjZf5anOggQJLIsu4J\u002fyBAQStngncVIUDwjRsW5SshQJ\u002fwz6lSQiFATlOEPcBYIUD9tTjRLW8hQKwY7WSbhSFAWnuh+AicIUAJ3lWMdrIhQLhACiDkyCFAZ6O+s1HfIUAWBnNHv\u002fUhQMVoJ9ssDCJAdMvbbpoiIkAjLpACCDkiQNKQRJZ1TyJAgfP4KeNlIkAwVq29UHwiQN+4YVG+kiJAjhsW5SupIkA9fsp4mb8iQOzgfgwH1iJAmkMzoHTsIkBJpucz4gIjQPgInMdPGSNAp2tQW70vI0BWzgTvKkYjQAUxuYKYXCNAtJNtFgZzI0Bj9iGqc4kjQBJZ1j3hnyNAwbuK0U62I0BwHj9lvMwjQB+B8\u002fgp4yNAzuOnjJf5I0B9RlwgBRAkQCypELRyJiRA2wvFR+A8JECKbnnbTVMkQDjRLW+7aSRA5zPiAimAJECWlpaWlpYkQEX5SioErSRA9Fv\u002fvXHDJECjvrNR39kkQFIhaOVM8CRAAYQceboGJUCw5tAMKB0lQF9JhaCVMyVADqw5NANKJUC9Du7HcGAlQGxxolvediVAG9RW70uNJUDKNguDuaMlQHiZvxYnuiVAJ\u002fxzqpTQJUDWXig+AuclQIXB3NFv\u002fSVANCSRZd0TJkDjhkX5SiomQJLp+Yy4QCZAQUyuICZXJkDwrmK0k20mQJ8RF0gBhCZATnTL226aJkD91n9v3LAmQKw5NANKxyZAW5zolrfdJkAK\u002f5wqJfQmQLlhUb6SCidAaMQFUgAhJ0AWJ7rlbTcnQMWJbnnbTSdAdOwiDUlkJ0AjT9egtnonQNKxizQkkSdAgRRAyJGnJ0Awd\u002fRb\u002f70nQN\u002fZqO9s1CdAjjxdg9rqJ0A9nxEXSAEoQOwBxqq1FyhAm2R6PiMuKEBKxy7SkEQoQPkp42X+WihAqIyX+WtxKEBW70uN2YcoQAVSACFHnihAtLS0tLS0KEBjF2lIIssoQBJ6HdyP4ShAwdzRb\u002f33KEBwP4YDaw4pQB+iOpfYJClAzgTvKkY7KUB9Z6O+s1EpQCzKV1IhaClA2ywM5o5+KUCKj8B5\u002fJQpQDnydA1qqylA6FQpodfBKUCXt900RdgpQEYaksiy7ilA9HxGXCAFKkCj3\u002frvjRsqQFJCr4P7MSpAAaVjF2lIKkCwBxir1l4qQF9qzD5EdSpADs2A0rGLKkC9LzVmH6IqQGyS6fmMuCpAG\u002fWdjfrOKkDKV1IhaOUqQHm6BrXV+ypAKB27SEMSK0DXf2\u002fcsCgrQIbiI3AePytANEXYA4xVK0Djp4yX+WsrQJIKQStngitAQW31vtSYK0Dwz6lSQq8rQJ8yXuavxStATpUSeh3cK0D998YNi\u002fIrQKxae6H4CCxAW70vNWYfLEAKIOTI0zUsQLmCmFxBTCxAaOVM8K5iLEAXSAGEHHksQMaqtReKjyxAdQ1qq\u002felLEAkcB4\u002fZbwsQNLS0tLS0ixAgTWHZkDpLEAwmDv6rf8sQN\u002f6740bFi1Ajl2kIYksLUA9wFi19kItQOwiDUlkWS1Am4XB3NFvLUBK6HVwP4YtQPlKKgStnC1AqK3elxqzLUBXEJMriMktQAZzR7\u002f13y1AtdX7UmP2LUBkOLDm0AwuQBKbZHo+Iy5Awf0YDqw5LkBwYM2hGVAuQB\u002fDgTWHZi5AziU2yfR8LkB9iOpcYpMuQCzrnvDPqS5A201ThD3ALkCKsAcYq9YuQDkTvKsY7S5A6HVwP4YDL0CX2CTT8xkvQEY72WZhMC9A9Z2N+s5GL0CkAEKOPF0vQFNj9iGqcy9AAsaqtReKL0CwKF9JhaAvQF+LE93yti9ADu7HcGDNL0C9UHwEzuMvQGyzMJg7+i9ADovylVQIMEBlvMxfixMwQLztpinCHjBAFB+B8\u002fgpMEBrUFu9LzUwQMOBNYdmQDBAGrMPUZ1LMEBy5Oka1FYwQMkVxOQKYjBAIUeerkFtMEB4eHh4eHgwQNCpUkKvgzBAJ9ssDOaOMEB\u002fDAfWHJowQNY94Z9TpTBALm+7aYqwMECFoJUzwbswQN3Rb\u002f33xjBANANKxy7SMECLNCSRZd0wQONl\u002flqc6DBAOpfYJNPzMECSyLLuCf8wQOn5jLhACjFAQStngncVMUCYXEFMriAxQPCNGxblKzFAR7\u002f13xs3MUCf8M+pUkIxQPYhqnOJTTFATlOEPcBYMUClhF4H92MxQP21ONEtbzFAVOcSm2R6MUCsGO1km4UxQANKxy7SkDFAWnuh+AicMUCyrHvCP6cxQAneVYx2sjFAYQ8wVq29MUC4QAog5MgxQBBy5Oka1DFAZ6O+s1HfMUC\u002f1Jh9iOoxQBYGc0e\u002f9TFAbjdNEfYAMkDFaCfbLAwyQB2aAaVjFzJAdMvbbpoiMkDM\u002fLU40S0yQCMukAIIOTJAel9qzD5EMkDSkESWdU8yQCnCHmCsWjJAgfP4KeNlMkDYJNPzGXEyQDBWrb1QfDJAh4eHh4eHMkDfuGFRvpIyQDbqOxv1nTJAjhsW5SupMkDlTPCuYrQyQD1+yniZvzJAlK+kQtDKMkDs4H4MB9YyQEMSWdY94TJAmkMzoHTsMkDydA1qq\u002fcyQEmm5zPiAjNAodfB\u002fRgOM0D4CJzHTxkzQFA6dpGGJDNAp2tQW70vM0D\u002fnCol9DozQFbOBO8qRjNArv\u002feuGFRM0AFMbmCmFwzQF1ik0zPZzNAtJNtFgZzM0AMxUfgPH4zQGP2IapziTNAuyf8c6qUM0ASWdY94Z8zQGmKsAcYqzNAwbuK0U62M0AY7WSbhcEzQHAeP2W8zDNAx08ZL\u002fPXM0AfgfP4KeMzQHayzcJg7jNAzuOnjJf5M0AlFYJWzgQ0QH1GXCAFEDRA1Hc26jsbNEAsqRC0ciY0QIPa6n2pMTRA2wvFR+A8NEAyPZ8RF0g0QIpuedtNUzRA4Z9TpYReNEA40S1vu2k0QJACCDnydDRA5zPiAimANEA\u002fZbzMX4s0QJaWlpaWljRA7sdwYM2hNEBF+UoqBK00QJ0qJfQ6uDRA9Fv\u002fvXHDNEBMjdmHqM40QKO+s1Hf2TRA+++NGxblNEBSIWjlTPA0QKpSQq+D+zRAAYQceboGNUBYtfZC8RE1QLDm0AwoHTVABxir1l4oNUBfSYWglTM1QLZ6X2rMPjVADqw5NANKNUBl3RP+OVU1QL0O7sdwYDVAFEDIkadrNUBscaJb3nY1QMOifCUVgjVAG9RW70uNNUByBTG5gpg1QMo2C4O5ozVAIWjlTPCuNUB4mb8WJ7o1QNDKmeBdxTVAJ\u002fxzqpTQNUB\u002fLU50y9s1QNZeKD4C5zVALpACCDnyNUCFwdzRb\u002f01QN3ytpumCDZANCSRZd0TNkCMVWsvFB82QOOGRflKKjZAO7gfw4E1NkCS6fmMuEA2QOoa1FbvSzZAQUyuICZXNkCZfYjqXGI2QPCuYrSTbTZAR+A8fsp4NkCfERdIAYQ2QPZC8RE4jzZATnTL226aNkClpaWlpaU2QP3Wf2\u002fcsDZAVAhaORO8NkCsOTQDSsc2QANrDs2A0jZAW5zolrfdNkCyzcJg7ug2QAr\u002fnCol9DZAYTB39Fv\u002fNkC5YVG+kgo3QBCTK4jJFTdAaMQFUgAhN0C\u002f9d8bNyw3QBYnuuVtNzdAbliUr6RCN0DFiW552003QB27SEMSWTdAdOwiDUlkN0DMHf3Wf283QCNP16C2ejdAe4Cxau2FN0DSsYs0JJE3QCrjZf5anDdAgRRAyJGnN0DZRRqSyLI3QDB39Fv\u002fvTdAiKjOJTbJN0Df2ajvbNQ3QDYLg7mj3zdAjjxdg9rqN0DlbTdNEfY3QD2fERdIAThAlNDr4H4MOEDsAcaqtRc4QEMzoHTsIjhAm2R6PiMuOEDylVQIWjk4QErHLtKQRDhAofgInMdPOED5KeNl\u002flo4QFBbvS81ZjhAqIyX+WtxOED\u002fvXHDonw4QFbvS43ZhzhAriAmVxCTOEAFUgAhR544QF2D2up9qThAtLS0tLS0OEAM5o5+6784QGMXaUgiyzhAu0hDElnWOEASeh3cj+E4QGqr96XG7DhAwdzRb\u002f33OEAZDqw5NAM5QHA\u002fhgNrDjlAyHBgzaEZOUAfojqX2CQ5QHfTFGEPMDlAzgTvKkY7OUAlNsn0fEY5QH1no76zUTlA1Jh9iOpcOUAsyldSIWg5QIP7MRxYczlA2ywM5o5+OUAyXuavxYk5QIqPwHn8lDlA4cCaQzOgOUA58nQNaqs5QJAjT9egtjlA6FQpodfBOUA\u002fhgNrDs05QJe33TRF2DlA7ui3\u002fnvjOUBGGpLIsu45QJ1LbJLp+TlA9HxGXCAFOkBMriAmVxA6QKPf+u+NGzpA+xDVucQmOkBSQq+D+zE6QKpziU0yPTpAAaVjF2lIOkBZ1j3hn1M6QLAHGKvWXjpACDnydA1qOkBfasw+RHU6QLebpgh7gDpADs2A0rGLOkBm\u002flqc6JY6QL0vNWYfojpAFGEPMFatOkBskun5jLg6QMPDw8PDwzpAG\u002fWdjfrOOkByJnhXMdo6QMpXUiFo5TpAIYks657wOkB5uga11fs6QNDr4H4MBztAKB27SEMSO0B\u002fTpUSeh07QNd\u002fb9ywKDtALrFJpuczO0CG4iNwHj87QN0T\u002fjlVSjtANEXYA4xVO0CMdrLNwmA7QOOnjJf5aztAO9lmYTB3O0CSCkErZ4I7QOo7G\u002fWdjTtAQW31vtSYO0CZns+IC6Q7QPDPqVJCrztASAGEHHm6O0CfMl7mr8U7QPdjOLDm0DtATpUSeh3cO0CmxuxDVOc7QP33xg2L8jtAVSmh18H9O0CsWnuh+Ag8QAOMVWsvFDxAW70vNWYfPECy7gn\u002fnCo8QAog5MjTNTxAYVG+kgpBPEC5gphcQUw8QBC0ciZ4VzxAaOVM8K5iPEC\u002fFie65W08QBdIAYQceTxAbnnbTVOEPEDGqrUXio88QB3cj+HAmjxAdQ1qq\u002felPEDMPkR1LrE8QCRwHj9lvDxAe6H4CJzHPEDS0tLS0tI8QCoErZwJ3jxAgTWHZkDpPEDZZmEwd\u002fQ8QDCYO\u002fqt\u002fzxAiMkVxOQKPUDf+u+NGxY9QDcsyldSIT1Ajl2kIYksPUDmjn7rvzc9QD3AWLX2Qj1AlfEyfy1OPUDsIg1JZFk9QERU5xKbZD1Am4XB3NFvPUDytpumCHs9QErodXA\u002fhj1AoRlQOnaRPUD5SioErZw9QFB8BM7jpz1AqK3elxqzPUD\u002f3rhhUb49QFcQkyuIyT1ArkFt9b7UPUAGc0e\u002f9d89QF2kIYks6z1AtdX7UmP2PUAMB9YcmgE+QGQ4sObQDD5Au2mKsAcYPkASm2R6PiM+QGrMPkR1Lj5Awf0YDqw5PkAZL\u002fPX4kQ+QHBgzaEZUD5AyJGna1BbPkAfw4E1h2Y+QHf0W\u002f+9cT5AziU2yfR8PkAmVxCTK4g+QH2I6lxikz5A1bnEJpmePkAs657wz6k+QIQceboGtT5A201ThD3APkAzfy1OdMs+QIqwBxir1j5A4eHh4eHhPkA5E7yrGO0+QJBElnVP+D5A6HVwP4YDP0A\u002fp0oJvQ4\u002fQJfYJNPzGT9A7gn\u002fnColP0BGO9lmYTA\u002fQJ1sszCYOz9A9Z2N+s5GP0BMz2fEBVI\u002fQKQAQo48XT9A+zEcWHNoP0BTY\u002fYhqnM\u002fQKqU0Ovgfj9AAsaqtReKP0BZ94R\u002fTpU\u002fQLAoX0mFoD9ACFo5E7yrP0BfixPd8rY\u002fQLe87aYpwj9ADu7HcGDNP0BmH6I6l9g\u002fQL1QfATO4z9AFYJWzgTvP0BsszCYO\u002fo\u002fQGJyBTG5AkBADovylVQIQEC5o9\u002f67w1AQGW8zF+LE0BAEdW5xCYZQEC87aYpwh5AQGgGlI5dJEBAFB+B8\u002fgpQEDAN25YlC9AQGtQW70vNUBAF2lIIss6QEDDgTWHZkBAQG+aIuwBRkBAGrMPUZ1LQEDGy\u002fy1OFFAQHLk6RrUVkBAHv3Wf29cQEDJFcTkCmJAQHUusUmmZ0BAIUeerkFtQEDNX4sT3XJAQHh4eHh4eEBAJJFl3RN+QEDQqVJCr4NAQHvCP6dKiUBAJ9ssDOaOQEDT8xlxgZRAQH8MB9YcmkBAKiX0OrifQEDWPeGfU6VAQIJWzgTvqkBALm+7aYqwQEDZh6jOJbZAQIWglTPBu0BAMbmCmFzBQEDd0W\u002f998ZAQIjqXGKTzEBANANKxy7SQEDgGzcsytdAQIs0JJFl3UBAN00R9gDjQEDjZf5anOhAQI9+67837kBAOpfYJNPzQEDmr8WJbvlAQJLIsu4J\u002f0BAPuGfU6UEQUDp+Yy4QApBQJUSeh3cD0FAQStngncVQUDtQ1TnEhtBQJhcQUyuIEFARHUusUkmQUDwjRsW5StBQJumCHuAMUFAR7\u002f13xs3QUDz1+JEtzxBQJ\u002fwz6lSQkFASgm9Du5HQUD2IapziU1BQKI6l9gkU0FATlOEPcBYQUD5a3GiW15BQKWEXgf3Y0FAUZ1LbJJpQUD9tTjRLW9BQKjOJTbJdEFAVOcSm2R6QUAAAAAAAIBBQA=="},"y":{"dtype":"f8","bdata":"AAAAAAAA8D+HmgnE8P\u002fvP1rJK8bC\u002f+8\u002f3HzMcXX\u002f7z85mQgxCP\u002fvPyu1tWx6\u002fu8\u002fLAJkjMv97z83X2D2+vzvPyuXtg8I\u002fO8\u002f7MszPPL67z95D2neuPnvP\u002forrldb+O8\u002fG5wkCNn27z+3tLpOMfXvPykBL4lj8+8\u002fVdMTFG\u002fx7z++CNNKU+\u002fvP8oFsocP7e8\u002fhOjVI6Pq7z8S9Ed3DejvPxo2+thN5e8\u002fcmfMnmPi7z9FCZEdTt\u002fvPxPAEqkM3O8\u002fvO0ZlJ7Y7z\u002f4i3IwA9XvP4dI8s450e8\u002fWuR+v0HN7z8i1xRRGsnvP3o4ztHCxO8\u002fHPDpjjrA7z9fLtPUgLvvP14uKe+Utu8\u002fIEPHKHax7z8GMc3LI6zvP+rUpyGdpu8\u002fLhoac+Gg7z8bQUYI8JrvP9t2tyjIlO8\u002fY8BrG2mO7z+TOd4m0ofvP96pEZECge8\u002fwm+bn\u002fl57z9SxK6XtnLvPxlYKL44a+8\u002fjkqaV39j7z9bfVioiVvvP6ZEhfRWU+8\u002fiHUegOZK7z\u002fs0wqPN0LvP+XgJ2VJOe8\u002frApYRhsw7z9JP5F2rCbvPwrj6zn8HO8\u002ftyuy1AkT7z+X4W+L1AjvPyWHAqNb\u002fu4\u002fcOipYJ7z7j\u002f0EhkKnOjuP9G2h+VT3e4\u002fGvLDOcXR7j\u002f4hkRO78XuP1t9O2vRue4\u002fyDCp2Wqt7j\u002fdym\u002fjuqDuPw8rZ9PAk+4\u002fCzxx9XuG7j8it46W63juPwZW9AQPa+4\u002fHnMgkOVc7j+NGPGIbk7uPwh+ukGpP+4\u002ffPVdDpUw7j9lRmFEMSHuP6p3Bjt9Ee4\u002fwAdkS3gB7j+pkn3QIfHtP2nlXCd54O0\u002fSH4rr33P7T9GeUzJLr7tP\u002fbndtmLrO0\u002fxpPQRZSa7T\u002fLKQl3R4jtP9TOddikde0\u002fjRot2Kti7T9FeCPnW0\u002ftP9jrR3m0O+0\u002fAjmhBbUn7T9ga2sGXRPtPxa9Nfmr\u002fuw\u002fCdoAX6Hp7D93fV28PNTsP2Jni5l9vuw\u002fYaeYgmOo7D\u002f8OYEH7pHsP7\u002f1Trwce+w\u002f3cQ5Oe9j7D8zKMgaZUzsPy0A8AF+NOw\u002f+Zc3lDkc7D8079Z7lwPsPxA+2WeX6us\u002fy68+DDnR6z8MTx4ifLfrP5AfyGdgnes\u002fZmDnoOWC6z+r8aSWC2jrP6bYyRfSTOs\u002fx9zh+Dgx6z\u002fsN14UQBXrPyVUuErn+Oo\u002fx5CUgi7c6j+gCOWoFb\u002fqP8FSDLGcoeo\u002fGzcAlcOD6j8IUGxVimXqP4uR1PnwRuo\u002f2q63kPcn6j+bVrEvngjqP+w9nPPk6Ok\u002fKPKzAMzI6T8TabaCU6jpP+BGBa17h+k\u002fWNHGukRm6T8dhwbvrkTpP8ZR1ZS6Iuk\u002fjElp\u002f2cA6T+5AD2Kt93oPxFNLpmpuug\u002fKYWcmD6X6D9WJ4b9dnPoP87fpUVTT+g\u002fNuOO99Mq6D\u002ffksii+QXoP4Vf6d\u002fE4Oc\u002ff9+wUDa75z\u002fMDCGgTpXnP5ufloIOb+c\u002fenngtXZI5z9dFVYBiCHnP37w7DVD+uY\u002f899MLqnS5j\u002fERuPOuqrmPyUg9QV5guY\u002fadGvy+RZ5j8ktzgi\u002fzDmP\u002fVguxXJB+Y\u002fTW92vEPe5T+NBsc2cLTlP8LKMq9PiuU\u002fXldwWuNf5T8xJm53LDXlPwDZV08sCuU\u002fGNmZNeTe5D9iQOOHVbPkP2z\u002fJa6Bh+Q\u002fIzSVGmpb5D\u002f0pKFJEC\u002fkP0dU9MF1AuQ\u002fcSBnFJzV4z9QZPvbhKjjPziNzr0xe+M\u002fwpoMaaRN4z+ifeCW3h\u002fjP8ZKYgri8eI\u002fTjiDkLDD4j9rWvf\u002fS5XiP2gWHTm2ZuI\u002fnULiJfE34j906qa5\u002fgjiPxyuHfHg2eE\u002f\u002frUp0pmq4T+VMrprK3vhP8Rho9WXS+E\u002fbhN1MOEb4T+Tp06lCezgP+2AsGUTvOA\u002foudKqwCM4D9gWMq301vgP9Q8odSOK+A\u002fsxaepWj23z+gmEkVjZXfPwsJDrePNN8\u002fwglaV3XT3j+yQLvPQnLePyCUUgb9EN4\u002fZz1D7aiv3T9EuRyCS07dP6efP83p7Nw\u002ffG494YiL3D\u002f+VDPaLSrcP2gPIN3dyNs\u002fseQ0F55n2z+I2iG9cwbbP6A1XQpkpdo\u002f6l5mQHRE2j92SASmqePZPw1vf4YJg9k\u002fApjXMJki2T\u002f5bPX2XcLYP9MZ2CxdYtg\u002fZxO\u002fJ5wC2D\u002fTL1A9IKPXPxs8usLuQ9c\u002fmDzUCw3l1j+XiDlqgIbWP8bzYixOKNY\u002fejq9nHvK1T8f6LwADm3VP43x75cKENU\u002fZj8Nm3az1D+tZgI7V1fUPzbQ\u002f5+x+9M\u002fmZGD6Iqg0z+WPGMo6EXTP7zr1GfO69I\u002fUtV3okKS0j8qr1zGSTnSP10wDrPo4NE\u002f4v6ZOCSJ0T9qWZoWATLRP+7OQPuD29A\u002fAldigrGF0D88H4U0jjDQP5XG4As9uM8\u002fg0x9q80Qzz9kSeXZ1mrOP6yvHvtgxs0\u002fhMVmQ3QjzT8RhnC1GILMPw09qyBW4ss\u002fdRCSHzREyz\u002f7KwUWuqfKP4BBrS\u002fvDMo\u002f8BBqXtpzyT8tqcxYgtzIP0IRnpjtRsg\u002fHQhzWSKzxz8Qhk2XJiHHP+OoTA0AkcY\u002f8K9rNLQCxj8\u002fqlBCSHbFP6R0KyjB68Q\u002fSqGlkSNjxD9i3ePjc9zDP1VkmTy2V8M\u002fKQouce7Uwj+pYPcNIFTCP2N0hVVO1cE\u002fd5cDQHxYwT9Jqa16rN3AP6ZBWmfhZMA\u002fWj00ODrcvz9iV9rFwvK+P17dGnNfDb4\u002fckGgohIsvT9QWDoe3k68PyeD+hbDdbs\u002f31R9JcKguj9k62FK28+5P8og7+4NA7k\u002f0aHm5Vg6uD+M4YVsunW3PxHLtCswtbY\u002fafthObf4tT\u002fFNgwaTEC1P4W0eMLqi7Q\u002f1saVmY7bsz\u002f9SYl6Mi+zPx8x6rbQhrI\u002fFG4kGWPisT\u002feWgbn4kGxPxG0duRIpbA\u002fyRxSVo0MsD9XHN4KUO+uP8f8jYMgza0\u002f2eqGy3myrD\u002f0YVuzSJ+rP4zIfi55k6o\u002f7BmmWvaOqT+AhmSHqpGoP2myAD5\u002fm6c\u002fthh\u002fSV2spj+c992+LMSlP\u002fkMfwXV4qQ\u002fdk273zwIpD8Bp5xzSjSjP5HHuVPjZqI\u002ficwviOyfoT9krLWXSt+gP+IcxZDhJKA\u002f8lWpJSrhnj\u002fvXz2vkISdPxGQ4Xi8M5w\u002f0dT+lnLumj\u002fThHKKd7SZPxvVK1OPhZg\u002fA2C3gn1hlz+YGbBOBUiWP+QlDaPpOJU\u002fLSBENO0zlD8ehzeR0jiTP4Qp6TRcR5I\u002fUZ7ol0xfkT8vB3ZBZoCQPzouo7DXVI8\u002fAy1hZkC6jT95xaHRjDCMP4BjSL9Dt4o\u002f8GEZuuxNiT+o1EkoEPSHPzlkGmg3qYY\u002fTHhy6+xshT9CrHFSvD6EP8NQ7oQyHoM\u002fSIbYyt0Kgj8lUHrjTQSBPxrTjRsUCoA\u002fuJlHxIY3fj\u002fsWqq44HF8P+Yzd+9iwno\u002fLf5a9z0oeT9xdnYypqJ3P1aL1\u002fPTMHY\u002fgjFwmgPSdD8AAIupdYVzPwyLv95uSnI\u002fsR9qRTggcT\u002fDJaxHHwZwP8oM+Hnr9m0\u002fDwWa7CP\u002faz\u002fgybGPnSNqP0jXLSEXY2g\u002fBU7Zfli8Zj8vuU2sMi5lP9xzk9R\u002ft2M\u002ftJ2RRyNXYj+WunBzCQxhP\u002fo7KrRPql8\u002frDWwBfpiXT8mOlHRIEBbP06hesnkP1k\u002f4pMHLHhgVz9nivKKHqBVPwUKNY8s\u002fVM\u002fVDUytgd2Uj9u8AkKJglRP0xAZqobak8\u002fnfp3o6rwTD+lDh+pRKNKP0K7PepTf0g\u002fX1iZY2CCRj9h0+8iD6pEP4zPA4Uh9EI\u002f+VRUb3ReQT\u002fIo2IM\u002f80\u002fP6oBtL+oFz0\u002f1SmbajuWOj+RyOIiPUY4PwFjVP5iJDY\u002fGnMAbo8tND9Brb6Z0F4yPzvF+7xetTA\u002f8mq4CzVdLj\u002fUfFrvFpArP35YHaO8\u002fig\u002f4vFh9MakJj8RaejiGn4kPzGYSZHehiI\u002fmNYHRHa7ID\u002fPXefgAjEeP\u002ft5BbmvNRs\u002f2GwxoQ1\u002fGD+Pokm4nAcWP4QisVs9yhM\u002f9upAACvCET9gOK1p7tUPP4iIyJ4JgQw\u002fqt5iigZ+CT8v8Jdd1MUGP1CG7+\u002fpUQQ\u002fpxV\u002fdz0cAj8E0DyZPB8APzcpR6KJq\u002fw+NhP5Yjh2+T5klrvw1JX2PqZrW\u002fRkAvQ+qYV\u002fv5e08T5+J6vZdEvvPvC8IkFZn+s+ON6TyqtZ6D7nfulBN3DlPsvp\u002fDqv2eI+KZGTQJ6N4D7S074uqgjdPhhC3hW2bdk+AnOHdL491j6cojOzVW3TPtijub0o8tA++GzDOM6FzT4slBBwWa7JPk8ApGjaTsY+7aBVlsFZwz6ZRC0q5cLAPqAi46jA\u002frw+sbOeIeoKuT76UIKs4Ji1PtVi6wohmbI+eg2Dxrf7rz6M9IWDlXWrPt0f24oRiqc+XutTcd0lpD4Ppkiw5DehPrsvODQiYp0+PJ1uiykImT4RjKkNckqVPi03Oh1kFJI+1g\u002fRVtunjj7ENXbZc\u002fKJPnL4H4vg7IU+EOF4o\u002fN+gj5EqMDQMyd\u002fPov\u002fIpb5MHo+rmZZCF37dT6SMRKEvmpyPoGEMTlqzm4+cUQqgjC4aT7ibneOLW9lPkdZ7Am01GE+IIwV\u002fM6cXT6D0os1V4tYPjclFb1JTlQ+Xw+OgJHEUD585Min7qNLPoE2AOvsvEY+bCSNt02rQj5Oj5os65g+Ppi1J9E1Bjk+D3kZVAJtND45e9IynaMwPl3liy\u002fkDSs+TVN3JhzzJT5uz96KisUhPjCBc7fdtxw+dbvlZb0nFz7TT1KJZqESPrXP0fx86g0+At62MZ73Bz5BJJ2K6CgDPhA1mHb1kP49GRu\u002fWetT+D2KOwKDx1HzPeq5B6GDne49vAeCl+cz6D32gacb6hbjPWWhtbAcC949W\u002fH8mhOW1z3T6DZIKHnSPXGpc\u002fZZ3sw9TiXJoKuAxj2JBFS3e3\u002fBPQNo6nJxJbs9vUucSl0BtT1dNoaUjjawPYq2nNIs96g9Ju38mDMsoz230ImQXV+dPWgMhT71cJY9LdWlvNQZkT2421Onhv6JPZv383EutIM9BcNh75DKfT0pg2Et6nV2PWlRC0s343A9NQOBCiFTaT2qol9tkO9iPWrekXznPFw9ZEOk78X+VD0gyY10kyFPPUH4NUeBA0c9Yxd6WJ72QD3kDGPSEu84PfjHmZk7RTI9P4peEAmyKj1tg7g1oXEjPdHfcKjpPBw9w2yG7BBxFD3PnV1DAoENPX7JEiOnOQU95jhRikNx\u002fjytp04P9cL1PAwIf9wVA+88BnSaM5AG5jxrjB3yFS\u002ffPA8IHpGKANY8\u002f3CyDKjxzjwfiN7vx6\u002fFPEjI3KxSS748YEHc8CYWtTx5gd351UCtPMB1kit4OKQ80ABKwPramzzpmrAIQh6TPIX3qJYaJoo8hW1g5FTRgTwkpWqNaTF4PMuKcq83XXA8nMPV\u002fA8OZjyvFAdj8pxdPKejjbsnzlM8w3V3ZuljSjyXoueqsoNBPCtkC7kzKDc86oaTcUF\u002fLjzaCuGojQAkPGllAGnyIRo87qM1l4IAETzcHPnfbQgGPFrHSJZdb\u002fw71vsLf8FF8jsJ7qnWs2LnO4r3ajuFzd07Yg\u002fbUNPo0jtMbsNPa+THOze8UWGODr47OtD7xLbSsjttb7xAuninO3PjI7ApI507Tj+vbf4AkjuatQZy3SWGO9N60gbqHns7rM9dIzeHcDsr36+HJw1kO5\u002fBgEr9NVg7kAY9bBsYTTuKxdV\u002fsmVBO0Dy3659tDQ7Xlqs3HGFKDvYOjDvyOUcOxcsp6RV8RA7YlkePWzEAzvFgYXVIPL2OtRUf1vLf+o6EMEM0eNx3jrYZLs+32XROjiSXKaix8M6\u002fdVERFhetjrE5eTAICmpOg4ycw8BJpw6sbm6RcZRjzq7Ss2b91OBOmrdt8VOEXM6dHVk9EfdZDqzO0Zbk7NWOitF90s6j0g6bldCvK1qOjpA86tA2z8sOh9CZ2NICB46qYWQ8zO9Dzotr3tM3qsAOqAJ+dSFaPE5t\u002f2ne0ER4jmPzLEB9qLSOVMrenTTGsM5xhTvjGt2sznhkEILxrOjOaejlChy0ZM5XlCeTZTOgzk12xlf76pzOWNiIx3pZmM5vkILSIkDUznLGUx4c4JCOXGFQNXb5TE50wQ5E3cwITm3uQxaZmUQObHBf8s\u002fEP841O3SsKo47TgY5ayZsUvbOCfRN4YDUck4F8bT9SVQtzgrWSJ3RFClOP+Gq9EFWJM4uZliMGhtgThuTVOcSitvOJ8Ks20+qls4q5e8fqteSDitwefZyE01OI3lF0kQeyI4QI01s67QDzi+LI5J5yv7N\u002fIIUWXPBec31MYyXCta0zelAg5a6iLAN9dHXf0lsao3scEBEFnllTevz72YQdCBN+Y5vplQvmw3TiVEaMX+VjdCXQlkoT1CN74nrs78sCw3Ytc+FsNeFjcmAOiyN0oBN1UuWGf\u002ffeo2tkuNQskd1Da5UWY+6Ea+NhLo0eVTlKY2YTtBpoevkDYAcGaDLW94NuWCWTeWuWE2Jjqp6Lp5STYjDVaOjCIyNpd7X+bLkhk2LU8Fx7\u002fbATb8ovdCPLPoNeX2\u002f\u002fYi6tA1SqmF7Q\u002fwtjXx\u002fC+T1sueNaGv+tRNd4Q1XIN73yrtajVvAjm9NohRNfDc46FjmDY1ujpXnljRHDVBf\u002frkHS8CNcDiHl+PtOY0wCKNGz4MzDQCOwtKDyOxNGqzu+GXtpQ0\u002f7QfP9rCeDRXBmHot0VdNPu0Ah4CHEE035ihIm\u002fGIzSNVcMRpJgGNBIBzjQIhukzZyYoNA9\u002fzDPeQ1c8lnGvM4K2qNC8JJEzs\u002fdjzq94cjOQj1i916lTM0gK0LHTrTQztUJZSj97FTMIidppPwr2Mrs4JWb\u002fVNYyHErhCBRYtjItTxlpuxKWMij1fFHzhnUymYuIW2O5VDLZmBDGG7EzMlaGdegtdxIyz2EeiiYW8TGZmBjn6TLPMeIt3vOVGawxN5Cx1gT3iDGllS1MJuBlMVP\u002fQu5F50IxuuyF0GobIDFIiOlD+Q\u002f7MHsAJ7RaadYwkG9HPkFLsjAf4uNVFm+NMLrVi+9TVWcwyd6J2JE5QjAOzp1ABAwcMKrvJ74FQvUvWo85KsK8zy+ttwsegVSnLxnYDknV4oAvKSyKLYAQWC+y3xNd8OAwLzmWga6WTQcvtTx3EO+o3y7QXXePMCm1LinwCKr\u002f04su3s+hi5r\u002fYS7CNtc+s+U2LmtjrJfgpAwuTCuF84md4S066VwRcEy1LQFIpRauT4kt4jTGnoSPXS1\u002fa482B\u002fYwLUZDY8YcHwMt9rCXi9As1Syf8ofumwenLDMMUItKmHgs0WCwpMvJSSxFKr0M\u002fooaLP6vdEJB0OorhgF4jZmUuiuzcjvOP9qJK+UpyUyHqlgrPuaFdRkVJysA6p+tnS71KjofEID2DsMqvlOXJkrPkCpPaaa4IRBdKtJ3FxS+nigqg55Uzypv9Cm0JVFFIJ3AKRmbUsZsdYop2\u002feI7IGhVClMRr\u002f8t38fKdSNY8uSieco9icpOIA2sSi8+9IWNKJ4KGSM8iJyPkEo7gRE4PScByj3+4hcvZ7PJ4mcAAPvspQnrXcNXXJ9Wie8lJys4ZAgJ3WjzkHBPuQmU\u002fNA3oIrqCb23+7j6i5sJsrdepKSCzAmaJ6qGxnW8SW31rvwXVqzJTp8sXvmfXQlvf09tlUrNSXD9cgN+lT1JNHeBxmR9rQkey3YSvEVdCRGfzTJdsIyJLpXZllGE\u002fEjcfqLqFhJriO4Lo4wBSpqI2PH\u002fT0vAyYjOpGspQ8I4iLCyoDzccGcIjg1HxVlUFYiOSEzaNLYECISlVTzj77IIaTOdN3uq4Ehf9i4yzOKOCEIDHb4sY\u002fwIO3EX2zCuKUg5QDOcHqtWyD2LdkTRiARIDlg9SFAlMQfesR1CyIBeB+yd93XRywrH0mDuU7m190etXLgmIbKjx5yuG5RbGtAHtmDgV01cfAdS3h+Xunpnx0bTa6i9wFOHW4dnZNgVPscevGUnScaqBxAT4VxpJNUHGw+BUwxAAEcmnwWwdctqxu9LFE2UwNVGxaRnwajav8aBDd9r5uzphrBgUCvJbRPGhw2037HYvUZKoWFogHdmxnapDchJIZBGXubpiAsRuUYJMYvAR7riBju4dh7yiYsGGN8V\u002fV3qs4Xn9lUSXQZcBdz9fod40kQFzydWv7Ov68WB5RADbHLTRb1wjWmwurqFWHMoyBLZocV6eas82qRIxVzUr0o1ni\u002fFO9qS4dJVFgUBT05\u002f8ES8hPsQjPLW8uJE+Ff1T79rCETSRZfSAdBtxKlsy6HJ1tNEuTDiQMsxuERxTEjC1GjdBFLzkgOEPgGEVgGDKhSfpgQelnrLzQEKRDkvELBZna4Dw5w7yWx5EYPGumTvBl\u002f1A5T1PWFbYxhDt8Gd9ZzuewNfHYXAqJ2dg024OpKcscADfQNcyei7ocMQmdIPNJJEAxB5HwcsyaVC5UyoPa3MBoL\u002ftz8lTPpngp\u002f1tNlHmEhCj5fxDeEnKIJSuS03P\u002f4IglyYyOPMmeiCIe4rsJE+iAIz3NEdJnHnQeerF4eZ9EYBw=="},"type":"scatter","xaxis":"x","yaxis":"y2"}],                        {"template":{"layout":{"font":{"color":"#333333","family":"Alegreya, serif","size":14},"legend":{"font":{"size":12}},"title":{"font":{"size":20}},"xaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}},"yaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}}}},"xaxis":{"anchor":"y","domain":[0.0,0.94],"title":{"text":"Time t"}},"yaxis":{"anchor":"x","domain":[0.0,1.0],"title":{"text":"Event density"}},"yaxis2":{"anchor":"x","overlaying":"y","side":"right","title":{"text":"S(t) — limbo"},"range":[0,1.05]},"annotations":[{"font":{"color":"#c0392b","size":14},"showarrow":false,"text":"P(doom) = 0.64","x":8.717146433041302,"y":0.10760324282404954},{"font":{"color":"#2471a3","size":14},"showarrow":false,"text":"P(deliverance) = 0.36","x":6.921151439299123,"y":0.05382097374770676},{"font":{"color":"#27ae60","size":14},"showarrow":false,"text":"P(limbo) = 0.00","x":29.75,"y":0.05,"yref":"y2"}],"legend":{"bgcolor":"rgba(255,255,255,0.5)","bordercolor":"#ccc","borderwidth":1},"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified","height":440},                        {"responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('74e314b4-6b8d-43c4-8305-b1b19087c814');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-race-dynamics-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Resolution of the race with superlinear doom risk (g(𝒞) = 0.005𝒞²) and linear safety progress (h(𝒞) = 0.015𝒞). Compute throughput c(t) = e^{0.1t}, α = 0.5. The green curve (right axis) is S(t), the probability of limbo — neither doom nor deliverance has yet occurred. The shaded areas (left axis) show doom density λ_d(t)S(t) (red) and deliverance density λ_s(t)S(t) (blue). Their integrals give P(doom) and P(deliverance); the residual 1 − P(doom) − P(deliverance) is P(limbo) over this time window.
</figcaption>
</figure>
</div>
</div>
</section>
<section id="the-constant-hazard-ratio-case" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="the-constant-hazard-ratio-case"><span class="header-section-number">4</span> The constant-hazard-ratio case</h2>
<p>Consider an ultra-simple sanity check. If the ratio <img src="https://latex.codecogs.com/png.latex?%5Cpi_d(t)%20%5Cequiv%20%5Cpi_d"> is constant over time, then <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)%20=%20%5Cpi_d"> regardless of the compute trajectory — a standard competing-risks identity. That happens when <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> are linear and <img src="https://latex.codecogs.com/png.latex?%5Calpha"> is constant: if <img src="https://latex.codecogs.com/png.latex?g(x)%20=%20ax"> and <img src="https://latex.codecogs.com/png.latex?h(x)%20=%20bx">, then <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_c(t)%20=%20(1-%5Calpha)%5Cmathcal%7BC%7D(t)"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_s(t)%20=%20%5Calpha%5Cmathcal%7BC%7D(t)">, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi_d%20=%20%5Cfrac%7Ba(1-%5Calpha)%5C,%5Cmathcal%7BC%7D(t)%7D%7Ba(1-%5Calpha)%5C,%5Cmathcal%7BC%7D(t)%20+%20b%5Calpha%5C,%5Cmathcal%7BC%7D(t)%7D%20=%20%5Cfrac%7Ba(1-%5Calpha)%7D%7Ba(1-%5Calpha)%20+%20b%5Calpha%7D,%0A"></p>
<p>and the <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(t)"> cancels. The probability of a doom outcome <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)"> depends only on <img src="https://latex.codecogs.com/png.latex?%5Calpha"> and the ratio <img src="https://latex.codecogs.com/png.latex?a/b">, not on the throughput <img src="https://latex.codecogs.com/png.latex?c(t)"> or how fast it grows. This is the regime in which the speed of progress doesn’t matter — only the split between capability and safety compute determines our fate. If you’ve ever heard someone say “it doesn’t matter how fast AI progresses, only whether we invest enough in safety” — this is the (very specific) model in which that’s true. It is the implicit model whenever we reason about a safety budget as a fraction without asking where on the curve we are.</p>
<p>It’s also not very plausible, in my opinion. Let’s get more realistically complicated.</p>
</section>
<section id="sweeping-the-split" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="sweeping-the-split"><span class="header-section-number">5</span> Sweeping the split</h2>
<p>With non-linear response functions this invariance breaks down: <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)"> depends on the full trajectory <img src="https://latex.codecogs.com/png.latex?c(t)">, because the time spent at each level of spend determines how much hazard accumulates there. The plot below sweeps <img src="https://latex.codecogs.com/png.latex?%5Calpha"> under exponential growth <img src="https://latex.codecogs.com/png.latex?c(t)%20=%20e%5E%7Brt%7D"> at three growth rates.</p>
<div id="cell-fig-pdoom-vs-alpha" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">alphas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.99</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>)</span>
<span id="cb4-2">t_sim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3000</span>)</span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_pdoom(g_fn, h_fn, alpha_val, r, Cc0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, Cs0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, leaky<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb4-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""P(doom) by integration over a trajectory, from legacy spends Cc0, Cs0.</span></span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    leaky=True feeds g the total spend instead of the capability spend."""</span></span>
<span id="cb4-8">    c_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.exp(r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t_sim)</span>
<span id="cb4-9">    Cc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Cc0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cumulative_trapezoid((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha_val) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c_rate, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-10">    Cs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Cs0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cumulative_trapezoid(alpha_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c_rate, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-11">    ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_fn(Cc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> Cs <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> leaky <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> Cc)</span>
<span id="cb4-12">    ls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h_fn(Cs)</span>
<span id="cb4-13">    cumhaz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid(ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> ls, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-14">    S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>cumhaz)</span>
<span id="cb4-15">    doom_density <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S</span>
<span id="cb4-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.trapezoid(doom_density, t_sim)</span>
<span id="cb4-17"></span>
<span id="cb4-18">curves <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb4-19">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Linear"</span>,      g_lin,  h_lin,  C_NEUT),</span>
<span id="cb4-20">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Convex-doom"</span>,  g_pess, h_pess, C_DOOM),</span>
<span id="cb4-21">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Concave-doom"</span>, g_opt,  h_opt,  C_SALV),</span>
<span id="cb4-22">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Overhang"</span>,     g_sig,  h_sig,  C_RUIN),</span>
<span id="cb4-23">]</span>
<span id="cb4-24"></span>
<span id="cb4-25">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> go.Figure()</span>
<span id="cb4-26"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, gf, hf, col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> curves:</span>
<span id="cb4-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r, dash, suffix <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"solid"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fast r=0.3"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dash"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slow r=0.05"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dashdot"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"buildout stop r=0"</span>)]:</span>
<span id="cb4-28">        pr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([compute_pdoom(gf, hf, a, r) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> alphas])</span>
<span id="cb4-29">        fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>alphas, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pr, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>suffix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>,</span>
<span id="cb4-30">                                 line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dash),</span>
<span id="cb4-31">                                 hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"α=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.2f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  P(doom)=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.3f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>))</span>
<span id="cb4-32"></span>
<span id="cb4-33">fig.add_hline(y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, line_dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dot"</span>, line_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#bbb"</span>, annotation_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"P(doom) = ½"</span>,</span>
<span id="cb4-34">              annotation_position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom right"</span>)</span>
<span id="cb4-35"></span>
<span id="cb4-36">fig.update_layout(</span>
<span id="cb4-37">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>LAYOUT,</span>
<span id="cb4-38">    xaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Safety fraction α"</span>,</span>
<span id="cb4-39">    yaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"P(doom)"</span>,</span>
<span id="cb4-40">    height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">480</span>,</span>
<span id="cb4-41">)</span>
<span id="cb4-42">fig.show()</span></code></pre></div></div>
</details>
<div id="fig-pdoom-vs-alpha" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pdoom-vs-alpha-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div style="height:480px; width:100%;">            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-svg.min.js"></script><script>if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script>window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-4.0.0.min.js" integrity="sha256-FEYfO0yRyLtZCpnW0Dw/0DHKQO7Afrq3ml4+rBB818o=" crossorigin="anonymous"></script>                <div id="06867bb3-1992-4d55-862f-629769a88cf9" class="plotly-graph-div" style="height:100%; width:100%;"></div>            <script>                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("06867bb3-1992-4d55-862f-629769a88cf9")) {                    Plotly.newPlot(                        "06867bb3-1992-4d55-862f-629769a88cf9",                        [{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","dash":"solid","width":2.5},"name":"Linear (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"l0TCKYHM7z\u002fYIC9ZNozvP0W3xGBQS+8\u002fYLgzDM0J7z+rPW8cqsfuPyIPa0flhO4\u002fSgPYN3xB7j8yat6MbP3tP8lx1tmzuO0\u002fSXL+pU9z7T\u002fwDy9sPS3tP1EfjZp65uw\u002f+jc5kgSf7D824fym2FbsP\u002f9C9R70Dew\u002fHkU7MlTE6z83BYkK9nnrPzWL3MLWLus\u002fdqMXZ\u002fPi6j8TxJzzSJbqPyvi6FTUSOo\u002fwhoqZ5L66T+tEdP1f6vpP3znKruZW+k\u002fk6bZX9wK6T+pBXF6RLnoP+dc8Y7OZug\u002fX6lKDncT6D+DeNlVOr\u002fnP3eU364Uauc\u002fgEf4TQIU5z+3C4hS\u002f7zmP9N5J8YHZeY\u002fAEgJnBcM5j\u002fYJ1uwKrLlP+hPocc8V+U\u002fV3sMjkn75D+2J8qWTJ7kPyzWTltBQOQ\u002f9BKaOiPh4z8PA3R47YDjPx81pDybH+M\u002fUm8hkie94j\u002fpMDpmjVniP4iatYfH9OE\u002f2GzspdCO4T8OydlPoyfhPyFbI\u002fM5v+A\u002f4pAY245V4D8E+lBfONXfP18AnOi3\u002fN4\u002fu5rjDZAh3j9isoM9tEPdP8t9+5UXY9w\u002fRz5v46x\u002f2z8A7hGdZpnaP8\u002fNdeI2sNk\u002fH7TCeA\u002fE2D898NDH4dTXP5WEJ9ee4tY\u002fpWvcSjft1T9CiFVgm\u002fTUP8jQ6Oq6+NM\u002fmTBaUIX50j9qhjWF6fbRP6wQAwnW8NA\u002f3QKpxHHOzz8BtVE1\u002f7PNP3MxSnQukss\u002f9TE\u002fhthoyT+JCPha1TfHPznhqsP7\u002fsQ\u002fEoboaCG+wj\u002f2dhrAGnXAP5C1GwF2R7w\u002f6f4KMKiTtz8FkJk9bc6yP6IxFC3H7qs\u002fkzaak00coj\u002fJv221PUmQPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","dash":"dash","width":2.5},"name":"Linear (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"yViJhJbM7z+TUhGFS4zvP1x0Z11lS+8\u002feDM72eEJ7z81aH65vsfuP1uTI7T5hO4\u002fGj7ac5BB7j\u002fAZMmXgP3tPxDbR7PHuO0\u002fcJiSTWNz7T+C2YDhUC3tP0AENt2N5uw\u002fjDrRoRef7D+dhhqD61bsP+mLLccGDuw\u002fDaYhpmbE6z+cXq9JCHrrP2Ah08zoLus\u002fUBZtOwXj6j+kBt6RWpbqP+IxobzlSOo\u002fk\u002fbil6P66T\u002fdMBTvkKvpP1UwenyqW+k\u002foCS76OwK6T\u002fy32bKVLnoP7vLe6XeZug\u002f6Ovn6oYT6D8kygX4Sb\u002fnP8AhFRYkauc\u002f7CKveREU5z8YIjZCDr3mP3GGQHkWZeY\u002fasj+ESYM5j82T5zoOLLlPyn6msFKV+U\u002f7R8pSVf75D\u002foy3ESWp7kPxf\u002f5pZOQOQ\u002fHLeFNTDh4z+TexMy+oDjP2wuVbSnH+M\u002fd9k+xzO94j9pLxxYmVniP8hzsTXT9OE\u002f73hUD9yO4T9CYP1zrifhP6jDTtFEv+A\u002fOeyUcplV4D+EaXf\u002fTNXfP1lkdfjL\u002fN4\u002fYG7ui6Mh3j9lhjcox0PdP\u002f\u002fMyOspY9w\u002fvkC\u002for5\u002f2z8Fa0bEd5naPzrs6G9HsNk\u002fN8nGah\u002fE2D8bTbAc8dTXP85BJI2t4tY\u002fTDMwYEXt1T80XjHTqPTUPx3ZdLnH+NM\u002fNHS1eJH50j+FtXUF9fbRP3hDNN\u002fg8NA\u002fsezxGIbOzz8mZXEtErTNP7UiHAxAkss\u002f1oaJuehoyT909Wol5DfHPwwR4CAJ\u002f8Q\u002fhnliVC2+wj9N3kM1JXXAPynTcfWHR7w\u002fTHTWJLeTtz9klhgpec6yP\u002fI0i93Y7qs\u002fZU0uCVkcoj+mcDYBSEmQPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","dash":"dashdot","width":2.5},"name":"Linear (buildout stop r=0)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"PYCWYpjM7z9nL3JeTYzvP+IUFjJnS+8\u002fjpQxqeMJ7z80dbaEwMfuP6Qll3r7hO4\u002fxByDNZJB7j9lRKFUgv3tP2xdSGvJuO0\u002fBEy1AGVz7T9QOb+PUi3tP293iYaP5uw\u002fAxQzRhmf7D+tBIQi7VbsP\u002f\u002fXl2EIDuw\u002fR9SFO2jE6z9YbQbaCXrrP+r4FVjqLus\u002fgYiUwQbj6j9VzuISXJbqP7\u002fyezjnSOo\u002fcDyMDqX66T90b4RgkqvpP47CqeirW+k\u002fgUyiT+4K6T++xf0rVrnoP8p9ugHgZug\u002f6F3GQYgT6D+\u002f1HtJS7\u002fnPwyCGmIlauc\u002fzHk7wBIU5z\u002f580CDD73mPzM6wbQXZeY\u002fSqfsRycM5j8EhO4YOrLlP7WQSOxLV+U\u002fSwQpblj75D8CyroxW57kP87Bb7BPQOQ\u002f0sdESTHh4z9mQf8\u002f+4DjP0DtY7yoH+M\u002ffLFmyTS94j9yHFNUmlniP0JM7SvU9OE\u002fX+2K\u002f9yO4T8n+iNeryfhP+HkWrVFv+A\u002fGs57UJpV4D9EzuSuTtXfP9U0bJvN\u002fN4\u002fCh9YIqUh3j8DM\u002f2xyEPdP9I202grY9w\u002fxsz2EsB\u002f2z8oH5MneZnaPwJuMsZIsNk\u002fxlr0syDE2D+ay6hY8tTXP3Mizruu4tY\u002fbIFxgUbt1T8xuO\u002fmqfTUP5Fulb\u002fI+NM\u002fmQIdcZL50j\u002fchQjw9fbRPzEn1rvh8NA\u002fX4katofOzz9VikSuE7TNP0SDXnBBkss\u002f0M3+AOpoyT8VwNVP5TfHP47nAS4K\u002f8Q\u002fTsj7Qy6+wj9v7hMHJnXAP52y+1yJR7w\u002fQOrDT7iTtz+FBOEWes6yPy25uz3a7qs\u002fPwLj7Fkcoj9lp2bNSEmQPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","dash":"solid","width":2.5},"name":"Convex-doom (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"Vj\u002fWItbE7z8qJFYSAafvP+lL8ig+kO8\u002fmEPZy8F87z\u002fdKhGpNmvvPy9CxVT1Wu8\u002fuuPa25xL7z9XMcQC7zzvP037OuDALu8\u002fhxBUPfMg7z\u002fBoNpqbhPvPzMP8s0fBu8\u002fZidGWfj47j\u002fizQmO6+vuPzInC8\u002fu3u4\u002fLeaI5\u002fjR7j+GzNSzAcXuP\u002fu1s+ABuO4\u002fVnBtuvKq7j+ytOkGzp3uPwpUueeNkO4\u002fuM7hwSyD7j\u002fLz+MppXXuPyXS4dLxZ+4\u002fWYAWgA1a7j8x4v338kvuPyCMuPicPe4\u002fJI5JLQYv7j+r9WEjKSDuPxRweUEAEe4\u002ftWD8vIUB7j84g12Qs\u002fHtPxib3HCD4e0\u002ffCjVw+7Q7T\u002fk4meT7r\u002ftP3z4T4J7ru0\u002f0M6xvo2c7T9CNazzHIrtPxCCbjkgd+0\u002f+LuNA45j7T9SbkgNXE\u002ftPyKtW0N\u002fOu0\u002fUJb7qusk7T9pkW1FlA7tP8vFqe9q9+w\u002fhoVKPWDf7D\u002f8NepNY8bsP3KF4JthrOw\u002fONETw0aR7D+Y+Ec+\u002fHTsP7+48xhpV+w\u002fA7gqk3E47D\u002ft9Yu09hfsP0mSVsrV9es\u002fCGS5y+fR6z+XwQ2eAKzrP\u002f670jDug+s\u002f04G9ZndZ6z\u002fZWMq9WizrP7achKNM\u002fOo\u002fx5cnXPXI6j8XS+VZ7pHqP44sMNS+Vuo\u002fmSw2W9cW6j9tO1wGjNHpP6SJy68Mhuk\u002fN7dlaVoz6T8H0vPnONjoPxdC4ekZc+g\u002fcq3DZv8B6D8sAv4tT4LnP5tx5qKO8OY\u002fsVWckfVH5j\u002fBPBM3t4HlP8Cx7Va9k+Q\u002fbJe9bDtu4z\u002frky3slPbhP8Yc2es79t8\u002f4vzsQ54e2j+sUcZ4V+rPPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","dash":"dash","width":2.5},"name":"Convex-doom (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"4h2pPt2x7z\u002ft\u002fPl0EIrvP4D9JimWa+8\u002f4PYGtWRR7z8mJGVvtznvPw9BjliwI+8\u002fT+Lnzs4O7z+z\u002fS8hwPruP2MmrCVL5+4\u002fKUj4H0bU7j9uKmM8kcHuP8xTRlETr+4\u002fb5WV2bec7j9geyGjbYruP7ZjYekleO4\u002f4PqatNNl7j8JVaJla1PuPxhIjl\u002fiQO4\u002fDKoKxi4u7j8xJSVKRxvuP31bbwEjCO4\u002f1JmXRLn07T8We2+TAeHtP1I253zzzO0\u002fFfrliYa47T9UKisqsqPtP5Jhk6Jtju0\u002fIAk9\u002fK947T+2Wh\u002f0b2LtP\u002fdntuqjS+0\u002fyS9x00E07T+8EZUjPxztP+rmTMCQA+0\u002fOjCa6yrq7D8BdtwvAdDsP3pCnkkGtew\u002fMBVQDyyZ7D+HI5BWY3zsP4x5kdabXuw\u002fpLIlB8Q\u002f7D9Pj9n7yB\u002fsP4lzfTqW\u002fus\u002fcH9WjBXc6z+FWxLJLrjrP5CQbZrHkus\u002fiYtIN8Nr6z+T7acUAkPrP2SGzophGOs\u002f8H0+bLvr6j\u002fExfaL5bzqP+tsqy+xi+o\u002fClr6aepX6j9s\u002fa5XVyHqP\u002fm+9zm35+k\u002fTDDrZcGq6T\u002f6P8b\u002fI2rpPzHqwnWCJek\u002fg4YVq3Pc6D\u002fMxz+\u002ff47oPxD3GVgdO+g\u002fNRkrTa7h5z+9umOIe4HnPyRD9OGvGec\u002fB\u002fMwqVGp5j9Vm\u002fNwOi\u002fmP+tDS48MquU\u002fYqmUiSUY5T\u002fI+glYjHfkPybMC\u002fzZxeM\u002fzvKdRBoA4z8ijru7oSLiPx1b+IjUKOE\u002fJ3KBstgM4D\u002f6Jtx4Wo7dP1Pf7zdEnNo\u002fj3\u002fu5Hsq1z\u002ful\u002fNT1BnTP\u002fPbpyUMkMw\u002fWoEbVphLwT\u002fq5eiVKpulPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","dash":"dashdot","width":2.5},"name":"Convex-doom (buildout stop r=0)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"xNvKXcyr7z8+GGaj14DvPz22uBToX+8\u002fXfUDKJBD7z+GRENB6CnvPy8+6DkBEu8\u002fC3chc1D77j84xMG+fOXuPws8OWRI0O4\u002fzeDBP4a77j9d7mXRE6fuP+16qL7Vku4\u002f965apbV+7j+PxdWvoGruP8qf+J2GVu4\u002fZL6qF1lC7j9l7U0vCy7uP3M3WwSRGe4\u002frWodfN8E7j\u002fR5OwJ7O\u002ftP5wSdYKs2u0\u002f3zPr9hbF7T9NGQSWIa\u002ftP8l+EJHCmO0\u002fTVsQBPCB7T82qNXfn2rtPylJgdXHUu0\u002fqKjHQl067T94woIeVSHtP8yfJ+WjB+0\u002fhbe\u002fhD3t7D9MfwxIFdLsPy+hfcAdtuw\u002fQkShrkiZ7D+j8rHohnvsPzQE3z7IXOw\u002f3gfjXPs87D\u002fpQm+oDRzsP9e24xvr+es\u002f8cC3HH7W6z8Bud9Mr7HrP1MsX1Zli+s\u002fJHUUsIRj6z\u002fNMaBa7znrP3\u002f2F5SEDus\u002fEnr1gCDh6j8l9GfIm7HqP9pA0SHLf+o\u002fMajH0H5L6j8N0WoMghTqP68VME6a2uk\u002f4\u002f53g4ad6T+r1Dkc\u002flzpP2jeze+vGOk\u002f8XZK70DQ6D\u002freu6aSoPoPyBdmi1ZMeg\u002fe2s\u002fbunZ5z89fkoTZnznP1kvNZ8kGOc\u002fHdhil2Gs5j8yKgvvOzjmP39aFXevuuU\u002ftsWJGY4y5T\u002fwA1Kcd57kP2spUZ3P\u002fOM\u002fR9wcdrFL4z8OnVPC4YjiP46Tvne9seE\u002fVgIXDSfD4D\u002fdKnAA53LfPwcwwiy4IN0\u002fI\u002ff2EgGG2j+ddAWHKZjXP\u002f+EU7nwTNQ\u002f0OB5u4yd0D8OXvhqghzJPyYNMbWNf8A\u002finTP76NQsD\u002fchS3s\u002f4SNPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#2471a3","dash":"solid","width":2.5},"name":"Concave-doom (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"QJwhRFQF6D\u002fyP972j\u002fDiP9ShDm+6BOA\u002fYWsnTH0S3D9r7GC0KCPZP4UZSZEe2NY\u002fpz3aXyX81D+b7yrYo2\u002fTPypgSlltHtI\u002fN\u002f0JQuj60D81Gj4vCPfPP0g8uyWXMs4\u002f0hxhkBSezD8Pl6o9iDHLP+Av4hq55sk\u002fKUlU8bW4yD+R9JYAg6PHP\u002fAA0qrfo8Y\u002fOPE7SBy3xT9HTxEV+9rEP+2QVOuYDcQ\u002fZBOXkFtNwz8pCAUT5JjCP3x9ayYE78E\u002foh+rwLVOwT+h88pqFLfAP5eVMeFXJ8A\u002f+z+8b589vz9ODpyUvzm+P7XPxJj6Qb0\u002fRB4zt1hVvD\u002fGG7zu+3K7P00sepkcmro\u002fxJYXjAbKuT8Lh3SkFgK5P219GbS4Qbg\u002fpO6+tmWItz\u002fF2zNIotW2P8NrSk\u002f9KLY\u002fWO5Q1Q6CtT+2ghsDd+C0P1ZK1D3dQ7Q\u002fMdu9Xu+rsz9C9dkBYRizP5TeBujqiLI\u002fPoqnaUr9sT\u002fkdVP3QHWxP+hHX6aT8LA\u002f4\u002fFRxwpvsD\u002fXqhsJ4+CvPyeeNw8r6a4\u002fLjJAR432rT9qDx4HrQitP923crUwH6w\u002fNmF4LME5qz+jhIobCViqP69N3GO0eak\u002fLrJVbG+eqD9VXsRn5sWnPys9bIfE76Y\u002fxshqErMbpj96MB5YWEmlP6U\u002fiXBWeKQ\u002fxigIuUmooz8Tdt30xtiiPy4v+u5YCaI\u002fQrHxan05oT9Dmu0aoWigP3YAiVM0LJ8\u002fpJhtUEGCnT\u002f4uR18h9GbP9z8snOzF5o\u002fdI43R89RmD8rKegM9HuWP12u+orEkJQ\u002fHpoFN3mIkj\u002fkb0uH81aQP3a9\u002fX2Lzos\u002f2ECEMIodhj+hmcvr8k99Pw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#2471a3","dash":"dash","width":2.5},"name":"Concave-doom (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"rC2Eskym7j+jvzH3GK3rPwC6Uqf72+g\u002fzkxiJxRz5j8kF7z+v2rkPxieEXXbseI\u002fSEyj9M844T+rFJM9SObfPxs3W9FPrt0\u002fI1qkrLS62z8dhKDbwv\u002fZP1EwU61CdNg\u002fxI6TNd0Q1z\u002fS\u002fE4Sqs\u002fVP1uTMEbbq9Q\u002fiEiawX6h0z9TRTaPT63SP5bhFVuSzNE\u002fDzPiSvr80D\u002fD\u002fjr9kzzQP+2180RqE88\u002ftNjzHN\u002fFzT\u002fkr4jgDY7MP85dCyfRacs\u002fWSt8bklXyj9HNqdD0lTJP3I+cF\u002f5YMg\u002fFLLHUnd6xz\u002f1SwVyKaDGP2jC4MEM0cU\u002fWHWGtTkMxT9REG2X4FDEPyc\u002fZH5GnsM\u002fu4ZntsLzwj+XhX+JvFDCP1Ktt1iptME\u002fX94i+AofwT\u002f1R0NDbo\u002fAP02fC+FpBcA\u002fDU9mYDoBvz+wFJ6rXgG+P1IEQ9KcCr0\u002fj228cF0cvD+QYphFFDa7P2hZcAo\u002fV7o\u002fAOg2bGR\u002fuT8gvnAeE664P0CfcgXh4rc\u002fh\u002fA4dGodtz9Ogc96UV22P7J4jEI9orU\u002fKLqZddnrtD9Omm2v1Tm0Pyho5vPki7M\u002fVgG7Kr3hsj9j9dqcFjuyP+p1JnGrl7E\u002fVPWSJTf3sD9OB1MBdlmwP0A+wPdIfK8\u002feVx6J\u002f1Jrj9FmKEcfhutP7LpMzA98Ks\u002f\u002fF7qcqXHqj9gM1ofGaGpP82awGbue6g\u002f4L0kVmtXpz9Az5pxwDKmP94nQnABDaU\u002fGz24Lhvloz9tLrNbxbmiPw2lHlxtiaE\u002fdQIF9BVSoD8xdem3RiKeP6e2PO0Fhps\u002far3mWhPFmD8wgf7FWs+VP\u002f1o9CociZI\u002f3xrgkIRzjT\u002fS\u002fC4bMICDPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#2471a3","dash":"dashdot","width":2.5},"name":"Concave-doom (buildout stop r=0)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"jlbCYDKC7z\u002f2HhZiz\u002fPtP6Leyl7n3+s\u002fyuKTGN246T8ovqMPObHnP2L62feG2eU\u002f0sbFbuoz5D8zGIaD\u002frziP8OCqs6ib+E\u002fcgJzOotG4D+gUAECr3neP8lCgc+BnNw\u002ftflo2Tju2j+bCWwNmWjZP+OFVFdcBtg\u002fQrqP7gvD1j81J3OG3prVP6kl6lqbitQ\u002feeno94GP0z9IrZ0xNqfSP+Nh8amvz9E\u002f22VrRywH0T+3NegUJUzQP7HP3S6KOs8\u002fJczWd8PyzT8wH6Ig6b7MP6KG4BQsncs\u002fyh2qVfKLyj\u002fR7HKnz4nJPy7ZtGd\u002flcg\u002foyRkVt+txz8dlhgp69HGP3ffu8W4AMY\u002ffKnfCHU5xT\u002fM\u002f+4AYXvEPy82govPxcM\u002fjBlmRSMYwz\u002fzkZDAzHHCP5srZvVI0sE\u002fINh25h85wT+5wkhv46XAP5E49jcuGMA\u002ffR+4kEUfvz9hOsVs1Re+P0BtKNdrGb0\u002fCnB\u002f\u002fXMjvD\u002fJQbE9YzW7PwgtShm4Tro\u002fkBovQvluuT+e7eC8tJW4PxO56xR\u002fwrc\u002fqQdpoPL0tj\u002fribHPriy2P\u002faeeYZXabU\u002fwFKce5SqtD+qO8ecEPCzPwTECnN5ObM\u002fnQT\u002fhH6Gsj8M9Kqy0NaxP8CNmoYhKrE\u002fsjZ5diKAsD8p3egXCLGvP\u002fkm09jpZa4\u002fvpc3ZUEerT83c78RXdmrP5D78tV+lqo\u002fgcjg19dUqT\u002fzr+uVghOoP0O84w170aY\u002fu04z5ZONpT9jzbT4ZkakPwxzTaA++qI\u002fUNk43PKmoT8uQoZvsUmgP16\u002fz6A9vZ0\u002fvl+sNVDAmj8SNaK\u002fRouXPx4aPGY8AJQ\u002fgqijNnvEjz9ucy8BMgeFPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#8e44ad","dash":"solid","width":2.5},"name":"Overhang (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"mi4eKzj+7z8AUiiecPnvP7nGoiJw8u8\u002fbAKxv4fp7z+bqGIs5t7vP57iwHWq0u8\u002fhMtwn+rE7z8rsZi7trXvP6AnpZcape8\u002fYPlIux6T7z9OJ\u002fIJyX\u002fvP6spVi0da+8\u002fedp73RxV7z9LQM4RyD3vP\u002f86GyIdJe8\u002fEJ\u002fE2xgL7z8cs96Ntu\u002fuP5mEAQ7w0u4\u002fUlHztr207j8gbuZhFpXuPwSmuFrvc+4\u002fQR9pTzxR7j8WCM067yzuP1\u002fgZUr4Bu4\u002fkfoav0Xf7T8+jHjIw7XtPxDG91lciu0\u002fpMOz+fZc7T9u+M2HeC3tPzi\u002fof3C++w\u002fmkHCI7XH7D\u002fAdJk9KpHsPwabXan5V+w\u002fFJf6cvYb7D+P8YfY7tzrP+1RBb+rmus\u002fWZJ5FvBU6z+cbGAteAvrPzwq0vT4veo\u002fBTtgOR9s6j+PTsjYjhXqP9ytEQPiuek\u002fRMhEn6hY6T9DWaX5Z\u002fHoPxq58POag+g\u002fotCmB7MO6D9ubieFGpLnP3Pke5M4Dec\u002fqZjNgXd\u002f5j+R+s7rTejlPxYquu5KR+U\u002fcvNaHiWc5D8YAbEJy+bjP5wN5QtzJ+M\u002fYHWuN6de4j8NY8PmSY3hP26UA1eQtOA\u002f\u002fz8VquWr3z8o\u002fEBPKebdP0+vblVQG9w\u002fROTzoKhO2j+oCTaiP4PYP4j5tojIu9Y\u002ftd8bXZL61D+uRP7+ikHTP7VcRsNKktE\u002fNSMnXknczz\u002fy\u002fP+rcKzMP3FiGjUHl8k\u002fSI\u002fWNuedxj9Yy9296cLDP39JCb0GCME\u002fOL6xRu7evD\u002f0++DUuPe3P0cjWWDmYLM\u002fUCjVtmpFrj\u002f4SDUvEpCmP9v1h7zOf58\u002fmuFOTpUDlD8hDnHu+oqGPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#8e44ad","dash":"dash","width":2.5},"name":"Overhang (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"tFQCsen\u002f7z\u002fdK9xR6f\u002fvP1wTxdzo\u002f+8\u002fGSoqR+j\u002f7z9dhr545\u002f\u002fvP2Hd1jnm\u002f+8\u002fPnisF+T\u002f7z93DPs\u002f4P\u002fvP8o4ClfZ\u002f+8\u002fhzGbS83\u002f7z\u002fRWrsruf\u002fvP2Z2lfyY\u002f+8\u002fwVhblmf\u002f7z\u002f6qnyEHv\u002fvPz3nqem1\u002fu8\u002fXaCgZiT+7z\u002fCfmgCX\u002f3vP4BkdBJZ\u002fO8\u002f83v9IAT77z82PdzPT\u002fnvP1cKFbYp9+8\u002f\u002f8k7Nn307z85JrZLM\u002fHvP\u002fDduUwy7e8\u002fg3qnnl3o7z\u002fePw1aleLvPw2ILdu12+8\u002fjW5RO5fT7z8jYnauDMrvPyOU+7\u002fjvu8\u002fN0LTZ+Ox7z\u002fTdFvwyqLvP8GCW6NQke8\u002foau1MiB97z8V7S7P2GXvP2dvX90KS+8\u002f43qsNjUs7z+YSZniwQjvP5HerDQC4O4\u002fBsUqPiqx7j\u002fI8G6LS3vuP76SzTZPPe4\u002fepY0fO\u002f17T8VvH4xsaPtP1YvfeHdRO0\u002fx6mizX\u002fX7D8unjrSYVnsP50J4gsWyOs\u002fdiOqBQch6z\u002fFBi7Ol2HqP\u002fDHZvtWh+k\u002fJcTDjEaQ6D\u002fwWo\u002f\u002fNXvnPxCTWsMkSOY\u002fKcVAFZv45D8P0PGe4Y\u002fjPwS\u002fqHEBE+I\u002f6fMKqoCI4D91dXkTxO\u002fdP+mgotL\u002f0do\u002fipNGUsnG1z9kjF4kgNvUP+iUegQZG9I\u002fNwoa2pwbzz8forknTHLKP5AsYKRdQMY\u002fmgH\u002fb7WHwj9OBJzbfo2+P9aostBG8rg\u002fyJphNsAwtD\u002fENuHTljiwPy00Eurw7ak\u002f7kHdrpaupD800cDpsoegP1mZVqkKmpo\u002fhVd3ShGmlT9su4oiceGRP0DHhkPhBY4\u002fWxqjRS6biT93+xn8TiWGPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#8e44ad","dash":"dashdot","width":2.5},"name":"Overhang (buildout stop r=0)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"reeRR+r\u002f7z8NpLXz6f\u002fvP1F1yJDp\u002f+8\u002fN+B7G+n\u002f7z89jo+P6P\u002fvPzzmeOfn\u002f+8\u002fy\u002fvhG+f\u002f7z\u002fOoegi5v\u002fvP4959u7k\u002f+8\u002foS7vbOP\u002f7z+qS0WB4f\u002fvPxowRwPf\u002f+8\u002f4cmutdv\u002f7z880EU71\u002f\u002fvP9eTXQbR\u002f+8\u002fwl\u002f5Qcj\u002f7z\u002fQqdWzu\u002f\u002fvP0ao1Zap\u002f+8\u002f3T+3bY\u002f\u002f7z9I5iHOaf\u002fvP7guKSQ0\u002f+8\u002fGJwqbuj+7z8pzIrwfv7vP0IqRuDt\u002fe8\u002fp+2SAin97z9sJvg+IfzvP4mUPiDE+u8\u002fAFJ1P\u002fv47z\u002fI6eGSq\u002fbvP9mFB5m08+8\u002fDBnkVe\u002fv7z9o4\u002fcVLevvP+DFgec15e8\u002f4kdttsbd7z9sBpzyjtTvP8tWZqItye8\u002f76RxvC277z9sv56cAarvP0iPqmD9lO8\u002f+t7n80977z9yNoiR+lvvP02VEZDGNe8\u002f2lqJaDkH7z\u002ffJZIvh87uPzrZ1zOEie4\u002fVgxYR5c17j9YWvN8r8\u002ftPzxY+OdBVO0\u002fLlNAEla\u002f7D9wJeDqqQzsP\u002fmzic72N+s\u002fcN1SNl896j+yQ4N+BRrpP2BPdfO+zOc\u002fTseCw8RW5j+grDQbNbzkPybNV4ozBOM\u002fA1esyYg44T+p\u002fbHIisneP0YyTkQlKts\u002fzl38H+yp1z+nOxfslFzUP4a4bNkwUdE\u002fc\u002fzH\u002fl4jzT9KPCCQIUbIP09Gx7MUDMQ\u002flurlx8lvwD+l\u002fjlLkM26P37oMWR3xrU\u002f5L0IM9KqsT+klDPUhrOsP\u002feieZvoZac\u002fgoadoCIwoz90zdkgcbefP\u002fjmOl1Vc5o\u002fk\u002fGVNutFlj86aq1TnvCSP+wOpXSXQpA\u002fipTrusUsjD\u002ft\u002fiQgBZ+IPw=="},"type":"scatter"}],                        {"template":{"layout":{"font":{"color":"#333333","family":"Alegreya, serif","size":14},"legend":{"font":{"size":12}},"title":{"font":{"size":20}},"xaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}},"yaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}}}},"shapes":[{"line":{"color":"#bbb","dash":"dot"},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":0.5,"y1":0.5,"yref":"y"}],"annotations":[{"showarrow":false,"text":"P(doom) = ½","x":1,"xanchor":"right","xref":"x domain","y":0.5,"yanchor":"top","yref":"y"}],"legend":{"bgcolor":"rgba(255,255,255,0.5)","bordercolor":"#ccc","borderwidth":1},"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified","xaxis":{"title":{"text":"Safety fraction α"}},"yaxis":{"title":{"text":"P(doom)"}},"height":480},                        {"responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('06867bb3-1992-4d55-862f-629769a88cf9');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pdoom-vs-alpha-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Probability of doom as a function of safety fraction α, computed by integration over an exponential compute trajectory. Solid lines: fast growth (r = 0.3); dashed: slow growth (r = 0.05); dash-dot: no growth (r = 0), throughput held at its starting value from the outset. For the linear case (grey), the curves coincide — growth rate doesn’t matter. For non-linear responses, faster growth shifts the doom probability because it changes how much time the system spends in different hazard regimes.
</figcaption>
</figure>
</div>
</div>
<div id="77714781" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Quantities quoted in the prose below, so they cannot drift from the plots.</span></span>
<span id="cb5-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> resolution_stats(g_fn, h_fn, alpha_val, r, scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>):</span>
<span id="cb5-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Median resolution spend 𝒞(T*) and time T* for one trajectory."""</span></span>
<span id="cb5-4">    c_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.exp(r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t_sim)</span>
<span id="cb5-5">    C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid(c_rate, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb5-6">    Cc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha_val) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c_rate, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb5-7">    Cs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cumulative_trapezoid(alpha_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c_rate, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb5-8">    lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> scale <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (g_fn(Cc) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h_fn(Cs))</span>
<span id="cb5-9">    F <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>cumulative_trapezoid(lam, t_sim, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb5-10">    i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(np.searchsorted(F, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(t_sim) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> C[i], t_sim[i]</span>
<span id="cb5-12"></span>
<span id="cb5-13">C_med, t_med <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resolution_stats(g_pess, h_pess, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)</span>
<span id="cb5-14">C_med_slow, t_med_slow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resolution_stats(g_pess, h_pess, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>)</span>
<span id="cb5-15">C_med_lin, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resolution_stats(g_lin, h_lin, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>)</span>
<span id="cb5-16"></span>
<span id="cb5-17">ov_slow_half <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_pdoom(g_sig, h_sig, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>)</span>
<span id="cb5-18">ov_slow_09 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_pdoom(g_sig, h_sig, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>)</span>
<span id="cb5-19">_ov_fast <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([compute_pdoom(g_sig, h_sig, a, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> alphas])</span>
<span id="cb5-20">_ov_slow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([compute_pdoom(g_sig, h_sig, a, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> alphas])</span>
<span id="cb5-21">ov_cross <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> alphas[np.where(np.diff(np.sign(_ov_fast <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> _ov_slow)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]]</span>
<span id="cb5-22">ov_alpha_star <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X0_SAFE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (X0_SAFE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> X0_DOOM)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># safety's switch flips first iff alpha exceeds this</span></span></code></pre></div></div>
</details>
</div>
<div class="callout callout-style-simple callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Scale, and muddling through
</div>
</div>
<div class="callout-body-container callout-body">
<p>The race ends at <img src="https://latex.codecogs.com/png.latex?T%5E*%20=%20%5Cmin(T_d,%20T_s)">, at total spend <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)">, and the shapes of <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> matter only over the spends below that, the functions never being evaluated outside that (random) range. Multiplying <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> by a common constant leaves their shapes the same but sets how much compute is spent before the race resolves: larger hazards end it in expectation sooner, at a smaller spend. The convex and concave responses used here, <img src="https://latex.codecogs.com/png.latex?x%5E2"> and <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7Bx%7D">, cross at <img src="https://latex.codecogs.com/png.latex?x%20=%201">, so calling the doom hazard “convex” says something about the race only if <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)%20%5Cgg%201"> with high probability. I chose the constants so that the median resolution spend lands at around 11 units, past the crossover. That is monopoly money; an empirically calibrated model would use <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)"> in GW-years or FLOPs and scale every unit accordingly.</p>
<p>The same constant sets how long the muddling through lasts. Shrink the coefficients by a hundred and the convex-doom race takes about 3 times as long, but resolves at a spend about 8 times larger, 82 units rather than 11. Near the origin the convex and concave curves are hard to tell apart; far out, the asymptotic shape dominates, and <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> hovers near 1 in the convex-doom world. So while muddling through, our fate is decided by the tails of the response functions, which are the part we can observe least, and every trajectory looks like muddling from inside at first.</p>
</div>
</div>
</section>
<section id="some-scenarios" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="some-scenarios"><span class="header-section-number">6</span> Some scenarios</h2>
<p>Each choice of <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> is a world, and in each world the sweep above tells us which of the two controls, allocation <img src="https://latex.codecogs.com/png.latex?%5Calpha"> and pacing <img src="https://latex.codecogs.com/png.latex?c(t)">, does anything. The four shape assumptions in the plot are toy versions of familiar worldviews.</p>
<p><strong>The linear scenario.</strong> Both <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> are linear, the constant-hazard-ratio case: <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> is the same at every spend, so the average over <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)"> is trivial, speed is irrelevant, and only <img src="https://latex.codecogs.com/png.latex?%5Calpha"> matters.</p>
<p><strong>Convex-doom.</strong> Convex <img src="https://latex.codecogs.com/png.latex?g">, concave <img src="https://latex.codecogs.com/png.latex?h">, “fast takeoff, hard alignment.” Every unit of capability spend adds more doom hazard than the last, and every unit of safety spend adds less deliverance hazard than the last. While both spends are small the safety curve can be the higher of the two, but the doom curve keeps steepening, overtakes it at some spend and pulls away, and from there <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> climbs towards 1. Run the race far enough and doom wins no matter how we split the compute, so the game is getting to deliverance before the overtaking point. Faster growth means more compute is spent before the race is done, so the race is more likely to end past that point; in the sweep that is the solid fast-growth line for this scenario, placed just above the dashed slow one, and the gap is widest when the overtaking point falls between where the slow race ends and where the fast one does. So “fast takeoff, hard alignment” is the claim that doom’s curve overtakes safety’s before the race is over.</p>
<p><strong>Concave-doom.</strong> Concave <img src="https://latex.codecogs.com/png.latex?g">, convex <img src="https://latex.codecogs.com/png.latex?h">, “slow takeoff, tractable alignment”, the optimistic mirror image of the previous one. The doom hazard saturates and the deliverance hazard is superlinear once we invest enough, so <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> falls with spend and faster growth helps. This generates “<a href="https://www.lesswrong.com/tag/alignment-tax?ref=danmackinlay.name">we just need to invest enough in alignment</a>” scenarios where the alignment tax is finite and worth paying.</p>
<p><strong>Compute overhang.</strong> Sigmoidal <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> with different inflection points, “<a href="https://www.lesswrong.com/tag/compute-overhang?ref=danmackinlay.name">the capability overhang</a>.” Each hazard here is more like a switch than a slope: near off until its spend crosses a threshold, then <em>on</em>. Doom’s switch is at a certain capability spend and safety’s at a certain safety spend, and the two need not coincide. Once the first flips, one hazard is running and the other is still off. If doom’s flipped first, <img src="https://latex.codecogs.com/png.latex?%5Cpi_d%20%5Capprox%201"> for that stretch, the dangerous window; if safety’s did, there is a deliverance window (yay). With fixed <img src="https://latex.codecogs.com/png.latex?g">, <img src="https://latex.codecogs.com/png.latex?h">, the split decides which flips first: in the plots doom’s threshold is at 3 units of capability spend and safety’s at 7 units of safety spend, so with spends <img src="https://latex.codecogs.com/png.latex?(1-%5Calpha)%5Cmathcal%7BC%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Calpha%5Cmathcal%7BC%7D">, safety’s flips first only when <img src="https://latex.codecogs.com/png.latex?%5Calpha"> exceeds 0.7. The growth rate decides how long the stretch lasts. Under slow growth it is long, the first hazard has the race almost to itself, and <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)"> is a cliff in <img src="https://latex.codecogs.com/png.latex?%5Calpha"> rather than a slope: 0.96 at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200.5">, 0.04 at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200.9">. Under fast growth the second switch flips before the race has resolved, both hazards run together, and the cliff softens into a slope. So speed helps at low <img src="https://latex.codecogs.com/png.latex?%5Calpha">, where doom flips first and faster growth shortens the dangerous window, and hurts at high <img src="https://latex.codecogs.com/png.latex?%5Calpha">, where safety flips first and faster growth shortens its head start. In the sweep that reversal is where the fast and slow lines cross, at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20%5Capprox"> 0.73, which is also where the cliff is, since both are set by which switch flips first. The low-<img src="https://latex.codecogs.com/png.latex?%5Calpha"> case is my version of the <a href="https://www.lesswrong.com/tag/compute-overhang?ref=danmackinlay.name">compute-overhang argument</a> against pausing: slowing down while doom’s switch is on and safety’s is off only prolongs the time spent at <img src="https://latex.codecogs.com/png.latex?%5Cpi_d%20%5Capprox%201">. NB that same argument is lethal in the convex-doom scenario, where no safety switch is coming and faster growth hurts at every <img src="https://latex.codecogs.com/png.latex?%5Calpha">, and from inside the dangerous window the two look the same, doom hazard climbing and safety hazard flat, until safety’s switch flips or doesn’t.</p>
<p><strong>The pause.</strong> A “<a href="https://www.lesswrong.com/tag/ai-pause?ref=danmackinlay.name">compute pause</a>” here means a stop to buildout: from some <img src="https://latex.codecogs.com/png.latex?t_0"> the throughput <img src="https://latex.codecogs.com/png.latex?c(t)"> holds at its current value instead of growing. The dash-dot curves in the sweep are the limiting case <img src="https://latex.codecogs.com/png.latex?t_0%20=%200">, a world whose throughput never grew; a pause imposed after compute has already been spent is a legacy question, and I come to it below. Under a buildout stop the spends keep growing, linearly rather than exponentially, so the hazards keep growing too and the race still resolves eventually, at a smaller spend than it otherwise would. A pause therefore helps where <img src="https://latex.codecogs.com/png.latex?%5Cpi_d"> rises with spend and hurts where it falls: the implicit assumption behind <a href="https://pauseai.info/?ref=danmackinlay.name">calls for a compute pause</a> is the convex-doom world, and the overhang argument against pausing is the low-<img src="https://latex.codecogs.com/png.latex?%5Calpha"> overhang world. A pause does more than this only if we spend it raising <img src="https://latex.codecogs.com/png.latex?%5Calpha">.</p>
</section>
<section id="all-compute-bad" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="all-compute-bad"><span class="header-section-number">7</span> What if safety compute is also capabilities compute?</h2>
<p>In the base model the doom hazard depends only on capability spend <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_c"> and the deliverance hazard only on safety spend <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_s">. That quarantine is itself optimistic. Suppose safety research requires running large models, probing their behaviour, red-teaming, and training oversight systems, all of which also advance capabilities as a side effect; at the limit, all compute advances capabilities regardless of intent, because “<a href="https://www.lesswrong.com/posts/5HtDzRAk7ePWsiL2L/safety-is-not-a-product?ref=danmackinlay.name">capabilities are fungible</a>”. The <em>leaky</em> model makes the doom hazard a function of total spend,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda_d(t)%20=%20g%5Cbigl(%5Cmathcal%7BC%7D(t)%5Cbigr),%20%5Cqquad%20%5Clambda_s(t)%20=%20h%5Cbigl(%5Cmathcal%7BC%7D_s(t)%5Cbigr),%0A"></p>
<p>so that <img src="https://latex.codecogs.com/png.latex?%5Calpha"> cannot lower the doom hazard at all, only raise the competing one. In the linear case, the ratio is <img src="https://latex.codecogs.com/png.latex?%5Cpi_d%20=%20a/(a%20+%20b%5Calpha)">, against the separable model’s <img src="https://latex.codecogs.com/png.latex?a(1-%5Calpha)/(a(1-%5Calpha)%20+%20b%5Calpha)">. With <img src="https://latex.codecogs.com/png.latex?a%20=%20b">, the separable model reaches even odds at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200.5"> and the leaky one only at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%201">; even there, every step toward deliverance still drags doom closer.</p>
<div id="cell-fig-leaky-pdoom" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> go.Figure()</span>
<span id="cb6-2">leak_gap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># leaky minus separable P(doom) at α = 0.9, quoted below</span></span>
<span id="cb6-3">_i09 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.argmin(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(alphas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>))</span>
<span id="cb6-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g_fn, h_fn, name, col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [</span>
<span id="cb6-5">    (g_lin, h_lin, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Linear"</span>, C_NEUT),</span>
<span id="cb6-6">    (g_pess, h_pess, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Convex-doom"</span>, C_DOOM),</span>
<span id="cb6-7">    (g_opt, h_opt, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Concave-doom"</span>, C_SALV),</span>
<span id="cb6-8">]:</span>
<span id="cb6-9">    r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span></span>
<span id="cb6-10">    pd_sep <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([compute_pdoom(g_fn, h_fn, a, r) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> alphas])</span>
<span id="cb6-11">    pd_leak <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([compute_pdoom(g_fn, h_fn, a, r, leaky<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> alphas])</span>
<span id="cb6-12">    leak_gap[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd_leak[_i09] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pd_sep[_i09]</span>
<span id="cb6-13">    fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>alphas, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pd_sep, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (separable)"</span>,</span>
<span id="cb6-14">                             line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dash"</span>),</span>
<span id="cb6-15">                             hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"α=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.2f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  P(doom)=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.3f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>))</span>
<span id="cb6-16">    fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>alphas, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pd_leak, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (leaky)"</span>,</span>
<span id="cb6-17">                             line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>),</span>
<span id="cb6-18">                             hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"α=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.2f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  P(doom)=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.3f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>))</span>
<span id="cb6-19"></span>
<span id="cb6-20">fig.add_hline(y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, line_dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dot"</span>, line_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#bbb"</span>, annotation_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"P(doom) = ½"</span>,</span>
<span id="cb6-21">              annotation_position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom right"</span>)</span>
<span id="cb6-22">fig.update_layout(</span>
<span id="cb6-23">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>LAYOUT,</span>
<span id="cb6-24">    xaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Safety fraction α"</span>,</span>
<span id="cb6-25">    yaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"P(doom)"</span>,</span>
<span id="cb6-26">    height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">480</span>,</span>
<span id="cb6-27">)</span>
<span id="cb6-28">fig.show()</span></code></pre></div></div>
</details>
<div id="fig-leaky-pdoom" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-leaky-pdoom-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div style="height:480px; width:100%;">            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-svg.min.js"></script><script>if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script>window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-4.0.0.min.js" integrity="sha256-FEYfO0yRyLtZCpnW0Dw/0DHKQO7Afrq3ml4+rBB818o=" crossorigin="anonymous"></script>                <div id="45ec25e3-1bd1-4503-b682-dd350ff7df66" class="plotly-graph-div" style="height:100%; width:100%;"></div>            <script>                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("45ec25e3-1bd1-4503-b682-dd350ff7df66")) {                    Plotly.newPlot(                        "45ec25e3-1bd1-4503-b682-dd350ff7df66",                        [{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","dash":"dash","width":2},"name":"Linear (separable)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"NILh6ZPM7z9eRojwSIzvP\u002fDYBs9iS+8\u002fYM4MUd8J7z\u002f9How3vMfuP99rdzj3hO4\u002fOF9+\u002fo1B7j\u002f7Fsgofv3tPyiJq0rFuO0\u002fAtBl62Bz7T+qS86FTi3tP0yGCIiL5uw\u002fmMYzUxWf7D9mPBg76VbsP2qx0YUEDuw\u002fMah3a2TE6z8h0sIVBnrrP3DDr5\u002fmLus\u002fcs0eFQPj6j9v43ByWJbqP9VvIaTjSOo\u002f\u002f\u002fxchqH66T+sk5TkjqvpP\u002fKwDXmoW+k\u002f8bJu7OoK6T8xm0fVUrnoP3gDl7fcZug\u002fDSFLBIUT6D\u002fbr74YSL\u002fnP5SeMT4iauc\u002f31E9qQ8U5z+rU0R5DL3mP8ZB3bcUZeY\u002fX8w4WCQM5j+vk4I2N7LlPy+xPBdJV+U\u002fAreVplX75D857bh3WJ7kP9iSGARNQOQ\u002f5uSxqi7h4z\u002fBq0qv+IDjP48LqDmmH+M\u002fylG+VDK94j9Zdtntl1niP38DvtPR9OE\u002f7BPCtdqO4T\u002f\u002fEt4irSfhP1LmtIhDv+A\u002fdiSTMphV4D+w7smQStXfPxGKRJvJ\u002fN4\u002fZGZhQKEh3j9oLHbuxEPdP46q+8MnY9w\u002fj5EPjbx\u002f2z8tId7AdZnaP5208n5FsNk\u002fKBBujB3E2D\u002f0QiFR79TXP+zfi9Sr4tY\u002f\u002fUC8ukPt1T\u002fjdRBBp\u002fTUP7Vt1jrG+NM\u002fitbJDZD50j8rGm6u8\u002fbRP3PIQpzf8NA\u002f0FKhu4POzz9rLhv5D7TNP+dOKgE+kss\u002f9hto2OZoyT8+DYhu4jfHP67nq5QH\u002f8Q\u002fKXxP8yu+wj+Yusb\u002fI3XAP786keKFR7w\u002fKUgCa7WTtz8Zd0jJd86yP4Ve7NPW7qs\u002f5YOUt1ccoj+fhCnSRkmQPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","width":3},"name":"Linear (leaky)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"UGyanRbN7z8OKCg2147vPy\u002f6RqWJUe8\u002fNVM7dCgV7z9BlRtWrtnuP1ruQiYWn+4\u002fcL\u002fV5lpl7j\u002fdqVa\u002fdyzuP+pqS\u002ftn9O0\u002fTbnwCCe97T9cZPx3sIbtP0P\u002fbPj\u002fUO0\u002f7mxmWREc7T\u002fKqxqI4OfsP2BHvo5ptOw\u002fGeGHk6iB7D+fRrrXmU\u002fsP+CVuLY5Huw\u002fH\u002fQjpYTt6z9+ZQIwd73rPwZX7vsNjus\u002fbHROxEVf6z9VZ5VaGzHrP30jiaWLA+s\u002fameRoJPW6j\u002fYHg1bMKrqPwlXrvdefuo\u002fZXrcqxxT6j\u002fXixy\u002fZijqP2wef4o6\u002fuk\u002ffsgTeJXU6T+91WECdavpP9j95bPWguk\u002fQ+iUJrha6T8rSWIDFzPpP8ZkzAHxC+k\u002fSMpr50Pl6D+aGIeHDb\u002foP+OhqsJLmeg\u002fyMRDhvxz6D9D1D\u002fMHU\u002foP7ZorpqtKug\u002ftPZmA6oG6D9XibEjEePnPxWA8iPhv+c\u002fIDFZNxid5z9pUpGbtHrnP+INd5i0WOc\u002fR6XNfxY35z+EjPis2BXnP43htoT59OY\u002flCrhdHfU5j8eRCn0ULTmP3Bp3IGElOY\u002f30KnpRB15j9s5lvv81XmP\u002fq3ufYsN+Y\u002fxxY3W7oY5j+Nx8zDmvrlPzUKw97M3OU\u002fxkuAYU+\u002f5T9MZVkIIaLlPwFZY5ZAheU\u002fW4BG1axo5T8KHROVZEzlP69AF6xmMOU\u002fwP619rEU5T\u002fe3D9XRfnkP392zLUf3uQ\u002fAEkVAEDD5D+LnVEppajkP2KHEypOjuQ\u002fX+wlADp05D\u002fQjmuuZ1rkP8gPvzzWQOQ\u002fYOHTt4Qn5D\u002fMIBgxcg7kP1FQl76d9eM\u002fmOndegbd4z9Dwd2Eq8TjPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","dash":"dash","width":2},"name":"Convex-doom (separable)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"9Al9xuq27z+cdds3wJHvP\u002fiOXJpRde8\u002faWBw8Ohc7z9jBFXX3kbvP+Fs2apjMu8\u002f6TNJIv8e7z9UIzn0YwzvP0iv675c+u4\u002fpoCxlMLo7j\u002f2Oh\u002fSd9fuP2zyxxRlxu4\u002fHIwSV3e17j8faUC0nqTuP4q6DpLNk+4\u002fRjFdCviC7j\u002flC5x+E3LuP2bQTUcWYe4\u002fwubfdvdP7j+K7Buqrj7uP+sWUeIzLe4\u002ff7yGZn8b7j+EuNGpiQnuP2K2bDVL9+0\u002fihGPlbzk7T9MUj1I1tHtPzuZeq2Qvu0\u002f3Gph+OOq7T9YJMAgyJbtPxaF5NQ0gu0\u002fs+JLayFt7T+09fPThFftP0p8C4lVQe0\u002f+7TCfokq7T8POfoRFhPtP88si\u002fXv+uw\u002ffhTeHQvi7D9vyn2qWsjsP5vfSc3Qrew\u002fSc3erl6S7D9nWrpP9HXsP5ncjmWAWOw\u002fBskfNPA57D+GXORgLxrsP2C9iMAn+es\u002fVjM5HcHW6z\u002fS12v04LLrP9WwmCpqjes\u002fPPz6sjxm6z9V3gspNT3rP1KN4FgsEus\u002fDVDssvbk6j+iU86mY7XqPxrIvd88g+o\u002f2d+\u002fW0VO6j\u002fIu\u002f9UOBbqP9LjOfLH2uk\u002foC3+r5ub6T\u002fXGkhwTljpP1jyKRhsEOk\u002f+PVjm27D6D+d39VKunDoPwFFaCuZF+g\u002fi+lCBTW35z8KJa+7j07nP6N\u002f00953OY\u002feLBjq4Jf5j\u002f4CQrn69XlP2nzIxyMPeU\u002fkttjy6+T5D8aD6Y06dTjP1+JxSjL\u002fOI\u002fShA\u002f9YEF4j\u002fOgKo8NefgP7CYWq8aLt8\u002fG2bKMjEL3D9824NULjjYP+T8V8vtb9M\u002flgs51+iTyj+DyHhVDXa1Pw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","width":3},"name":"Convex-doom (leaky)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"29TrDJS37z8bYOj2+5PvP87ULtWtee8\u002fwZQRpdtj7z9fw4gI0lDvP9dLF166P+8\u002f0ZBlCxgw7z+2TpoamyHvPzWh5JoMFO8\u002fHAAO30QH7z9dsvEjJvvuPzhke2iZ7+4\u002fpIcOdYzk7j80UriR8NnuP4LLaqa5z+4\u002fu\u002fxXnt3F7j+w1lf3U7zuPx+vGG8Vs+4\u002fTDoAxRuq7j8aO8CKYaHuP\u002fRPlP\u002fhmO4\u002fd3Ve85iQ7j+U3aqvgojuP1rwMeWbgO4\u002fbxHRnOF47j\u002fhODcrUXHuPw4jsyboae4\u002f2gq0XqRi7j+spKfUg1vuP4cr87WEVO4\u002ffaXTVqVN7j\u002f9Yvwt5EbuP4QN09A\u002fQO4\u002f+Agv8LY57j\u002fR7YZVSDPuP7zSeuDyLO4\u002frTqthLUm7j+D+d5HjyDuP41fREB\u002fGu4\u002fUpoLk4QU7j9ohw1zng7uP0VJox\u002fMCO4\u002fXc6b4wwD7j+TNU0UYP3tP8WUvhDF9+0\u002fYSfnQDvy7T8FVgAVwuztPwJj6ARZ5+0\u002fBtSTj\u002f\u002fh7T+A8Ys6tdztP6vreJF51+0\u002f+WO2JUzS7T9vQfGNLM3tP2zZzWUayO0\u002f95OVTRXD7T+pS+vpHL7tP6fAheMwue0\u002fGIjv5lC07T9580ukfK\u002ftPyZ5IM+zqu0\u002fPTQiHval7T\u002f5HQdLQ6HtP2KrWhKbnO0\u002fAIRVM\u002f2X7T9oDbhvaZPtPxiOp4vfju0\u002fj7CNTV+K7T\u002fdNPp96IXtPx+jhud6ge0\u002fhNa7VhZ97T9oO\u002fmZunjtP+2eXYFndO0\u002ffnGx3hxw7T8xYFKF2mvtP6ArIEqgZ+0\u002fE6VqA25j7T9BvOCIQ1\u002ftP8aKgLMgW+0\u002fG0uIXQVX7T+PKmhi8VLtPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#2471a3","dash":"dash","width":2},"name":"Concave-doom (separable)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"k3XToTxR7T\u002fIlinwAlvpPy03OToFSOY\u002fPDa9DOTj4z8d+e3JYfrhP+moc0fsaOA\u002f0kukU4cx3j+J1d+C7PTbP6czB8pYBto\u002f1pR2axtW2D9RfQkAjdjWP9AS1yfThNU\u002f1H5UYRJU1D9jSOas40DTP5MleTz1RtI\u002fi+q5QMdi0T9p04WMe5HQPxj3bUNkoc8\u002f9yb\u002fjN08zj8u7roVCvLMP1ZfuPARvss\u002fydP+YIaeyj9hDCg1T5HJP5NqIwGclMg\u002f5JWQTNimxz\u002fVGWoJosbGP15rZdLB8sU\u002fpFL5jyQqxT+qzgo61mvEP9bzS339tsM\u002ftjcTGNgKwz9uTgvMt2bCP0oCasn\u002fycE\u002f9MH3fSI0wT+j8Ye2n6TAPy5V4wQDG8A\u002fG++ZvcQtvz8BtMTZuS++P9j+gwM0O70\u002fmDxwZpBPvD\u002fo+oUgOWy7P+fgOt+jkLo\u002f5vFhplC8uT+LWsW6yO64Px5kSaudJ7g\u002fxs4tdGhmtz8+p6C4yKq2P6TKVQ9k9LU\u002fn2M9X+VCtT8fQsVJ\u002fJW0P0GGT6Bc7bM\u002fYEu24r1Isz\u002f1k9DE2qeyP7yC+LhwCrI\u002fmMqMfT9wsT8OyEyrCNmwP2rtO0KPRLA\u002fir7PZC5lrz9jDOe6yUWuP5M8NRp5Kq0\u002f157Qs8MSrD8yHfL3Lf6qP6o\u002fSvs37Kk\u002f2VMoilvcqD9Jsw7LCc6nPymhxUWowKY\u002fZ9zhEI2zpT86JNzJ+aWkP8CF7MsUl6M\u002fTqBtyt+Foj8d3eZqKnGhP3biZoV+V6A\u002fj2mBwwNunj+ufhZGixqcPwYxdFXnrZk\u002f2pJVvG4elz8vF+hXGF2UP8FB3ozvT5E\u002fKgPqrw+Eiz9rVq0aQjmCPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#2471a3","width":3},"name":"Concave-doom (leaky)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"s+HFnRxY7T9EYwIDY3XpP5Aw5fQ9d+Y\u002frtv2oqkm5D+rtl8q+k7iP4u5kN2vzeA\u002fJ3xsqHQY3z9nATQhs\u002fbcP\u002f10nUrFINs\u002fhuUjK0eH2T9lJBQq0h7YP0QaQGjC3tY\u002f6hNmTmrA1T+4OxS1ib7UP4z9VNjv1NM\u002fK1wULTkA0z910QLanz3SP\u002f2+ifDYitE\u002fXIZFjfrl0D8IPlZOaE3QP0sFTL6If88\u002f36qExMd3zj8Vg726i4HNP1ISlBMYm8w\u002fv0NBLerCyz+1BnIQsPfKP+vNjOtAOMo\u002fj5Vi65aDyT+xnfAoytjIPyabkXQMN8g\u002fi79306Wdxz9R4q+N8QvHP24WFLJbgcY\u002fJUMP+179xT8CElADg3\u002fFP2lX1rxaB8U\u002ffcVcH4OUxD9lAiwFoibEP40RBC9lvcM\u002fi3MVaYFYwz81rQ7MsffCP4b7Gha3msI\u002f6o5eGFdBwj9RBgw2XOvBP0+ApPKUmME\u002fMYNUjdNIwT9QMbCn7fvAPyfjU\u002fa7scA\u002fEdgl+hlqwD\u002fxOSTB5STAPwuZo1v\u002fw78\u002f2zbpiJRCvz9Tv+v6U8W+PwMFlDgKTL4\u002fW5a3FIfWvT\u002fu8g1rnWS9P4eWeuMi9rw\u002fAyH7uu+KvD90qZ6R3iK8P3UU\u002fjzMvbs\u002fg4i9npdbuz\u002fmM61+Ify6P0jYK2hMn7o\u002faT14ivxEuj+j\u002faebF+25P6FFAr6El7k\u002fBEeEZyxEuT97Y1tL+PK4P7mcJkXTo7g\u002fwbPVRalWuD91rwBCZwu4P2lQliH7wbc\u002f+1bDsFN6tz\u002fhf\u002feRYDS3P+PD7zAS8LY\u002fUsevtlmttj84glb+KGy2PykOvIpyLLY\u002fLzfIfCnutT8T8HKKQbG1Pw=="},"type":"scatter"}],                        {"template":{"layout":{"font":{"color":"#333333","family":"Alegreya, serif","size":14},"legend":{"font":{"size":12}},"title":{"font":{"size":20}},"xaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}},"yaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}}}},"shapes":[{"line":{"color":"#bbb","dash":"dot"},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":0.5,"y1":0.5,"yref":"y"}],"annotations":[{"showarrow":false,"text":"P(doom) = ½","x":1,"xanchor":"right","xref":"x domain","y":0.5,"yanchor":"top","yref":"y"}],"legend":{"bgcolor":"rgba(255,255,255,0.5)","bordercolor":"#ccc","borderwidth":1},"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified","xaxis":{"title":{"text":"Safety fraction α"}},"yaxis":{"title":{"text":"P(doom)"}},"height":480},                        {"responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('45ec25e3-1bd1-4503-b682-dd350ff7df66');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-leaky-pdoom-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: P(doom) vs safety fraction α, comparing the separable model (dashed) with the leaky model (solid), where the doom hazard depends on total spend. The gap between them is the cost of the leak. Compute throughput c(t) = e^{0.1t}.
</figcaption>
</figure>
</div>
</div>
<p>The gap between the dashed and solid curves represents the cost of the leak. As expected, the gap grows with <img src="https://latex.codecogs.com/png.latex?%5Calpha">. At <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200.1"> the curves nearly coincide; at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200.9"> they differ by 0.49 in the linear world and 0.36 in the convex-doom world, where the superlinear doom hazard is driven by total spend. In the concave-doom world the leak hardly matters, because deliverance wins the race regardless. In other words, the leak matters most to those who want to spend the most on safety, and the argument that “safety research needs frontier models” implies that safety spend counts toward the doom hazard. The leak also makes a pause the only intervention that touches the doom hazard at all, since <img src="https://latex.codecogs.com/png.latex?%5Calpha"> cannot, though whether that moves <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)"> is again a question of shape.</p>
</section>
<section id="starting-in-the-middle" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="starting-in-the-middle"><span class="header-section-number">8</span> Starting in the middle</h2>
<p>The sweeps above all start from zero compute, which is pedagogically convenient but unrealistic because there is clearly a bunch of compute out there already (I say, asking Claude to deploy 8 H100s to check my spelling). The legacy spend that brought us here was made at, presumably, a negligible safety fraction, so it is nearly all capability spend, and its hazard <img src="https://latex.codecogs.com/png.latex?g(%5Cmathcal%7BC%7D_%7Bc,0%7D)"> has been running for a while already. It is reasonable to apply the split only to new compute; it can dilute the legacy spend but not remove it.</p>
<p>In the linear world the conditional doom probability becomes</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi_d(t)%20=%20%5Cfrac%7Ba%5Cbigl(%5Cmathcal%7BC%7D_%7Bc,0%7D%20+%20(1-%5Calpha)%5C,%5Cmathcal%7BC%7D(t)%5Cbigr)%7D%7Ba%5Cbigl(%5Cmathcal%7BC%7D_%7Bc,0%7D%20+%20(1-%5Calpha)%5C,%5Cmathcal%7BC%7D(t)%5Cbigr)%20+%20b%5Cbigl(%5Cmathcal%7BC%7D_%7Bs,0%7D%20+%20%5Calpha%5C,%5Cmathcal%7BC%7D(t)%5Cbigr)%7D,%0A"></p>
<p>which starts at the legacy ratio, nearly all doom, and drifts toward the constant <img src="https://latex.codecogs.com/png.latex?a(1-%5Calpha)/(a(1-%5Calpha)%20+%20b%5Calpha)"> as new compute swamps the old. How fast it drifts depends on <img src="https://latex.codecogs.com/png.latex?c(t)">, so the growth rate now matters even in the linear world: at a fixed split, faster growth lowers <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)">, because we spend less time at the legacy ratio and accumulate less doom hazard while there.</p>
<div id="cell-fig-legacy-spend" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> go.Figure()</span>
<span id="cb7-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> Cc0, col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, C_NEUT), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, C_RUIN), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, C_DOOM)]:</span>
<span id="cb7-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r, dash, suffix <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"solid"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fast r=0.3"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dash"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slow r=0.05"</span>)]:</span>
<span id="cb7-4">        pr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([compute_pdoom(g_lin, h_lin, a, r, Cc0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Cc0) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> alphas])</span>
<span id="cb7-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> Cc0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>:</span>
<span id="cb7-6">            legacy_hi, legacy_lo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pr.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(), pr.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># quoted below</span></span>
<span id="cb7-7">        fig.add_trace(go.Scatter(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>alphas, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pr, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"𝒞_c,0 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>Cc0<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>suffix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>,</span>
<span id="cb7-8">                                 line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dash),</span>
<span id="cb7-9">                                 hovertemplate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"α=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{x:.2f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">  P(doom)=%</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{y:.3f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>))</span>
<span id="cb7-10"></span>
<span id="cb7-11">fig.add_hline(y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, line_dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dot"</span>, line_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#bbb"</span>, annotation_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"P(doom) = ½"</span>,</span>
<span id="cb7-12">              annotation_position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom right"</span>)</span>
<span id="cb7-13">fig.update_layout(</span>
<span id="cb7-14">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>LAYOUT,</span>
<span id="cb7-15">    xaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Safety fraction α"</span>,</span>
<span id="cb7-16">    yaxis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"P(doom)"</span>,</span>
<span id="cb7-17">    height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">480</span>,</span>
<span id="cb7-18">)</span>
<span id="cb7-19">fig.show()</span></code></pre></div></div>
</details>
<div id="fig-legacy-spend" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-legacy-spend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div style="height:480px; width:100%;">            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-svg.min.js"></script><script>if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script>window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-4.0.0.min.js" integrity="sha256-FEYfO0yRyLtZCpnW0Dw/0DHKQO7Afrq3ml4+rBB818o=" crossorigin="anonymous"></script>                <div id="fb4a1256-4379-4033-a9ef-47302ece464b" class="plotly-graph-div" style="height:100%; width:100%;"></div>            <script>                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("fb4a1256-4379-4033-a9ef-47302ece464b")) {                    Plotly.newPlot(                        "fb4a1256-4379-4033-a9ef-47302ece464b",                        [{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","dash":"solid","width":2.5},"name":"𝒞_c,0 = 0 (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"l0TCKYHM7z\u002fYIC9ZNozvP0W3xGBQS+8\u002fYLgzDM0J7z+rPW8cqsfuPyIPa0flhO4\u002fSgPYN3xB7j8yat6MbP3tP8lx1tmzuO0\u002fSXL+pU9z7T\u002fwDy9sPS3tP1EfjZp65uw\u002f+jc5kgSf7D824fym2FbsP\u002f9C9R70Dew\u002fHkU7MlTE6z83BYkK9nnrPzWL3MLWLus\u002fdqMXZ\u002fPi6j8TxJzzSJbqPyvi6FTUSOo\u002fwhoqZ5L66T+tEdP1f6vpP3znKruZW+k\u002fk6bZX9wK6T+pBXF6RLnoP+dc8Y7OZug\u002fX6lKDncT6D+DeNlVOr\u002fnP3eU364Uauc\u002fgEf4TQIU5z+3C4hS\u002f7zmP9N5J8YHZeY\u002fAEgJnBcM5j\u002fYJ1uwKrLlP+hPocc8V+U\u002fV3sMjkn75D+2J8qWTJ7kPyzWTltBQOQ\u002f9BKaOiPh4z8PA3R47YDjPx81pDybH+M\u002fUm8hkie94j\u002fpMDpmjVniP4iatYfH9OE\u002f2GzspdCO4T8OydlPoyfhPyFbI\u002fM5v+A\u002f4pAY245V4D8E+lBfONXfP18AnOi3\u002fN4\u002fu5rjDZAh3j9isoM9tEPdP8t9+5UXY9w\u002fRz5v46x\u002f2z8A7hGdZpnaP8\u002fNdeI2sNk\u002fH7TCeA\u002fE2D898NDH4dTXP5WEJ9ee4tY\u002fpWvcSjft1T9CiFVgm\u002fTUP8jQ6Oq6+NM\u002fmTBaUIX50j9qhjWF6fbRP6wQAwnW8NA\u002f3QKpxHHOzz8BtVE1\u002f7PNP3MxSnQukss\u002f9TE\u002fhthoyT+JCPha1TfHPznhqsP7\u002fsQ\u002fEoboaCG+wj\u002f2dhrAGnXAP5C1GwF2R7w\u002f6f4KMKiTtz8FkJk9bc6yP6IxFC3H7qs\u002fkzaak00coj\u002fJv221PUmQPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#7f8c8d","dash":"dash","width":2.5},"name":"𝒞_c,0 = 0 (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"yViJhJbM7z+TUhGFS4zvP1x0Z11lS+8\u002feDM72eEJ7z81aH65vsfuP1uTI7T5hO4\u002fGj7ac5BB7j\u002fAZMmXgP3tPxDbR7PHuO0\u002fcJiSTWNz7T+C2YDhUC3tP0AENt2N5uw\u002fjDrRoRef7D+dhhqD61bsP+mLLccGDuw\u002fDaYhpmbE6z+cXq9JCHrrP2Ah08zoLus\u002fUBZtOwXj6j+kBt6RWpbqP+IxobzlSOo\u002fk\u002fbil6P66T\u002fdMBTvkKvpP1UwenyqW+k\u002foCS76OwK6T\u002fy32bKVLnoP7vLe6XeZug\u002f6Ovn6oYT6D8kygX4Sb\u002fnP8AhFRYkauc\u002f7CKveREU5z8YIjZCDr3mP3GGQHkWZeY\u002fasj+ESYM5j82T5zoOLLlPyn6msFKV+U\u002f7R8pSVf75D\u002foy3ESWp7kPxf\u002f5pZOQOQ\u002fHLeFNTDh4z+TexMy+oDjP2wuVbSnH+M\u002fd9k+xzO94j9pLxxYmVniP8hzsTXT9OE\u002f73hUD9yO4T9CYP1zrifhP6jDTtFEv+A\u002fOeyUcplV4D+EaXf\u002fTNXfP1lkdfjL\u002fN4\u002fYG7ui6Mh3j9lhjcox0PdP\u002f\u002fMyOspY9w\u002fvkC\u002for5\u002f2z8Fa0bEd5naPzrs6G9HsNk\u002fN8nGah\u002fE2D8bTbAc8dTXP85BJI2t4tY\u002fTDMwYEXt1T80XjHTqPTUPx3ZdLnH+NM\u002fNHS1eJH50j+FtXUF9fbRP3hDNN\u002fg8NA\u002fsezxGIbOzz8mZXEtErTNP7UiHAxAkss\u002f1oaJuehoyT909Wol5DfHPwwR4CAJ\u002f8Q\u002fhnliVC2+wj9N3kM1JXXAPynTcfWHR7w\u002fTHTWJLeTtz9klhgpec6yP\u002fI0i93Y7qs\u002fZU0uCVkcoj+mcDYBSEmQPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#8e44ad","dash":"solid","width":2.5},"name":"𝒞_c,0 = 5 (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"nQ467GLg7z9Yfz9s+bjvP+ngDCNAke8\u002f6WyWCzZp7z8KHTUc2kDvP4YXjEYrGO8\u002fmGBtdyjv7j8sy72W0MXuPzMiWIcinO4\u002fcITvJh1y7j+N6\u002fBNv0fuP9jXY88HHe4\u002fCxnKePXx7T\u002fMrP4Rh8btP9CqE127mu0\u002fljYvFpFu7T9BbmfzBkLtP5VNnaQbFe0\u002fFnxW083n7D\u002fF\u002fJUiHLrsP0C1sy4FjOw\u002ffsIyjYdd7D\u002fmj5bMoS7sP+2lNnRS\u002f+s\u002fkiQRBJjP6z963Zv0cJ\u002frPz0BlLbbbus\u002f3FLMstY96z990\u002flJYAzrP3HYftR22uo\u002fknw0ohio6j+nXTL6Q3XqP6GVlBr3Qeo\u002f9N4\u002fODAO6j+O0qN+7dnpP0Atew8tpek\u002fTwmKAu1v6T\u002fS+FllKzrpP3rr8zrmA+k\u002fqMqXexvN6D\u002fttHEUyZXoP3fBTOfsXeg\u002fsTJDyoQl6D82\u002fmuHjuznP2GPhdwHs+c\u002f\u002f6edeu545z\u002frQbYFQD7nP2xSZxT6Auc\u002fhU99LxrH5j\u002fmVJTRnYrmP3\u002fFr2aCTeY\u002fjETOS8UP5j8n33nOY9HlPwA+VCxbkuU\u002fULSekqhS5T9o\u002f70dSRLlPy6Iudg50eQ\u002f5PS1vHeP5D842Gqw\u002f0zkP75Gk4fOCeQ\u002fmhpZAuHF4z8FqbrMM4HjPzir6n3DO+M\u002flhiql4z14j\u002fCrJuFi67iP3jQkJy8ZuI\u002fnZjPGRwe4j9yilEiptThP\u002f3P+sFWiuE\u002f3ILJ6ik\u002f4T9Sr\u002fxzG\u002fPgP8SrMhknpuA\u002f6Vt+eUhY4D\u002f18HIWewngP8FjS6Z0c98\u002fl6FK5gLS3j9Clswyly7ePzaWR44nid0\u002f9vJotanh3D+LS6UcEzjcPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#8e44ad","dash":"dash","width":2.5},"name":"𝒞_c,0 = 5 (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"fBE7kX3l7z9dvm9va8TvP39WCuQfo+8\u002fZGQRSpqB7z9JnfH52V\u002fvP99NcEnePe8\u002fCm+di6Yb7z9EYcUQMvnuPzhLYiaA1u4\u002fRhkNF5Cz7j+0Gm4qYZDuP9o5LaXybO4\u002fqsvhyENJ7j9n9AHUUyXuP7yc0QEiAe4\u002fcvVQiq3c7T+ehCqi9bftP\u002f+5oHr5ku0\u002fXQV7Qbht7T9YbPIgMUjtP1uanT9jIu0\u002fEWdcwE387D\u002fLzkLC79XsP1dYg2BIr+w\u002fT+NYslaI7D8O2u\u002fKGWHsP3TCTrmQOew\u002feCc+iLoR7D\u002fD1i8+lunrPyBsJd0iwes\u002f7iSWYl+Y6z9W9VPHSm\u002frPw3Zb\u002f\u002fjRes\u002fnVgd+ikc6z+BPJWhG\u002fLqP8Fm99q3x+o\u002fqcsrhv2c6j9\u002fgcJ963HqP43f0paARuo\u002feKPZoLsa6j+xFJZlm+7pP0kc5qgewuk\u002f5EehKESV6T\u002fxrXKcCmjpP2yosbVwOuk\u002fyls5H3UM6T95\u002fj59Ft7oP3zVJm1Tr+g\u002flNlXhSqA6D+t+A1VmlDoP57lKmShIOg\u002fD2kFMz7w5z9UJDc6b7\u002fnP6K3aOoyjuc\u002fhTscrIdc5z8d\u002fXXfayrnP\u002fZrA9zd9+Y\u002fWSeA8NvE5j9ZGJliZJHmP7+ErW51XeY\u002fTAaORw0p5j+9UDkWKvTlP\u002fGvlvnJvuU\u002foCYuBuuI5T++FN5Fi1LlP7ZLjreoG+U\u002fZXXgTkHk5D8Nsd3zUqzkP79IoYLbc+Q\u002frV8Ay9g65D+qeC6QSAHkPz+yXogox+M\u002fO5RhXHaM4z\u002fYSD+nL1HjPyQZzvVRFeM\u002fQgNFxtrY4j\u002f5PcqHx5viP3N7\u002fZkVXuI\u002fTrp9TMIf4j+ucWreyuDhPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","dash":"solid","width":2.5},"name":"𝒞_c,0 = 20 (fast r=0.3)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"yLGQ1\u002fn27z\u002fuq7evruvvP8aBK9RW4O8\u002faRvdJ\u002fLU7z\u002fhV1eNgMnvP\u002fknveYBvu8\u002fm53HFXay7z+B78P73KbvP8ZwkXk2m+8\u002fInyfb4KP7z9WUuu9wIPvP6Tr\u002fUPxd+8\u002fZ7vp4BNs7z8jZkhzKGDvPwxpONkuVO8\u002f6rJa8CZI7z\u002fALdCVEDzvP9o4N6brL+8\u002fvBKp\u002fbcj7z96Mrd3dRfvPxCQaO8jC+8\u002f8No2P8P+7j+bngtBU\u002fLuP1pUPc7T5e4\u002fzmGMv0TZ7j+HAyDtpczuPxYjgy73v+4\u002fIBihWjiz7j9MU8JHaabuP\u002f3yiMuJme4\u002fnUDtupmM7j\u002f9FTrqmH\u002fuP90pCS2Hcu4\u002fEEM\u002fVmRl7j\u002fnUAg4MFjuP7Vo06PqSu4\u002fzqZOapM97j+i8mJbKjDuP6akL0avIu4\u002fMg0G+SEV7j8X22RBggfuPyVh8+vP+e0\u002fGrl8xArs7T83w+qVMt7tP9AAQSpH0O0\u002f6UiXSkjC7T9WVRS\u002fNbTtPxcn6E4Ppu0\u002fd0BGwNSX7T9Fs1\u002fYhYntP+QBXVsie+0\u002fTNFXDKps7T9IalStHF7tP3cIO\u002f95T+0\u002fufTQwcFA7T+XabGz8zHtPyw\u002fRpIPI+0\u002f2lzAGRUU7T9P7g8FBAXtP8tZ3A3c9ew\u002fwfR77Jzm7D8kdetXRtfsP+EbxQXYx+w\u002fcZY3qlG47D8nlPz3sqjsP5sMT6D7mOw\u002fzDPhUiuJ7D\u002fOGNK9QXnsP4\u002froo0+aew\u002f7uUrbSFZ7D+\u002f1JAF6kjsP0A8Nf6XOOw\u002fQBSw\u002fCoo7D99F7+kohfsP36hOZj+Buw\u002fZRYDdz726z9mz\u002fzeYeXrP9WG92to1Os\u002fCz6kt1HD6z9fl4RZHbLrPw=="},"type":"scatter"},{"hovertemplate":"α=%{x:.2f}  P(doom)=%{y:.3f}","line":{"color":"#c0392b","dash":"dash","width":2.5},"name":"𝒞_c,0 = 20 (slow r=0.05)","x":{"dtype":"f8","bdata":"exSuR+F6hD\u002fEqGZYWvGWP6Ujewai0qE\u002f6fLC4JYsqD8swgq7i4auP7dIqUpAcLI\u002fWTDNtzqdtT\u002f6F\u002fEkNcq4P5z\u002fFJIv97s\u002fPuc4\u002fykkvz9wZy42kijBP0FbwGwPv8I\u002fEk9So4xVxD\u002fjQuTZCezFP7M2dhCHgsc\u002fhCoIRwQZyT9VHpp9ga\u002fKPyYSLLT+Rcw\u002f9wW+6nvczT\u002fH+U8h+XLPP8z28Cu7hNA\u002ftfA5x\u002flP0T+d6oJiOBvSP4Xky\u002f125tI\u002fbt4UmbWx0z9W2F009HzUPz\u002fSps8ySNU\u002fJ8zvanET1j8PxjgGsN7WP\u002fi\u002fgaHuqdc\u002f4LnKPC112D\u002fJsxPYa0DZP7GtXHOqC9o\u002fmaelDunW2j+Coe6pJ6LbP2qbN0Vmbdw\u002fU5WA4KQ43T87j8l74wPePyOJEhciz94\u002fDINbsmCa3z96PtKmzzLgP267dvRumOA\u002fYzgbQg7+4D9Xtb+PrWPhP0syZN1MyeE\u002fP68IK+wu4j8zLK14i5TiPyipUcYq+uI\u002fHCb2E8pf4z8Qo5phacXjPwQgP68IK+Q\u002f+Jzj\u002fKeQ5D\u002ftGYhKR\u002fbkP+GWLJjmW+U\u002f1RPR5YXB5T\u002fJkHUzJSfmP70NGoHEjOY\u002fsoq+zmPy5j+mB2McA1jnP5qEB2qivec\u002fjgGst0Ej6D+CflAF4YjoP3f79FKA7ug\u002fa3iZoB9U6T9f9T3uvrnpP1Ny4jteH+o\u002fR++Gif2E6j88bCvXnOrqPzDpzyQ8UOs\u002fJGZ0ctu16z8Y4xjAehvsPwxgvQ0agew\u002fAd1hW7nm7D\u002f1WQapWEztP+nWqvb3se0\u002f3VNPRJcX7j\u002fR0PORNn3uP8ZNmN\u002fV4u4\u002fuso8LXVI7z+uR+F6FK7vPw=="},"y":{"dtype":"f8","bdata":"snYutM\u002f57z8UyRxjCfLvP7aUscQ96u8\u002ffH520Wzi7z\u002fzZuSBltrvPyw1Y8660u8\u002f\u002f6BJr9nK7z\u002f\u002f+9wc88LvPx\u002f6UA8Hu+8\u002fyXjHfhWz7z+ARVBjHqvvP2\u002fj6LQho+8\u002f3E98ax+b7z\u002fVxeJ+F5PvP6uA4eYJi+8\u002fiH0qm\u002faC7z\u002feO1yT3XrvP9d8Ace+cu8\u002fswGRLZpq7z\u002fLSG2+b2LvP9NJ5HA\u002fWu8\u002fmDAvPAlS7z\u002fFFnIXzUnvP0+8u\u002fmKQe8\u002fpT4F2kI57z\u002fLzjGv9DDvP7hlDnCgKO8\u002fFHhRE0Yg7z++p5qP5RfvP610ctt+D+8\u002f\u002f+tJ7REH7z+1VXq7nv7uP93gRDwl9u4\u002faU7SZaXt7j8rmjIuH+XuP5CiXIuS3O4\u002fi84tc\u002f\u002fT7j\u002fcsWnbZcvuP5avubnFwu4\u002fJ5usAx+67j9GV7aucbHuP0tzL7C9qO4\u002fg8ZU\u002fQKg7j\u002fYCUeLQZfuPyxvCk95ju4\u002f+jaGPaqF7j\u002ftQ4RL1HzuPx2ssG33c+4\u002fekiZmBNr7j\u002fIQa3AKGLuP2SbPNo2We4\u002f7bt32T1Q7j9I826yPUfuP2j+EVk2Pu4\u002fdogvwSc17j+kqXTeESzuPyljbKT0Iu4\u002fthh\u002fBtAZ7j8QB\u002fL3oxDuPwa45mtwB+4\u002fQnNaVTX+7T91rCWn8vTtPyZu+1Oo6+0\u002fqcFoTlbi7T+tE9SI\u002fNjtP6uVfPWaz+0\u002fzZt5hjHG7T+T97ktwLztP7VOA91Gs+0\u002fbG7xhcWp7T8lm\u002fUZPKDtPxTcVYqqlu0\u002fCkMsyBCN7T8eMGbEboPtPyaRw2\u002fEee0\u002f8xzWuhFw7T8WigCWVmbtPwvBdfGSXO0\u002f3Ak4vcZS7T\u002faNBjp8UjtPw=="},"type":"scatter"}],                        {"template":{"layout":{"font":{"color":"#333333","family":"Alegreya, serif","size":14},"legend":{"font":{"size":12}},"title":{"font":{"size":20}},"xaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}},"yaxis":{"tickfont":{"size":12},"title":{"font":{"size":16}}}}},"shapes":[{"line":{"color":"#bbb","dash":"dot"},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":0.5,"y1":0.5,"yref":"y"}],"annotations":[{"showarrow":false,"text":"P(doom) = ½","x":1,"xanchor":"right","xref":"x domain","y":0.5,"yanchor":"top","yref":"y"}],"legend":{"bgcolor":"rgba(255,255,255,0.5)","bordercolor":"#ccc","borderwidth":1},"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified","xaxis":{"title":{"text":"Safety fraction α"}},"yaxis":{"title":{"text":"P(doom)"}},"height":480},                        {"responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('fb4a1256-4379-4033-a9ef-47302ece464b');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-legacy-spend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: P(doom) vs safety fraction α in the linear world, starting from a legacy capability spend 𝒞_{c,0} of 0, 5 or 20 units and no legacy safety spend. Solid: fast growth (r = 0.3); dashed: slow growth (r = 0.05). With no legacy spend the two growth rates coincide; with one, faster growth dilutes it sooner and the curves separate.
</figcaption>
</figure>
</div>
</div>
<p>I chose the values 5 and 20 relative to the model’s scale: starting from zero, the linear race resolves at a spend of roughly 12 units, so the red curves describe a world that has already spent more on capability than the whole race would otherwise take to resolve. At that ratio of old to new, the whole sweep of <img src="https://latex.codecogs.com/png.latex?%5Calpha"> under slow growth moves <img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Bdoom%7D)"> only from 1.00 to 0.92, from near-certain to slightly less certain. Whether the split matters is a question about the ratio of legacy spend to <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)">, and I don’t know that ratio for the world, only that it is not zero. Legacy spend also pulls the speed verdict the opposite way from convexity: legacy makes faster growth good, because new spend at the current, better split swamps it sooner, and a convex <img src="https://latex.codecogs.com/png.latex?g"> makes it bad, because the race resolves further out along the curve. Which wins is a question about the same two numbers, the legacy ratio and the overtaking point.</p>
</section>
<section id="what-did-we-just-learn" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="what-did-we-just-learn"><span class="header-section-number">9</span> What did we just learn?</h2>
<p>Whether more compute is good or bad for safety is not, I argue, a well-posed question, but a more explicit model of how doom and deliverance would actually happen gives us some that are:</p>
<ol type="1">
<li>What are the local shapes of <img src="https://latex.codecogs.com/png.latex?g"> and <img src="https://latex.codecogs.com/png.latex?h"> over the spends the race passes through before it resolves?</li>
<li>How much compute leaks between the two pools, in each direction?</li>
<li>How large are the legacy spends relative to <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D(T%5E*)">?</li>
</ol>
<p>This is, I think, the actual content of the disagreement I started with. The people who say more compute is good believe something like “the race will be over before doom’s curve overtakes safety’s”, or that “the safety leak is small”; the people who say it is bad believe the reverse.</p>
<p>I care about that, because each of those quantities is at least in principle estimable, and they give us a way to quantify a “P(doom)” over some interventions. Evals that track how dangerous capabilities scale with training compute are the nearest thing we have to an estimate of the local shape of <img src="https://latex.codecogs.com/png.latex?g">. Whether safety techniques transfer across model scales is an estimate of the shape of <img src="https://latex.codecogs.com/png.latex?h">. Whether interpretability results get folded into the next capabilities run is an estimate of the leak. None of these are <em>easy</em> to measure, mind you, but they are the kind of thing we could be wrong about in a checkable way—cruxes—and it is not obvious to me that the discourse has disclosed as many such cruxes as it should.</p>
<p>The other thing the model gives us is a sharper vocabulary for policy interventions. “Pause” and “invest in safety” are not rival strategies; they act on different inputs to the hazard functions, and we can more easily understand how they might complement or substitute for one another in the production function of continued existence by spelling them out. Anyone who advocates one intervention in all worlds is implicitly claiming to know which world we are in, and I would like to see that claim made explicit.</p>
</section>
<section id="what-to-model-next" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="what-to-model-next"><span class="header-section-number">10</span> What to model next</h2>
<p>This framework is minimal and stylized. Some important aspects it ignores:</p>
<ol>
<li><p><strong>Multiple actors.</strong> There is no game theory in this model — just a single planner choosing <img src="https://latex.codecogs.com/png.latex?%5Calpha">. In practice, the allocation is the outcome of many actors with misaligned incentives.</p></li>
<li><p><strong>More complicated hazards than pure time integration</strong>. Above I assume <img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BC%7D(t)%20=%20%5Cint_0%5Et%20c(u)%5C,%20du.%0A"> which doesn’t seem crazy to me; in fact, we could imagine that the doom process involves both the compute that preceded it and the compute currently being done. <img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BC%7D(t)%20=%20%5Cgamma%20%5Cint_0%5Et%20c(u)%5C,%20du%20+%20%5Cdelta%20c(t).%0A"> Here, <img src="https://latex.codecogs.com/png.latex?%5Cgamma"> and <img src="https://latex.codecogs.com/png.latex?%5Cdelta"> are parameters that determine the relative importance of past versus current compute in driving the doom hazard. I would refine this version to make the model properly robust.</p></li>
<li><p>The possibility of <strong>partial doom and incremental deliverance.</strong> Both events are modelled as discrete point arrivals — single moments at which the state transitions irreversibly. This is reasonable for doom (a single catastrophe), but strange for deliverance. Real alignment progress is incremental: better interpretability, verified properties, scalable oversight, each partially reducing risk. A more plausible model would replace the deliverance point process with one whose arrivals down-modulate the doom hazard rate — each safety milestone reduces <img src="https://latex.codecogs.com/png.latex?g"> rather than ending the race outright. We could also model incremental doom — each catastrophe raises the baseline risk, although that feels less natural. If we’re worried about bad-but-not-doom events, we’d probably move to some continuous badness index, like “dollar value of harm” or “number of lives lost”, rather than a binary doom/deliverance outcome.</p></li>
<li><p><strong>Optimal control.</strong> We haven’t solved for the optimal <img src="https://latex.codecogs.com/png.latex?%5Calpha(%5Ccdot)">. This is a dynamic optimal control problem, because <img src="https://latex.codecogs.com/png.latex?%5Calpha(t)"> affects the spends <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_c(u)"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BC%7D_s(u)"> at all future times <img src="https://latex.codecogs.com/png.latex?u%20%3E%20t">. That sounds fun, but it is probably not worth investigating because even if the model were true we wouldn’t know the response functions well enough to solve it, and even if we knew how to solve it, I cannot imagine us coordinating to implement that solution.</p></li>
<li><p><strong>Granular allocation of compute to many different teams or ideas with different safety/capability profiles</strong>, rather than a single aggregate <img src="https://latex.codecogs.com/png.latex?%5Calpha">. People have suggested to me that this matters. I think we might be able to produce a more granular model by allocating compute to buckets via some kind of stick-breaking process, then taking the max hazard? Definitely out of scope for this post, but maybe worth exploring in the future.</p></li>
<li><p><strong>Granular allocation of compute to “training” or “inference”</strong>. I deliberately didn’t explore this because I don’t think that distinction will survive for long in the status quo world, let alone the world where continual inference-time learning is incentivized by training bans. But it would not be a strange distinction to make in a more detailed model if that kind of thing interests you.</p></li>
<li><p><a href="https://80000hours.org/podcast/episodes/david-duvenaud-gradual-disempowerment/?ref=danmackinlay.name">A world with aligned AI could still suck</a>.</p></li>
</ol>
</section>
<section id="further-reading" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="further-reading"><span class="header-section-number">11</span> Further reading</h2>
<ul>
<li>The economics of how compute is allocated: <a href="../notebook/ai_economics_of_cognition.html">Operationalizing the bitter lessons</a></li>
<li>The survival analysis / hazard rate formalism: <a href="../notebook/survival_analysis.html">Survival analysis and reliability</a></li>
<li>Point process foundations: <a href="../notebook/point_processes.html">Point processes</a></li>
<li><a href="https://forum.effectivealtruism.org/topics/decisive-strategic-advantage?ref=danmackinlay.name#:~:text=A%20decisive%20strategic%20advantage%20is,to%20achieve%20complete%20world%20domination.">Decisive strategic advantage - EA Forum</a></li>
<li><a href="https://www.lesswrong.com/posts/PKy8NuNPknenkDY74/soft-takeoff-can-still-lead-to-decisive-strategic-advantage?ref=danmackinlay.name">Soft takeoff can still lead to decisive strategic advantage</a></li>
<li><a href="https://www.lesswrong.com/posts/qs7SjiMFoKseZrhxK/on-first-critical-tries-in-ai-alignment?ref=danmackinlay.name">On “first critical tries” in AI alignment</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="references"><span class="header-section-number">12</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Davidson2023AI" class="csl-entry">
Davidson, Denain, Villalobos, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2312.07413">AI Capabilities Can Be Significantly Improved Without Expensive Retraining</a>.”</span>
</div>
<div id="ref-Hooker2020Hardware" class="csl-entry">
Hooker. 2020. <span>“<a href="http://arxiv.org/abs/2009.06489">The Hardware Lottery</a>.”</span> <em>arXiv:2009.06489 [Cs]</em>.
</div>
<div id="ref-Nagy2013Statistical" class="csl-entry">
Nagy, Farmer, Bui, et al. 2013. <span>“<a href="https://doi.org/10.1371/journal.pone.0052669">Statistical Basis for Predicting Technological Progress</a>.”</span> <em>PLOS ONE</em>.
</div>
<div id="ref-Sanderson2025Rethinking" class="csl-entry">
Sanderson, Foley, Guo, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2505.04075">Rethinking LLM Advancement: Compute-Dependent and Independent Paths to Progress</a>.”</span>
</div>
<div id="ref-Sole2013Evolutionary" class="csl-entry">
Solé, Valverde, Casals, et al. 2013. <span>“<a href="https://doi.org/10.1002/cplx.21436">The Evolutionary Ecology of Technological Innovations</a>.”</span> <em>Complexity</em>.
</div>
<div id="ref-Sweeney2000Bathtub" class="csl-entry">
Sweeney, and Sterman. 2000. <span>“<a href="https://doi.org/10.1002/sdr.198">Bathtub Dynamics: Initial Results of a Systems Thinking Inventory</a>.”</span> <em>System Dynamics Review</em>.
</div>
<div id="ref-Trammell2024Existential" class="csl-entry">
Trammell, and Aschenbrenner. 2024. <span>“Existential Risk and Growth.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>AI safety</category>
  <category>bounded compute</category>
  <category>catastrophe</category>
  <category>tail risk</category>
  <category>point processes</category>
  <category>probability</category>
  <category>survival analysis</category>
  <category>when to compute</category>
  <guid>https://danmackinlay.name/notebook/who_computes.html</guid>
  <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/werelt_RP-P-BI-2384.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Community sovereign AI compute</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/aus_sovereign_llm.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Follow along while we work this out— <a href="https://github.com/danmackinlay/SOV?ref=danmackinlay.name">danmackinlay/SOV</a>, or <a href="https://woozy-page-39c.notion.site/32d398af2ab0805293a9dfb1561b4bdf?pvs=105&amp;ref=danmackinlay.name">Sign up for updates</a>.</p>
</div>
</div>
</div>
<div id="fig-pray-to-oracle" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pray-to-oracle-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/pray_to_oracle_RP-P-2014-67-41.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/pray_to_oracle_RP-P-2014-67-41.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-pray-to-oracle-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>It turns out I’m writing a series of posts about practical community infrastructure building.</p>
<p>This is the second one. In a <a href="../notebook/neo_friendly_societies.html">previous post</a> I sketched out a case for neo-friendly societies—small mutual aid groups that hedge against state decay by pooling resources and investing counter-cyclically. I wrote that because it seems unwise to bank on the Australian state taking a proactive approach to the oncoming global risks. What can we, as small collectives, do to prepare for the future if we don’t want to wait on the government to do it for us?</p>
<p>Here I talk about a specific asset that a small collective might want to own: a computer that can do intellectual work for us.</p>
<p>By which I mean: a machine that can run something like the class of AI models that currently power the tools many of us are starting to depend on for work—coding assistants, research tools, document drafters, agentic workflows. The kind of thing that, right now, we rent by the token from a company in San Francisco, or increasingly, from a company in Hangzhou.</p>
<p>A self-hosted open-weight model, at least at the level of hardware and scale that I have costed here, is not going to match the quality of the best commercial systems. Claude, GPT-5+, Gemini—these are the products of billions of dollars of training compute and proprietary post-training. The best open-weight models are good—impressively good for many tasks—but they’re not <em>that</em> good, not yet. The case for sovereign compute isn’t “this is better than what we pay for.” It’s “this is good enough that we’d be glad to have it if the commercial options became unavailable, unaffordable, or untrustworthy.” It’s a hedge more than a replacement.</p>
<p>I used AI heavily to scope out some plans (see <a href="../notebook/aus_sovereign_llm_technical.html">Australian Sovereign compute technical addendum</a>). It’s there if we want to get deep into the weeds. Here I’m thinking about the big picture: why would a small collective want to own its own AI compute, what would it look like, and how does it fit into the broader friendly society model? And is it affordable right now?</p>
<div class="callout callout-style-simple callout-none no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">None</span>A scenario
</div>
</div>
<div class="callout-body-container callout-body">
<p>It’s 2028. The Taiwan Strait crisis has been going for six months. Australia’s submarine cables to Asia are damaged—some by mines, some by “accidents”—and the US has requisitioned most of the Pacific satellite bandwidth for military use. Starlink is technically available, but routed through US ground stations under wartime emergency rules; latency is bad and the terms of service now exclude “non-allied commercial AI inference.”</p>
<p>You’re a freelance engineer. You used to start every morning with Claude helping you plan your work, draft client emails, review code. That stopped working in week two of the crisis. OpenAI is intermittently available, but the API is rate-limited to US customers and the pricing has tripled. The Chinese services—DeepSeek, Qwen’s hosted API—are obviously gone.</p>
<p>Your friend down the road is in a compute collective. They have a DGX Station in a rack at a colo in Alexandria. It’s running a de-censored Qwen model, nothing fancy, noticeably worse than Claude was. But it’s <em>there</em>. It drafts emails. It reviews code. It helps your friend’s small consultancy keep billing while half the industry is paralysed. You ask if you can join. There’s a waiting list.</p>
</div>
</div>
<p>This is the scenario that sovereign compute hedges against. I do not promise it is the most likely future, but nor does it seem absurd.</p>
<section id="the-problem-with-renting-our-cognition" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-problem-with-renting-our-cognition"><span class="header-section-number">1</span> The problem with renting our cognition</h2>
<p>If we use Claude, ChatGPT, Qwen or DeepSeek, we are renting inference from someone else’s data centre.</p>
<section id="they-want-to-extract-more-rent" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="they-want-to-extract-more-rent"><span class="header-section-number">1.1</span> They want to extract more rent</h3>
<p>The process we refer to as <a href="../notebook/moloch_vs_slack.html#involution">enshittification</a> has happened to many digital services; the social media version is the canonical shape:</p>
<ol type="1">
<li>A new service offers a great product, often for free, and rapidly gains users by providing value at a competitive price.</li>
<li>Once we have built habits and workflows around it, our switching costs go up. The platform tilts toward its <em>other</em> customers — advertisers, integrators, governments — at our expense.</li>
<li>Once those parties are locked in too, the platform extracts from them as well. Quality drops, prices rise, terms narrow. Everyone but the shareholders ends up worse off than at step 1.</li>
</ol>
<p>Today’s generous AI pricing is step 1.</p>
<p>We should not assume the labs will always be content to let us use their infrastructure for purposes that threaten their interests — organising labour, challenging their market power, building competing products, or doing work they’d rather we paid them more for. Three regulatory blocs — Washington, Brussels, Beijing — decide which AI capabilities reach us, and Australia has little leverage over any of them. Canberra has no credible AI sovereignty strategy of its own, so whichever way those blocs jump seriously constrains our options.</p>
</section>
<section id="state-pressure-on-providers" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="state-pressure-on-providers"><span class="header-section-number">1.2</span> State pressure on providers</h3>
<p>In early 2026 the Pentagon demanded that Anthropic grant the military unrestricted access to Claude for “all lawful uses”. When Anthropic’s CEO refused — insisting on two conditions, no autonomous weapons and no mass domestic surveillance — the Trump administration <a href="https://www.lawfaremedia.org/article/congress-not-the-pentagon-or-anthropic-should-set-military-ai-rules?ref=danmackinlay.name">designated Anthropic a “supply chain risk to national security”</a>, a label previously reserved for firms subject to adversarial foreign influence, like Huawei. The company’s $200M Pentagon contract was terminated; Anthropic <a href="https://www.newyorker.com/news/annals-of-inquiry/the-pentagon-went-to-war-with-anthropic-whats-really-at-stake?ref=danmackinlay.name">filed federal lawsuits</a> alleging illegal retaliation.</p>
<p>The Chinese open-weight models — <a href="https://qwenlm.github.io/blog/qwen3/?ref=danmackinlay.name">Qwen</a>, <a href="https://github.com/deepseek-ai/DeepSeek-R1?ref=danmackinlay.name">DeepSeek</a> — have their own version of this. They ship with <a href="https://shisa.ai/posts/qwen2-chinese-llm-censorship-analysis/?ref=danmackinlay.name">CCP-aligned guardrails</a> baked in at the alignment stage: hard refusals on Taiwan, Tiananmen, Xinjiang, plus subtler framing biases that shift depending on the language we prompt in. (These are fixable if we own the hardware — more on that below.)</p>
<p>There are many <a href="https://writing.antonleicht.me/p/cut-off?ref=danmackinlay.name">structural reasons</a> to expect that governments will lean on providers to restrict frontier access. For the most capable tier, there may be no API at all, especially for non-US or non-Chinese nationals. Open weights on hardware we own aren’t subject to that rationing logic.</p>
</section>
<section id="the-infrastructure-is-a-target" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="the-infrastructure-is-a-target"><span class="header-section-number">1.3</span> The infrastructure is a target</h3>
<p>The war in Iran has demonstrated that <a href="https://theintercept.com/2026/03/20/ai-data-centers-military-targets-iran-war/?ref=danmackinlay.name">AI data centres are now military targets</a>. Iran struck Amazon-owned data centres in the UAE and Bahrain, citing their role in supporting US military operations — operations that themselves <a href="https://www.nbcnews.com/tech/tech-news/us-military-using-ai-help-plan-iran-air-attacks-sources-say-lawmakers-rcna262150?ref=danmackinlay.name">relied on AI systems including Claude</a> to plan airstrikes at a pace no human planning process could match. The infrastructure we depend on for coding assistants and document drafters is the same infrastructure being used to prosecute wars.</p>
<p>That makes it a great target in wars. Connectivity is fragile. Australia’s international internet runs through <a href="https://theconversation.com/only-15-known-underwater-internet-cables-connect-australia-to-the-world-and-theyre-under-threat-from-fishing-boats-spies-and-natural-disasters-235772?ref=danmackinlay.name">roughly 18 submarine cables</a> landing at a handful of points in Perth and Sydney — cables that are <a href="https://thebulletin.org/2026/02/seabed-zero-baltic-sabotage-and-the-global-risks-to-undersea-infrastructure/?ref=danmackinlay.name">increasingly being sabotaged</a> in the Baltic, the Red Sea, and the Taiwan Strait. ASPI has called this <a href="https://www.aspistrategist.org.au/the-achilles-heel-of-a-digital-nation-australias-dependence-on-subsea-cables/?ref=danmackinlay.name">Australia’s digital Achilles’ heel</a>. Satellite internet is not a fallback we control: Starlink is a US private company with <a href="https://foreignpolicy.com/2026/03/20/starlink-spacex-musk-geopolitics-war-ukraine-russia-iran/?ref=danmackinlay.name">deep military ties and no accountability to foreign governments</a> — during the Ukraine war, corporate decisions about Starlink access <a href="https://spacenews.com/starlink-and-the-unravelling-of-digital-sovereignty/?ref=danmackinlay.name">altered the tactical balance on an active battlefield</a>. Australia has no sovereign satellite internet capability, and no particular claim on American satellite bandwidth in a crisis.</p>
<p>Cloud API queries cross all of this; a machine in our city does not.</p>
<p>The hardware supply chain is similarly concentrated. AI chips are mostly made by NVIDIA (American), fabricated by <a href="https://www.tsmc.com/?ref=danmackinlay.name">TSMC</a> (Taiwanese). If the Taiwan Strait becomes contested, the global AI compute supply chain goes through a chokepoint — the reason the US is <a href="https://www.cfr.org/articles/chips-act-how-us-microchip-factories-could-reshape-economy?ref=danmackinlay.name">spending tens of billions on domestic fabrication under the CHIPS Act</a>. Buying hardware <em>now</em>, while it’s available, is itself a hedge.</p>
</section>
</section>
<section id="what-sovereign-compute-looks-like-at-a-small-scale" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="what-sovereign-compute-looks-like-at-a-small-scale"><span class="header-section-number">2</span> What “sovereign compute” looks like at a small scale</h2>
<p>When governments talk about sovereign compute, they mean billion-dollar data centres and national AI strategies. I want to talk about something much smaller: what does it look like for a group of 25–50 people to own and operate enough compute to run a frontier-class AI model?</p>
<p>It turns out this is newly, nearly feasible, because of a convergence of two trends: open-weight models have gotten very good, and the hardware to run them has gotten (relatively) affordable.</p>
<p>There are a lot of free variables (which model? which hardware? what trade-offs?), so I’ll pick some baseline examples — slightly arbitrary, but workable for a small collective.</p>
<section id="hardware" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="hardware"><span class="header-section-number">2.1</span> Hardware</h3>
<p>The <a href="https://www.nvidia.com/en-us/products/workstations/?ref=danmackinlay.name">NVIDIA DGX Station</a> — the desktop version of the machines that power most AI data centres — now comes with a <a href="https://www.nvidia.com/en-us/data-center/dgx-station/?ref=danmackinlay.name">GB300 Grace Blackwell chip</a>. It sits under a desk, draws 1600 watts, and can run models with up to a trillion parameters. The relevant configuration for our purposes:</p>
<ul>
<li>252 GB of fast GPU memory (HBM3e), plus 496 GB of slower CPU memory (LPDDR5X)</li>
<li>About <a href="https://www.servethehome.com/nvidia-dgx-station-systems-available-at-last-gb300-gb200-workstations-for-your-desktop/?ref=danmackinlay.name">20 petaFLOPs of AI compute</a></li>
<li>Price: roughly $85,000–$125,000 USD (the <a href="https://www.msi.com/news/detail/MSI-Launches-XpertStation-WS300-on-NVIDIA-DGX-Station-Architecture-148278?ref=danmackinlay.name">MSI XpertStation WS300</a> lists at $85,000 USD), or around $135,000–$195,000 AUD landed with GST.</li>
</ul>
<p>That sounds like a lot for a desktop computer. It’s not a lot split between 50 people. At $160,000 AUD for a mid-range configuration, that’s $3,200 per member — comparable to a serious API habit for a year, but we own the hardware outright.</p>
</section>
<section id="model" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="model"><span class="header-section-number">2.2</span> Model</h3>
<p>Alibaba’s <a href="https://qwenlm.github.io/blog/qwen3/?ref=danmackinlay.name">Qwen3–235B-A22B</a> is a “mixture of experts” model — it has 235 billion parameters in total, but only activates 22 billion of them for any given query, which is to say, it needs lots of RAM but not unattainable amounts. It’s not Claude or GPT-5.x — expect noticeably weaker performance on the hardest reasoning and coding tasks — but it’s solidly capable for everyday use: drafting, summarising, code assistance, research support, agentic tool-calling. Good enough to use; good enough that we’d miss it if it were gone.</p>
<p>At 4-bit quantization (a compression technique that reduces memory usage with mild quality loss), the whole model fits in the DGX Station’s GPU memory with room to spare. The remaining memory is used for tracking conversation context, which determines how many people can use it at once.</p>
<p>A single DGX Station running Qwen3–235B at 4-bit quantization can serve 20–80 concurrent conversations depending on how long each conversation’s context is (see the <a href="../notebook/aus_sovereign_llm_technical.html">technical companion</a> for the detailed maths). For a collective of 50 people, not all of whom will be using it simultaneously, this is … manageable? Maybe. We’ll hit friction because we all want to use it during the day and leave it idle overnight, but with some scheduling and patience, it could work.</p>
</section>
<section id="the-cost-of-operation" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="the-cost-of-operation"><span class="header-section-number">2.3</span> The cost of operation</h3>
<p>Running the machine 24/7 costs about $350 AUD/month in electricity at Australian rates. Amortising the hardware over three years adds about $4,500/month. Total: roughly $5,000 AUD/month, or $100 per member per month in a 50-person collective.</p>
<p>For comparison, a serious user of Claude or ChatGPT’s pro tiers pays $20–$200 USD/month for rate-limited access. A developer using API access for agentic workflows can easily burn through $100–$500 USD/month in tokens. At reasonable utilization — say the machine is processing tokens 50–80% of the time, across a mix of fast prompt ingestion and slower token generation — we might push 500M–2B tokens per month through the system. That works out to roughly $2.50–$10 AUD per million tokens, depending on how busy we keep it. For comparison, commercial API pricing for models of comparable capability runs <a href="https://docs.anthropic.com/en/docs/about-claude/models?ref=danmackinlay.name#model-comparison-table">$3–$15 per million input tokens and $10–$75 per million output tokens</a> depending on the provider and model tier (e.g.&nbsp;Claude Sonnet at <a href="https://docs.anthropic.com/en/docs/about-claude/models?ref=danmackinlay.name">$3/$15 per million tokens</a>, GPT-5.5 at <a href="https://platform.openai.com/docs/pricing?ref=danmackinlay.name">$5.00/$30</a>). Self-hosted inference comes out roughly on par with commercial APIs of similar quality — modestly cheaper if we use it heavily.</p>
<p>The arithmetic above prices sysadmin labour at zero. Someone in the collective has to install vLLM, monitor uptime, rotate models, deal with OOMs at 2am, run the network, handle access management. At Australian engineering rates, billing any of that labour turns “comparable to a Claude habit” into “we should just pay for Claude”. In money terms this is roughly a wash with commercial APIs, plus an in-kind labour subsidy from whoever in the collective enjoys this sort of thing — dead money on most days, and the only thing that works on the day the cables go down.</p>
<p>On top of the wash we get no per-query metering, no rate limits, no surprise bills, and the hardware free and clear at the end of three years.</p>
</section>
<section id="sparse-attention" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="sparse-attention"><span class="header-section-number">2.4</span> Sparse attention</h3>
<p>A recent favourable development.</p>
<p>Everything above treats the memory used to track conversations as a hard ceiling: once the machine is holding enough simultaneous conversations, the rest of us wait. That ceiling holds for the model architectures I costed. But in April 2026 DeepSeek <a href="https://api-docs.deepseek.com/news/news260424?ref=danmackinlay.name">released V4</a> (<a href="https://huggingface.co/collections/deepseek-ai/deepseek-v4?ref=danmackinlay.name">weights here</a>), and the interesting part isn’t the parameter count — it’s the attention design. V4 compresses each conversation’s context before storing it, so a sprawling million-token agentic session costs the machine under 10 GB of working memory instead of the tens of GB a conventional model of the same class would need.</p>
<p>Feed that into the same arithmetic and the result reverses. The worst case — several members each running long, context-heavy agentic sessions at the same time — eases by roughly an order of magnitude. The binding constraint shifts from “memory fills up after one power user” to “how fast can the machine generate tokens” — a throughput question we can measure and budget for. The <a href="../notebook/aus_sovereign_llm_technical.html">technical companion</a> works the arithmetic.</p>
<p>Two caveats, though. This is one model family from one Chinese lab, so it carries the same dependency-versus-alignment split as the Qwen path: open weights settle whether we’re allowed to use it, not what guardrails were baked in at the alignment stage (the de-censoring discussion below covers this). And a memory ceiling moving outward is not a throughput guarantee — sparse attention also trims the compute per token, but the constraint moves rather than vanishes.</p>
<p>The architectural direction is the signal I care about more than this particular model. Hardware we buy now doesn’t become obsolete when models get more memory-efficient; it gets more capable per dollar, which is the counter-cyclical property the whole argument rests on.</p>
</section>
</section>
<section id="removing-ccp-guardrails" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="removing-ccp-guardrails"><span class="header-section-number">3</span> Removing CCP guardrails</h2>
<p>There’s a catch with using Chinese open-weight models: they come with censorship built in. <a href="https://shisa.ai/posts/qwen2-chinese-llm-censorship-analysis/?ref=danmackinlay.name">Research from Shisa.AI</a> has documented the specific patterns: Qwen models will hard-refuse certain prompts (anything touching Taiwan sovereignty, Tiananmen Square, various topics in Xinjiang), and increasingly, newer versions have shifted from outright refusal to <em>controlled compliance</em> — they’ll answer the question, but steer the framing toward CCP-aligned positions. The behaviour is language-dependent: the Shisa.AI analysis found significantly fewer refusals in Chinese than in English on the same questions (&gt;80% fewer), suggesting the censorship is calibrated for different audiences.</p>
<p>For an Australian collective, this is a solvable problem. The open-source community has developed several techniques for removing these guardrails, ranging from essentially free to moderately expensive:</p>
<p><a href="https://huggingface.co/blog/mlabonne/abliteration?ref=danmackinlay.name">Abliteration</a> (cost: ~$100–$200 AUD for models of our size) is a technique from <a href="../notebook/ai_mech_interp.html">representation engineering</a> where we identify the “refusal direction” in the model’s internal representations and remove it through a linear algebra operation on the weights. No training required — it’s a post-processing step that takes hours, not days. Over <a href="https://huggingface.co/models?other=abliterated&amp;ref=danmackinlay.name">4,000 community-modified models</a> have been published using this method on HuggingFace alone. It’s effective at removing hard refusals, though it doesn’t fully address the subtler framing biases.</p>
<p>Preference fine-tuning via Direct Preference Optimization <span class="citation" data-cites="Rafailov2023Direct">(Rafailov et al. 2023)</span> (cost: ~$1,500–$4,000 AUD including dataset creation) goes deeper. We create a dataset of question-answer pairs where the “preferred” answer is neutral/factual and the “dispreferred” answer is CCP-aligned, then train the model to prefer the neutral framing. This addresses both hard refusals and soft steering. The training runs on rented cloud GPUs — a few hundred to a couple of thousand dollars’ worth — and the resulting model can be deployed on our own hardware permanently.</p>
<p>Either way, the total cost of removing CCP guardrails is a rounding error compared to the hardware. And once it’s done, it’s done — the modified weights are on our machine, and no one can remotely re-censor them.</p>
</section>
<section id="audition-before-we-buy" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="audition-before-we-buy"><span class="header-section-number">4</span> Audition before we buy</h2>
<p>We don’t have to commit $160k on faith. Cloud GPU rental is now cheap enough that a collective can test-drive the full setup before buying hardware.</p>
<p>Rent a couple of H100 GPUs from a provider like <a href="https://lambdalabs.com/?ref=danmackinlay.name">Lambda Labs</a> or <a href="https://www.runpod.io/?ref=danmackinlay.name">RunPod</a> for <a href="https://intuitionlabs.ai/articles/h100-rental-prices-cloud-comparison?ref=danmackinlay.name">$2–$3 USD/hour per GPU</a>. Deploy the model with the same inference software (<a href="https://github.com/vllm-project/vllm?ref=danmackinlay.name">vLLM</a> or <a href="https://github.com/sgl-project/sglang?ref=danmackinlay.name">SGLang</a>) that we’d use on the DGX Station. Run our actual workloads for a week or two. See if the throughput, latency, and model quality meet our needs.</p>
<p>Total cost for a two-week test: $500–$1,000 AUD. If the collective decides to proceed, that’s money well spent on due diligence. If it decides not to, we’ve lost the cost of a nice dinner, not a house deposit.</p>
<p>NVIDIA also offers <a href="https://www.nvidia.com/en-us/data-center/dgx-cloud/?ref=danmackinlay.name">DGX Cloud</a>, which runs the exact same software stack as the physical DGX Station — same <a href="https://docs.nvidia.com/nim/large-language-models/latest/getting-started.html?ref=danmackinlay.name">NIM inference microservices</a>, same NGC containers, same management tooling. If workflow portability matters, this is the smoothest way to audition.</p>
</section>
<section id="the-nbn-still-sucks-but-not-so-badly-that-we-cant-work-around-it" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="the-nbn-still-sucks-but-not-so-badly-that-we-cant-work-around-it"><span class="header-section-number">5</span> The NBN still sucks but not so badly that we can’t work around it</h2>
<p>There’s a mundane infrastructure challenge that’s easy to overlook: <a href="../notebook/australia_broadband.html">Australian residential internet is not great</a>. If the machine is in someone’s house on an NBN connection — particularly one of the older HFC or FTTN links — we’re looking at multiple brief outages per day, asymmetric upload speeds that make remote access sluggish, and no SLA to speak of. For a token server, this is mostly an annoyance (our request fails, we retry), but for longer agentic workflows that run over minutes or hours, connection drops mid-session are annoying.</p>
<p>There’s a spectrum of options here. At one end: someone’s spare room, cheap, community-feeling, unreliable. At the other: a quarter-rack at a local colo facility with redundant fibre, expensive, corporate-feeling, rock-solid. In between: a business-grade NBN plan with a static IP and better SLA, or a 5G failover link for redundancy. The right answer probably depends on how many members are remote versus local, and how tolerant the collective is of occasional downtime.</p>
<p>Being as reliable as Anthropic’s data centres in peacetime would be nice, but the baseline that matters is the day the cables are cut — and even the NBN can probably beat that.</p>
</section>
<section id="the-member-side-stack" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="the-member-side-stack"><span class="header-section-number">6</span> The member-side stack</h2>
<p>The arithmetic above sizes the <em>server</em> — one DGX Station, one model, shared. Each member also needs <em>client</em>-side software on their laptop pointing at the shared endpoint, and the choices there are largely independent of the server choices.</p>
<p>It helps to be precise about the layers between a member’s keyboard and the model running in the colo, because most of the operational decisions live at different layers. The deep version of this taxonomy is <a href="../notebook/agent_harness_design.html#the-stack">on the harness design page</a>; the short version for the collective case:</p>
<ul>
<li><strong>Server / daemon</strong> (collective-owned) — the <a href="https://github.com/vllm-project/vllm?ref=danmackinlay.name">vLLM</a> or <a href="https://github.com/sgl-project/sglang?ref=danmackinlay.name">SGLang</a> process on the DGX Station, exposing an OpenAI-compatible HTTP endpoint. One of these, shared.</li>
<li><strong>Harness / agent loop</strong> (per-member) — the orchestration layer over the endpoint. Manages conversation state, tool calls, system prompts, multi-turn agent loops. Examples: <a href="https://github.com/earendil-works/pi?ref=danmackinlay.name"><code>pi</code></a> (Mario Zechner’s npm-installable coding-agent CLI, cross-platform), <a href="https://aider.chat?ref=danmackinlay.name">Aider</a>, <a href="https://github.com/sst/opencode?ref=danmackinlay.name">OpenCode</a>, <a href="https://continue.dev?ref=danmackinlay.name">Continue</a>.</li>
<li><strong>Frontend / chat client</strong> (per-member, optional) — Osaurus, Jan, LM Studio, a web UI, or any editor plugin (Cursor, Zed AI) configured for a custom OpenAI provider.</li>
</ul>
<p>The collective owns and operates the server; the members each pick their own harness and frontend pointing at it. Two members might use the same backend with completely different above-the-server setups — Cursor configured for a custom OpenAI provider for one, pi-in-tmux + Helix for another — without interfering with each other.</p>
<p><code>pi</code> is the harness that fits this model particularly well. It is cross-platform (Node, runs on macOS, Linux, Windows, Android), provider-agnostic by design (15+ providers plus arbitrary OpenAI- or Anthropic-compatible endpoints via a small config file), and harness-only — it does not bundle a chat GUI or tie itself to one model. A member adds the collective endpoint to <code>~/.pi/agent/models.json</code> alongside their Anthropic key and their local Ollama; pi switches between them with <code>/model</code> or <code>Ctrl+L</code>. That makes failover graceful at the per-member level — when the colo endpoint is up, the harness uses it; when it is down (cables cut, building on fire, sysadmin asleep), the same harness still works against Claude or a smaller local model. The collective backend is an upgrade, not a single point of failure.</p>
<p>The harness layer also explains where the operational labour lands once the server is humming. Members customise their own tooling — keybindings, extensions, skills, prompt templates — without coordination. The collective only has to keep one stack running (vLLM + a chosen model); each member runs whatever shape of agent loop over it suits their work.</p>
</section>
<section id="other-community-infrastructure" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="other-community-infrastructure"><span class="header-section-number">7</span> Other community infrastructure</h2>
<p>In the <a href="../notebook/neo_friendly_societies.html">previous post</a>, I argued that neo-friendly societies should invest in counter-cyclical assets — things that retain value precisely when the state and conventional institutions are under stress.</p>
<p>Sovereign compute infrastructure fits this criterion pretty well:</p>
<ul>
<li>It becomes <em>more</em> valuable if geopolitical tensions restrict access to foreign AI services</li>
<li>It becomes <em>more</em> valuable if commercial API providers raise prices or restrict capabilities</li>
<li>It becomes <em>more</em> valuable if regulatory changes create compliance barriers to using foreign-hosted AI</li>
<li>Unlike financial assets, it has direct use value — it does useful work for members every day</li>
</ul>
<p>It’s also a natural complement to the other things a friendly society might do. The same AI infrastructure that serves our members’ professional needs can also help run the society itself — generating regulatory filings, processing claims, managing communications. This is the AI-assisted administration angle from the previous post, but with infrastructure we own rather than rent.</p>
</section>
<section id="replicating-this-model" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="replicating-this-model"><span class="header-section-number">8</span> Replicating this model</h2>
<p>As with the friendly society model itself, the most valuable output from a societal perspective isn’t a single collective with a single machine, but the documented, replicable process that other groups can fork.</p>
<p>The hardware purchase process, the model selection and de-censoring procedure, the inference server configuration, the access management for members, the cost-sharing model — all of this can be packaged as a how-to guide. Publish the playbook, let others spin up their own nodes.</p>
<p>A network of small collectives, each with their own sovereign compute and running their own models, would be meaningfully more resilient than any individual group relying on a single commercial provider. And unlike a data centre, a DGX Station fits under a desk and plugs into a standard power circuit (a standard Australian 10A outlet can do that, and a <a href="https://www.accesscomms.com.au/australian-mains-plug-variants/?ref=danmackinlay.name">20A circuit</a> is a routine job for an electrician). The barrier to entry could be financial, not technical.</p>
</section>
<section id="legal-structure" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="legal-structure"><span class="header-section-number">9</span> Legal structure</h2>
<p>A collective that owns a $160k asset needs a legal entity. The <a href="../notebook/aus_sovereign_llm_technical.html#legal-coop">technical companion</a> explores the options in detail, but the short version: an <a href="https://www.nsw.gov.au/business-and-economy/incorporated-associations/about-incorporated-associations?ref=danmackinlay.name">incorporated association</a> is the simplest starting point (~$200 to set up, ~$57/year), and a <a href="https://business.gov.au/planning/business-structures-and-types/business-structures/co-operative?ref=danmackinlay.name">cooperative under the Co-operatives National Law</a> is the better long-term fit if the model proves viable — it’s the legal form designed for groups of people who jointly own infrastructure they all use. ACNC registration as a charity is probably not applicable unless the collective has an explicit community education or digital inclusion mission. Either way, budget $2,000–$5,000 for a solicitor to review the constitution before committing members’ money — cheap insurance on a six-figure purchase.</p>
</section>
<section id="open-questions" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="open-questions"><span class="header-section-number">10</span> Open questions</h2>
<ul>
<li>How do we handle the operational side — who has physical access to the machine, who administers it, what happens if it breaks?</li>
<li>Where does it physically live? Someone’s house? A shared office? A colo? (See broadband discussion above.)</li>
<li>Is there appetite for a network of these collectives, sharing infrastructure knowledge and potentially load-balancing across nodes?</li>
<li>What’s the right model governance framework? Who decides which models to run, what guardrails to keep or remove, what use policies to enforce?</li>
<li>How do we handle ongoing model updates? New versions of Qwen and other open models are released regularly; someone needs to evaluate, de-censor, quantize, and deploy them.</li>
</ul>
<p>As with the friendly society post: if you know about any of this, or if you’re interested in being part of a pilot group, I’d love to hear from you.</p>
<p>The hardware is available for now. The models are available. The software stack seems mature enough. The missing piece is the institutional form — the small, trust-based collective that can actually make it go.</p>
<p>That’s the part we need to build.</p>
</section>
<section id="related-projects" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="related-projects"><span class="header-section-number">11</span> Related projects</h2>
<ul>
<li><a href="https://www.projectnomad.us/?ref=danmackinlay.name#features">Project NOMAD — Offline Knowledge &amp; AI Server</a> (<a href="https://github.com/Crosstalk-Solutions/project-nomad?ref=danmackinlay.name">Crosstalk-Solutions/project-nomad</a>) — a self-contained, offline survival computer with knowledge tools and AI bundled in.</li>
</ul>
<p><img src="https://latex.codecogs.com/png.latex?%0Ac=%5Cmathbb%7BE%7D_%7B(x)%5Csim%20P%7D%5Bh_%5Ctheta(x)%5D%0A"></p>
</section>
<section id="references" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="references"><span class="header-section-number">12</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Rafailov2023Direct" class="csl-entry">
Rafailov, Sharma, Mitchell, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2305.18290">Direct Preference Optimization: Your Language Model Is Secretly a Reward Model</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>communicating</category>
  <category>community project</category>
  <category>cooperation</category>
  <category>DIY</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>institutions</category>
  <category>resilient tech</category>
  <category>slop</category>
  <category>straya</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/aus_sovereign_llm.html</guid>
  <pubDate>Sun, 22 Mar 2026 01:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/pray_to_oracle_RP-P-2014-67-41.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Sovereign compute for small collectives: technical implementation guide</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/aus_sovereign_llm_technical.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>Follow along while we work this out— <a href="https://github.com/danmackinlay/SOV?ref=danmackinlay.name">danmackinlay/SOV</a> or <a href="https://woozy-page-39c.notion.site/32d398af2ab0805293a9dfb1561b4bdf?pvs=105&amp;ref=danmackinlay.name">Sign up for updates</a>.</p>
</div>
</div>
</div>
<div id="fig-colossal-elephant-section" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-colossal-elephant-section-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/colossal_elephant_section.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/colossal_elephant_section.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-colossal-elephant-section-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>This is the technical companion to <a href="../notebook/aus_sovereign_llm.html">my sovereign LLM post</a>, which makes the institutional and geopolitical case for small collectives owning their own AI inference hardware. This post lays out the concrete implementation pathway: what to buy, what software to run, how to remove CCP guardrails from open-weight models, and what performance to expect.</p>
<p>There was a lot of research to make this work, so it is <em>heavily</em> AI-assisted. Be wary of hallucinations.</p>
<p>I’ll walk through a worked example targeting Alibaba’s Qwen3–235B-A22B served on an NVIDIA DGX Station GB300, since that’s the sweet spot I identified in the companion post. But most of this generalises to other models and hardware.</p>
<p>Sounds interesting? <a href="../contact.html">Get in touch</a>.</p>
<section id="the-box" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-box"><span class="header-section-number">1</span> The box</h2>
<section id="specifications" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="specifications"><span class="header-section-number">1.1</span> Specifications</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Component</th>
<th>Specification</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>GPU</td>
<td>1× <a href="https://www.nvidia.com/en-us/data-center/dgx-station/?ref=danmackinlay.name">NVIDIA GB300 Grace Blackwell Ultra Superchip</a></td>
</tr>
<tr class="even">
<td>GPU memory (HBM3e)</td>
<td><a href="https://wccftech.com/nvidia-dgx-station-upgraded-gb300-blackwell-ultra-desktop-superchip/?ref=danmackinlay.name">252 GB @ 7.1 TB/s bandwidth</a></td>
</tr>
<tr class="odd">
<td>CPU memory (LPDDR5X)</td>
<td>496 GB @ 396 GB/s bandwidth</td>
</tr>
<tr class="even">
<td>Total unified memory</td>
<td>784 GB</td>
</tr>
<tr class="odd">
<td>AI compute</td>
<td><a href="https://www.servethehome.com/nvidia-dgx-station-systems-available-at-last-gb300-gb200-workstations-for-your-desktop/?ref=danmackinlay.name">~20 PFLOPs (FP4)</a></td>
</tr>
<tr class="even">
<td>CPU-GPU interconnect</td>
<td>900 GB/s NVLink-C2C</td>
</tr>
<tr class="odd">
<td>Networking</td>
<td>800 Gb/s CX8 SuperNIC</td>
</tr>
<tr class="even">
<td>Power</td>
<td>1600W TDP</td>
</tr>
<tr class="odd">
<td>Form factor</td>
<td>Desktop tower</td>
</tr>
</tbody>
</table>
</section>
<section id="buying-one-in-australia" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="buying-one-in-australia"><span class="header-section-number">1.2</span> Buying one in Australia</h3>
<p>The DGX Station ships through NVIDIA’s partner network. Australian vendors include <a href="https://xenon.com.au/product/nvidia-dgx-station/?ref=danmackinlay.name">XENON Systems</a> (NVIDIA Elite Partner for ANZ, handles the full DGX line and enterprise support), <a href="https://www.dell.com/en-au?ref=danmackinlay.name">Dell Australia</a>, <a href="https://www.msi.com/news/detail/MSI-Launches-XpertStation-WS300-on-NVIDIA-DGX-Station-Architecture-148278?ref=danmackinlay.name">MSI</a> (its XpertStation WS300 is the DGX Station reference design, listed at $85,000 USD), and <a href="https://nvidia.mmt.com.au/?ref=danmackinlay.name">MMT</a>, Australia’s NVIDIA distributor for configure-to-order options.</p>
<p>Expect <strong>$135,000–$195,000 AUD</strong> landed depending on configuration, plus GST, shipping, and a UPS — we do not want 1600W of AI compute on raw mains power.</p>
</section>
<section id="power-and-cooling" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="power-and-cooling"><span class="header-section-number">1.3</span> Power and cooling</h3>
<p>1600W sustained is about the draw of a large space heater. Standard Australian outlets are <a href="https://www.accesscomms.com.au/australian-mains-plug-variants/?ref=danmackinlay.name">10 A at 240 V (2400 W)</a>, fine on a dedicated circuit; an electrician installing a 20 A circuit (4800 W) is a routine job if we want headroom. We’ll want decent ventilation if the machine lives in a small room. At <a href="https://www.canstar.com.au/energy/electricity-costs-kwh/?ref=danmackinlay.name">Australian retail rates (~$0.30/kWh)</a>, running 24/7 costs about <strong>~$350 AUD/month</strong>.</p>
</section>
</section>
<section id="what-the-box-rations" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="what-the-box-rations"><span class="header-section-number">2</span> What the box rations</h2>
<p>Everything below is a way of spending the resources of one box, so it helps to name them first. The DGX Station has three scarce quantities, and almost every design choice trades one against another.</p>
<p><em>Fast memory</em> — 252 GB of HBM3e. It holds the model weights and the live conversations. Weights are a fixed cost paid once; whatever is left over after the weights sets how many people can talk to the machine at the same time.</p>
<p><em>Memory bandwidth</em> — 7.1 TB/s out of that HBM. Generating a token means reading weights out of memory, so bandwidth, not compute, decides how fast the machine writes.</p>
<p><em>Compute</em> — about 20 PFLOPs at FP4. This mostly bites while ingesting a prompt, where the work is one big matrix multiply rather than a memory sweep.</p>
<p>Mixture-of-experts shrinks the compute and bandwidth bill per token. Quantization shrinks the weights so they fit in fast memory. Sparse or windowed attention shrinks the conversations so more of them fit. Each technique below stretches one of those three.</p>
</section>
<section id="models-that-fit" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="models-that-fit"><span class="header-section-number">3</span> Models that fit</h2>
<section id="mixture-of-experts" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="mixture-of-experts"><span class="header-section-number">3.1</span> Mixture of experts</h3>
<p>The baseline I cost throughout is Alibaba’s <a href="https://qwenlm.github.io/blog/qwen3/?ref=danmackinlay.name">Qwen3–235B-A22B</a>, a mixture-of-experts model: 128 expert feed-forward networks per layer, of which 8 fire for any given token. It carries 235B parameters in total but only puts ~22B to work per token. Total parameters set the memory footprint; active parameters set the compute and bandwidth cost. That split is what makes self-hosting tractable — we pay 235B to store the model and 22B to run it, and the result is competitive with dense models of 70–100B parameters. The attention is grouped-query: 64 query heads, 4 KV heads, 128 head dimension, 94 transformer layers. Those last numbers come back when we count KV cache.</p>
</section>
<section id="quantization" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="quantization"><span class="header-section-number">3.2</span> Quantization</h3>
<p>The weights have to fit in 252 GB of HBM, with plenty left for conversations. Quantization sets how much room is left over.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Quantization</th>
<th>Model size in memory</th>
<th>Fits in 252GB HBM?</th>
<th>HBM left for KV cache</th>
<th>Quality impact</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>BF16 (full)</td>
<td>~470 GB</td>
<td>No</td>
<td>N/A</td>
<td>Baseline</td>
</tr>
<tr class="even">
<td>FP8</td>
<td>~235 GB</td>
<td>Barely</td>
<td>~17 GB (unusable)</td>
<td>Minimal</td>
</tr>
<tr class="odd">
<td>AWQ 4-bit</td>
<td>~124 GB</td>
<td>Comfortable</td>
<td><strong>~128 GB</strong></td>
<td>Modest</td>
</tr>
<tr class="even">
<td>GPTQ Int4</td>
<td>~124 GB</td>
<td>Comfortable</td>
<td><strong>~128 GB</strong></td>
<td>Similar to AWQ</td>
</tr>
</tbody>
</table>
<p>AWQ 4-bit is the pick. It puts the model in ~124 GB and leaves ~128 GB free, and that free space is the conversation budget — the thing that decides how many people the box serves at once. FP8 looks better on paper but leaves ~17 GB, which forces constant offload to the slow tier and makes multi-user serving fall over. MoE models tolerate 4-bit well, since only 8 of 128 experts touch any token. Pre-quantized weights are on HuggingFace (e.g.&nbsp;<a href="https://huggingface.co/QuantTrio/Qwen3-235B-A22B-Instruct-2507-AWQ?ref=danmackinlay.name">QuantTrio/Qwen3–235B-A22B-Instruct-2507-AWQ</a>), or we can quantize our own with <a href="https://github.com/casper-hansen/AutoAWQ?ref=danmackinlay.name">AutoAWQ</a>.</p>
</section>
<section id="sparse-attention" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="sparse-attention"><span class="header-section-number">3.3</span> Sparse attention</h3>
<p>A conversation’s context lives in the KV cache, and in a conventional transformer that cache grows linearly with length — every token of history costs memory, in the same scarce HBM as the weights, for as long as the conversation lives. Sparse attention breaks that link. <a href="https://api-docs.deepseek.com/news/news260424?ref=danmackinlay.name">DeepSeek’s V4</a> (April 2026, <a href="https://huggingface.co/collections/deepseek-ai/deepseek-v4?ref=danmackinlay.name">weights</a>) uses an attention design DeepSeek calls DSA: it compresses the context before storing it, so a full million-token session costs roughly 9.6 GB of KV cache instead of the tens of GB a conventional model of the same class needs — about a 90% cut, per the DeepSeek tech report. It trims attention compute too, to roughly 27% of the previous generation’s at 1M context. For a box whose tightest constraint is long conversations, this relaxes the binding limit more than anything else; the concurrency maths below shows by how much.</p>
</section>
<section id="hybrid-sliding-window-attention" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="hybrid-sliding-window-attention"><span class="header-section-number">3.4</span> Hybrid sliding-window attention</h3>
<p>DSA is not the only way to break that linear-KV link, and the alternative is older and far better supported. A <em>hybrid sliding-window</em> model keeps ordinary attention but confines most layers to a fixed local window — each token sees only the last 128 tokens, say — and interleaves the occasional full-attention layer to carry information across the whole context. The windowed layers cost a constant amount of KV however long the conversation runs (a 128-token rolling buffer never grows); only the few global layers pay the linear-in-length price. <a href="https://huggingface.co/XiaomiMiMo/MiMo-V2.5?ref=danmackinlay.name">Xiaomi’s MiMo-V2.5</a> is built this way — 39 of its 48 attention layers slide a 128-token window, nine attend globally, with a learnable attention-sink bias so the windowing does not forget how the conversation began. Of the ~240 KB per token (FP16) a fully dense version would spend on KV, only those nine global layers — about 45 KB — keep growing with length; the rest is capped. My back-of-envelope makes that a bit over 5×; the <a href="https://huggingface.co/XiaomiMiMo/MiMo-V2.5?ref=danmackinlay.name">model card</a> calls it nearly 6×. Quantize the KV to FP8 as before and a million-token session costs roughly 22 GB, against the tens of GB a dense model of this size would need and the ~9.6 GB DSA reaches — windowing lands between dense and DSA, much cheaper than the first and not quite as aggressive as the second.</p>
<p>The trade-off runs the other way to DSA’s, and in our favour. Sliding windows are the mechanism under Mistral and Gemma, years old and handled by every serving engine without special cases — none of the pin-a-known-good-commit fragility DeepSeek V4 still demands, and none of the de-censoring-tooling lag. What we give up is some long-range fidelity, since a windowed model leans on its global layers and the sink bias to reach back where DSA compresses the entire history — but MiMo’s published long-context scores hold up well past 128k and degrade rather than collapse at 1M, so the two routes land close on capability and diverge mostly on how much we have to babysit the stack.</p>
</section>
</section>
<section id="concurrency" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="concurrency"><span class="header-section-number">4</span> Concurrency</h2>
<p>Fast memory holds weights plus conversations:</p>
<pre><code>fast memory  =  weights (paid once)  +  KV cache (paid per user, per token)

concurrent users  ≈  (memory left after weights)  ÷  (KV cost of one conversation)</code></pre>
<p>Qwen3–235B’s grouped-query attention (4 KV heads, 128 head dimension, 94 layers) costs, per token of context:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BKV%20per%20token%7D%20=%202%20%5Ctimes%20n_%5Ctext%7Bkv%5C_heads%7D%20%5Ctimes%20d_%5Ctext%7Bhead%7D%20%5Ctimes%20n_%5Ctext%7Blayers%7D%20%5Ctimes%20%5Ctext%7Bbytes%7D%20=%202%20%5Ctimes%204%20%5Ctimes%20128%20%5Ctimes%2094%20%5Ctimes%202%20%5Capprox%20192%5Ctext%7B%20KB%20(FP16)%7D"></p>
<p>Quantizing the KV cache to FP8 (supported by <a href="https://github.com/vllm-project/vllm?ref=danmackinlay.name">vLLM</a> and <a href="https://github.com/sgl-project/sglang?ref=danmackinlay.name">SGLang</a> with little quality loss) halves that to ~96 KB/token. With ~128 GB free after the 4-bit weights, the box holds about 1.4M tokens of context at once. Divide by session length to turn tokens into people:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Session length</th>
<th>Concurrent users</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>8K tokens</td>
<td>~175</td>
</tr>
<tr class="even">
<td>32K tokens</td>
<td>~44</td>
</tr>
<tr class="odd">
<td>128K tokens</td>
<td>~11</td>
</tr>
<tr class="even">
<td>1M tokens</td>
<td>~1.4</td>
</tr>
</tbody>
</table>
<p>Concurrency falls off a cliff as conversations lengthen. A 50-person collective with 5–15 active members sits comfortably at 8K–32K and falls over at 1M, where one member running a single million-token agentic session fills the box. The comfortable numbers assume short conversations — but long-context agentic work, the thing the collective most wants, drives sessions toward the bottom row, exactly where the box is tightest.</p>
<p>Cheaper attention is the way out of that corner, and there are two routes to it. On DeepSeek V4-Flash a full 1M-token session costs ~9.6 GB of KV rather than the tens of GB above, and its ~90 GB of 4-bit weights leave ~160 GB for cache, so the bottom row climbs from ~1.4 users to ~16. MiMo-V2.5’s hybrid windowing is the gentler version of the same move: a 1M-token session costs ~22 GB instead of tens, but its fatter ~160 GB of 4-bit weights leave only ~90 GB for cache, so it trails DSA on both the size of the cut and the room left to spend it. Either way the bottom row stops being a cliff:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Session length</th>
<th>conventional (Qwen)</th>
<th>MiMo-V2.5 + SWA</th>
<th>V4-Flash + DSA</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1M-token</td>
<td>~1.4 users</td>
<td>~4 users</td>
<td>~16 users</td>
</tr>
<tr class="even">
<td>128K-token</td>
<td>~11 users</td>
<td>~30 users</td>
<td>KV no longer the limit</td>
</tr>
</tbody>
</table>
<p>The box does not become infinite. Sixteen million-token sessions fitting in memory is not the same as the box generating tokens fast enough for sixteen heavy users at once — DSA’s compute cut helps, but the constraint moves from memory to throughput rather than vanishing. And these are illustrative figures: they reuse the conventional numbers above and DeepSeek’s published 1M figure rather than measured intermediate points, which is what phase 2 of the audition is for. Treat them as order-of-magnitude.</p>
<section id="the-warm-tier" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="the-warm-tier"><span class="header-section-number">4.1</span> The warm tier</h3>
<p>The 496 GB of LPDDR5X CPU memory works as a warm tier for KV cache. At 396 GB/s it’s roughly 18× slower than HBM — fine for sessions that have been idle a few seconds. That’s room for another ~2.6M tokens (FP8 KV) of sleeping conversations the box can resume without re-prefilling them. Both <a href="https://docs.vllm.ai/en/latest/examples/others/lmcache/?ref=danmackinlay.name">vLLM via LMCache</a> and SGLang (native) support this tiering.</p>
</section>
</section>
<section id="throughput" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="throughput"><span class="header-section-number">5</span> Throughput</h2>
<p>Decode — generating one token at a time — is bandwidth-bound: each token reads the active expert weights out of HBM.</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bactive%20expert%20weights%20at%204-bit%7D%20%5Capprox%2022%5Ctext%7BB%7D%20%5Ctimes%200.5%5Ctext%7B%20bytes%7D%20=%2011%5Ctext%7B%20GB%7D"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Btheoretical%20max%7D%20%5Capprox%20%5Cfrac%7B7.1%5Ctext%7B%20TB/s%7D%7D%7B11%5Ctext%7B%20GB%7D%7D%20%5Capprox%20645%5Ctext%7B%20tok/s%7D"></p>
<p>After attention, KV traffic, and memory-controller overhead:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Scenario</th>
<th>Estimated throughput</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Single user, single stream</td>
<td>200–400 tok/s</td>
<td>Excellent interactive experience</td>
</tr>
<tr class="even">
<td>8 concurrent users (batched)</td>
<td>100–200 tok/s per user</td>
<td>Continuous batching amortizes overhead</td>
</tr>
<tr class="odd">
<td>32 concurrent users</td>
<td>30–60 tok/s per user</td>
<td>Still responsive for agentic use</td>
</tr>
<tr class="even">
<td>Prefill (prompt processing)</td>
<td>2,000–5,000 tok/s</td>
<td>Compute-bound; GB300 excels here</td>
</tr>
</tbody>
</table>
<p>For agentic workloads these read better than they look. Agentic traffic is bursty: a short tool call (50–200 tokens), a wait for the tool to run, then the result arrives as a fresh prompt. Much of the time goes to prefill (fast) rather than sustained decode (slow), and the gaps let other users’ requests interleave.</p>
</section>
<section id="serving-software" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="serving-software"><span class="header-section-number">6</span> Serving software</h2>
<section id="inference-engine" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="inference-engine"><span class="header-section-number">6.1</span> Inference engine</h3>
<p><a href="https://github.com/vllm-project/vllm?ref=danmackinlay.name">vLLM</a> and <a href="https://github.com/sgl-project/sglang?ref=danmackinlay.name">SGLang</a> are the two mature open-source engines; both serve Qwen3–235B with MoE-aware parallelism. vLLM is the default. The features that matter here:</p>
<ul>
<li><strong><a href="https://blog.vllm.ai/2025/09/05/anatomy-of-vllm.html?ref=danmackinlay.name">Continuous batching</a></strong> with PagedAttention — multiplexes requests without wasting GPU memory</li>
<li><strong>Chunked prefill</strong> — stops long prompts blocking other users’ decode steps</li>
<li><strong>Automatic prefix caching</strong> — a shared system prompt (likely in a collective) is computed once and reused</li>
<li><strong><a href="https://docs.vllm.ai/en/latest/examples/others/lmcache/?ref=danmackinlay.name">KV offloading</a></strong> via LMCache — the GPU → CPU → disk warm tier above</li>
<li><strong><a href="https://docs.vllm.ai/en/latest/serving/expert_parallel_deployment/?ref=danmackinlay.name">Expert parallelism</a></strong> for MoE</li>
<li><strong>OpenAI-compatible API</strong> — a drop-in replacement for commercial endpoints</li>
</ul>
<p>A launch command for this configuration:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vllm</span> serve QuantTrio/Qwen3-235B-A22B-Instruct-2507-AWQ <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--quantization</span> awq <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tensor-parallel-size</span> 1 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-model-len</span> 32768 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--enable-prefix-caching</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--kv-cache-dtype</span> fp8 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--gpu-memory-utilization</span> 0.95</span></code></pre></div></div>
<p>SGLang earns its place if multi-turn conversation dominates: its <a href="https://lmsys.org/blog/2024-01-17-sglang/?ref=danmackinlay.name">radix-tree KV cache</a> discovers shared prefixes across turns automatically, for <a href="https://www.runpod.io/blog/sglang-vs-vllm-kv-cache?ref=danmackinlay.name">~10% more throughput than vLLM</a> on that workload with no manual tuning.</p>
<p>For DeepSeek V4 specifically, pin vLLM to a known-good commit, not a release tag. There are multiple reports of V4 working and then breaking across vLLM commits (NVIDIA developer forum, May 2026), which sharpens the never-run-<code>:latest</code> discipline rather than adding a new rule.</p>
</section>
<section id="orchestration" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="orchestration"><span class="header-section-number">6.2</span> Orchestration</h3>
<p>For a single box serving a small collective, vLLM or SGLang alone may be enough. If we want production-grade routing, <a href="https://developer.nvidia.com/blog/introducing-nvidia-dynamo-a-low-latency-distributed-inference-framework-for-scaling-reasoning-ai-models/?ref=danmackinlay.name">NVIDIA Dynamo</a> adds <a href="https://docs.nvidia.com/dynamo/latest/user-guides/kv-cache-aware-routing?ref=danmackinlay.name">KV-cache-aware request routing</a> (send a request to the GPU that already holds its context), dynamic batching, and prefill/decode disaggregation, and it’s built for the DGX stack. <a href="https://llm-d.ai/?ref=danmackinlay.name">llm-d</a> is the Kubernetes-native alternative, with SLA-aware load balancing, hierarchical KV offload, and scale-to-zero — more relevant once we run several machines or expose the service over a network.</p>
</section>
<section id="nim-and-portability" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="nim-and-portability"><span class="header-section-number">6.3</span> NIM and portability</h3>
<p>The path of least resistance is <a href="https://docs.nvidia.com/nim/large-language-models/latest/getting-started.html?ref=danmackinlay.name">NVIDIA NIM</a>, which packs the whole stack — engine, caching, API, monitoring — into one container behind an OpenAI-compatible API. Anything that speaks the OpenAI API (LangChain, AutoGen, Claude Code’s API mode) works unchanged. For a collective without a dedicated sysadmin that’s probably the right default; the trade-off is less flexibility than a hand-rolled vLLM stack.</p>
<p>The reason to standardise on NIM goes beyond convenience. NIM and <a href="https://catalog.ngc.nvidia.com/?ref=danmackinlay.name">NGC</a> containers run identically on <a href="https://www.nvidia.com/en-us/data-center/dgx-cloud/?ref=danmackinlay.name">DGX Cloud</a>, the major hyperscalers, and our own DGX Station. Build the workflows against that layer and we can develop on rented cloud GPUs, deploy to our own hardware with no code changes, fail back to cloud if the box dies or demand spikes, and migrate between cloud providers without rewriting anything. This is one of the few places vendor lock-in works in our favour: NVIDIA has every incentive to make its software run everywhere its hardware does.</p>
</section>
</section>
<section id="removing-the-guardrails" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="removing-the-guardrails"><span class="header-section-number">7</span> Removing the guardrails</h2>
<p>The companion post makes the case at a high level; here are the mechanics. Chinese open-weight models ship with CCP-aligned guardrails — hard refusals on Taiwan, Tiananmen, Xinjiang and the rest, plus softer framing biases. Removing them is a solved problem with two tiers of effort.</p>
<section id="abliteration" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="abliteration"><span class="header-section-number">7.1</span> Abliteration</h3>
<p><strong>Tools:</strong> <a href="https://github.com/p-e-w/heretic?ref=danmackinlay.name">Heretic</a> (fully automatic) or <a href="https://github.com/jim-plus/llm-abliteration?ref=danmackinlay.name">llm-abliteration/DECCP</a> (sharded processing for large models).</p>
<p>Abliteration computes the model’s “refusal direction” in its residual stream — by contrasting activations on harmful versus harmless prompts — then orthogonalises the relevant weight matrices against it. This is linear algebra on the static weights, not training. Rent an 8×H100 node (<a href="https://www.runpod.io/?ref=danmackinlay.name">RunPod</a> or <a href="https://lambdalabs.com/?ref=danmackinlay.name">Lambda Labs</a>, ~$20–25 USD/hr), load the model sharded, run the pipeline, save the weights: 2–4 hours, ~$50–100 USD. <a href="https://huggingface.co/blog/grimjim/projected-abliteration?ref=danmackinlay.name">Projected abliteration</a> only removes the mechanistically specific refusal component and preserves more general helpfulness — use it over the vanilla variant.</p>
<p>Refusal rates collapse to near zero. What it doesn’t fix is soft steering: the model will now <em>answer</em> about Taiwan but may still frame the answer with CCP-aligned assumptions. Evaluate before and after against a test set of sensitive prompts in both English and Chinese; the <a href="https://shisa.ai/posts/qwen2-chinese-llm-censorship-analysis/?ref=danmackinlay.name">Shisa.AI Qwen2 censorship analysis</a> is a good taxonomy of affected topics.</p>
</section>
<section id="preference-fine-tuning" class="level3" data-number="7.2">
<h3 data-number="7.2" class="anchored" data-anchor-id="preference-fine-tuning"><span class="header-section-number">7.2</span> Preference fine-tuning</h3>
<p>If abliteration alone isn’t enough — particularly for Chinese-language use, or for neutral framing on geopolitics — go to QLoRA with Direct Preference Optimization <span class="citation" data-cites="Rafailov2023Direct">(Rafailov et al. 2023)</span>. Build 1,000–5,000 preference pairs where the preferred answer is neutral and factual and the dispreferred one is CCP-aligned, across Taiwan, Tiananmen, Xinjiang, Tibet, the South China Sea, Hong Kong, party history, and economic-data reliability. Community datasets exist for smaller Qwen models and adapt to the 235B variant.</p>
<p>Practical configuration:</p>
<ul>
<li>Framework: <a href="https://unsloth.ai/?ref=danmackinlay.name">Unsloth</a> (<a href="https://unsloth.ai/docs/new/faster-moe?ref=danmackinlay.name">Qwen3 MoE fine-tuning</a>, up to 12× speedup)</li>
<li>LoRA rank 16–64 on q, k, v, o, gate, up, down projections</li>
<li><strong>Don’t fine-tune the MoE router</strong> — Unsloth disables this by default; destabilising expert routing causes cascading quality loss</li>
<li>BF16 with LoRA is <a href="https://unsloth.ai/docs/models/qwen3-how-to-run-and-fine-tune?ref=danmackinlay.name">recommended over QLoRA for Qwen3 MoE</a> (4-bit training hurts MoE architectures more)</li>
<li>4–8× H100 80GB with DeepSpeed ZeRO-3, ~20–50 hours</li>
</ul>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Item</th>
<th>Cost (AUD)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>GPU rental (200–400 H100-hours @ $3–4/hr)</td>
<td>$800–$1,600</td>
</tr>
<tr class="even">
<td>Dataset creation (DIY or contracted)</td>
<td>$500–$2,000</td>
</tr>
<tr class="odd">
<td>Evaluation and iteration</td>
<td>$200–$400</td>
</tr>
<tr class="even">
<td><strong>Total</strong></td>
<td><strong>$1,500–$4,000</strong></td>
</tr>
</tbody>
</table>
<p>Merge the LoRA adapters back into the base weights with Unsloth, re-quantize to AWQ 4-bit, and the merged model serves at full speed — zero adapter overhead at inference.</p>
<p>A full fine-tune ($5,000–$30,000+) only makes sense if we’re also doing domain adaptation (Australian legal, medical, government) and want to fold de-censoring into a larger run. Overkill for guardrail removal alone.</p>
</section>
<section id="newer-models-lag-the-tooling" class="level3" data-number="7.3">
<h3 data-number="7.3" class="anchored" data-anchor-id="newer-models-lag-the-tooling"><span class="header-section-number">7.3</span> Newer models lag the tooling</h3>
<p>De-censoring maturity trails the architecture. As of May 2026 the <a href="https://github.com/p-e-w/heretic?ref=danmackinlay.name">Heretic issue for V4-Flash</a> (#310) is open and unresolved; a community-abliterated V4-Flash exists only as a GGUF for llama.cpp, so a vLLM-servable FP8 de-censored build is still work, not a download. The position itself is unchanged whichever model we pick: open weights settle whether we’re <em>allowed</em> to run the model, not what guardrails were baked in at the alignment stage. V4 just changes which model sits under the knife.</p>
</section>
</section>
<section id="other-candidates" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="other-candidates"><span class="header-section-number">8</span> Other candidates</h2>
<p>Qwen3–235B is the current sweet spot, but it’s not the only model that fits, and the field moves fast.</p>
<p><strong>DeepSeek V4</strong> comes in two open-weight variants. <em>V4-Flash</em> (284B total / 13B active) fits the box and enters the bake-off as a same-compute-class candidate, with stronger published agentic numbers, native vLLM <code>deepseek_v4</code> support, and the DSA sparse attention that does so much for concurrency. Not an automatic swap for Qwen, but a serious third option. <em>V4-Pro</em> (1.6T / 49B active) does not fit — ~648 GB even at Q6 — so its role is the audition question: here’s the best open model money can rent, is the gap to the one we own acceptable?</p>
<p><strong><a href="https://artificialanalysis.ai/models/kimi-k2?ref=danmackinlay.name">Kimi K2</a></strong> (1T total / 32B active) is agentic-tuned and competitive with Claude Sonnet 4 and GPT-5.x on tool-calling and coding. It only fits with aggressive CPU offload (~500 GB at 4-bit, into the 784 GB unified memory), which drops single-user decode to 30–80 tok/s — usable for latency-tolerant agentic work, not ideal — and its de-censoring tooling lags.</p>
<p><strong><a href="https://ai.meta.com/blog/llama-4-multimodal-intelligence/?ref=danmackinlay.name">Meta’s Llama 4</a></strong> carries Western rather than CCP guardrails, so it skips the de-censoring step entirely. The largest open Llamas (including the 400B Maverick MoE, 17B active) don’t quite match Qwen3–235B on benchmarks as of early 2026, but that’s the lower-friction option if de-censoring is more hassle than we want.</p>
<p><strong><a href="https://huggingface.co/XiaomiMiMo/MiMo-V2.5?ref=danmackinlay.name">Xiaomi MiMo-V2.5</a></strong> (310B total / 15B active) is the April 2026 entrant that maps almost exactly onto the Qwen baseline, only leaner per token. At AWQ 4-bit the weights land around 160 GB — inside the 252 GB HBM with room left for KV cache — and 15B active against Qwen’s 22B means decode runs a little faster. It also brings its own KV relief: its hybrid sliding-window attention caps the cost of long conversations the way DeepSeek’s DSA does, by an older and better-supported route — which matters most on this box, where long-context KV is the binding constraint. It is natively omnimodal (text, image, video, and audio in one model) with a 1M-token context — the multimodal coverage Qwen3–235B simply does not have. The non-Pro size is the one I would audition: <a href="https://news.ycombinator.com/item?id=48446639&amp;ref=danmackinlay.name">some users report it tool-calls better than the much larger Pro</a> at a fraction of the cost, which is the trait a collective running agentic workloads actually wants. It is a Chinese open-weight model, so it carries the same CCP-aligned guardrails as Qwen, and the de-censoring section applies unchanged. The weights ship under a plain MIT licence — commercial use, redistribution, and fine-tuning explicitly allowed with no authorization — so the abliteration and preference-tuning described there are unambiguously within licence, with none of the use-restriction strings a community licence carries. The usual caveat still holds: check whether the abliteration tooling has caught up to its architecture before counting on it.</p>
<p>Its big sibling <strong><a href="https://huggingface.co/XiaomiMiMo/MiMo-V2.5-Pro?ref=danmackinlay.name">MiMo-V2.5-Pro</a></strong> (1.02T total / 42B active) plays the role V4-Pro and Kimi K2 play: it does not fit in HBM — ~510 GB at 4-bit, spilling into the 784 GB unified memory with the same offload penalty Kimi pays — so its job is the audition yardstick, the best open model we can rent measured against the one we own. Xiaomi serve it hosted behind an OpenAI-compatible API (also on <a href="https://openrouter.ai/xiaomi/mimo-v2.5-pro?ref=danmackinlay.name">OpenRouter</a>), and a <a href="https://mimo.xiaomi.com/blog/mimo-tilert-1000tps?ref=danmackinlay.name">TileRT build pushes it past 1000 tok/s</a> — a useful number to beat when deciding whether owning still beats renting.</p>
<p>A hybrid is also on the table: a small fast model (<a href="https://huggingface.co/collections/deepseek-ai/deepseek-r1-678e1e131c0169c0bc89728d?ref=danmackinlay.name">DeepSeek-R1-Distill-Qwen-32B</a> or similar) handles routine queries, the big model is reserved for hard reasoning, and aggregate throughput rises at no extra hardware cost.</p>
</section>
<section id="small-gpu" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="small-gpu"><span class="header-section-number">9</span> When smaller hardware makes sense</h2>
<p>The DGX Station above is the sweet spot for a 25–50 person collective with serious agentic and reasoning workloads. Several adjacent shapes have different answers, and the model picks change at each tier.</p>
<section id="the-small-collective-1015-people" class="level3" data-number="9.1">
<h3 data-number="9.1" class="anchored" data-anchor-id="the-small-collective-1015-people"><span class="header-section-number">9.1</span> The small collective (10–15 people)</h3>
<p>A $160k box does not pay back at 10 members. The lower tier is <strong>a single H100 80 GB</strong> on a workstation chassis — Lambda Tensorbook, MSI WS-class, or a custom build with one Hopper PCIe card. Hardware spend lands at roughly $30k–$45k AUD all-in versus $135k–$195k for the DGX.</p>
<p>Models that fit comfortably at AWQ 4-bit on 80 GB:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Model</th>
<th>~Weights</th>
<th>Indication</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://huggingface.co/Qwen/Qwen3.5-35B-A3B?ref=danmackinlay.name">Qwen3.5-35B-A3B</a></td>
<td>~18 GB</td>
<td>MoE daily driver, 3B active. Quality close enough to Qwen3-235B for most everyday use; throughput much higher because only 3B fire per token.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/NousResearch/Hermes-4.3-36B?ref=danmackinlay.name">Hermes-4.3-36B</a></td>
<td>~20 GB</td>
<td>Agentic-tuned Nous release, December 2025. The right pick if the workload is tool-using rather than pure reasoning.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B?ref=danmackinlay.name">DeepSeek-R1-Distill-Qwen-32B</a></td>
<td>~19 GB</td>
<td>Math-and-reasoning distill. Single user can run alongside one of the above on the same H100.</td>
</tr>
</tbody>
</table>
<p>Concurrency budget on an 80 GB H100: about 60 GB of KV-cache headroom, so roughly 12 users at 32K context or ~3 users at 128K — below the DGX figures, but a useful sanity check for a small group. The proposition is roughly “near-frontier quality at 25% of the hardware cost” — appropriate where sovereign compute is a hedge rather than a primary work tool.</p>
</section>
<section id="the-consumer-gpu-pilot-510-people-or-proof-of-concept" class="level3" data-number="9.2">
<h3 data-number="9.2" class="anchored" data-anchor-id="the-consumer-gpu-pilot-510-people-or-proof-of-concept"><span class="header-section-number">9.2</span> The consumer-GPU pilot (5–10 people, or proof of concept)</h3>
<p>Below the H100 tier, <strong>a single RTX 4090 24 GB or 5090 32 GB</strong> in a workstation is the entry point. Hardware spend ~$5k–$8k AUD all-in.</p>
<p>At 24 GB the practical model list shrinks to 14B-class at 4-bit:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Model</th>
<th>~Weights</th>
<th>Indication</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://huggingface.co/NousResearch/Hermes-4-14B?ref=danmackinlay.name">Hermes-4-14B</a></td>
<td>~8 GB</td>
<td>Agentic, function-calling. The smaller-tier baseline.</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-0528-Qwen3-8B?ref=danmackinlay.name">DeepSeek-R1-0528-Qwen3-8B</a></td>
<td>~5 GB</td>
<td>Math champion of its weight class — AIME-2024 86%, matches Qwen3-235B-thinking on math benchmarks.</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/microsoft/Phi-4-reasoning-plus?ref=danmackinlay.name">Phi-4-Reasoning-Plus-14B</a></td>
<td>~8 GB</td>
<td>Different reasoning style; useful alongside the DeepSeek distill.</td>
</tr>
</tbody>
</table>
<p>The use case is a pilot — small group, exploratory workload, willingness to live with smaller models — or a permanent setup for groups whose work is dominated by short-context interactive use. Concurrency falls hard at 24 GB (~3 users at 32K), so this tier rewards short sessions and a retrieval layer over brute-force long context.</p>
</section>
<section id="fast-path-slow-path-hybrid" class="level3" data-number="9.3">
<h3 data-number="9.3" class="anchored" data-anchor-id="fast-path-slow-path-hybrid"><span class="header-section-number">9.3</span> Fast-path / slow-path hybrid</h3>
<p>The hybrid pattern mentioned in Other candidates is more useful than its placement suggests. The shape: run two models in parallel.</p>
<ul>
<li><strong>Fast path</strong> on a small GPU: Qwen3.5-35B-A3B or <a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-0528-Qwen3-8B?ref=danmackinlay.name">DeepSeek-R1-0528-Qwen3-8B</a> for routine chat, code completion, simple tool calls. Sub-second latency, low cost per query.</li>
<li><strong>Slow path</strong> on the big GPU: Qwen3-235B or V4-Flash for hard reasoning, long-context agentic, multi-step research.</li>
</ul>
<p>A router — a small classifier model, or an explicit member choice via <code>/model</code> in the harness — sends each request to the appropriate backend. For agentic workloads where most steps are simple but a few need frontier-class reasoning, this is materially cheaper than running everything on the big model.</p>
<p>The pattern lets a collective grow from a single small box to a heterogeneous fleet without throwing away earlier investment — the H100 station that pilots phase 1 becomes the fast path in phase 3.</p>
</section>
<section id="per-member-workstations-as-fallback" class="level3" data-number="9.4">
<h3 data-number="9.4" class="anchored" data-anchor-id="per-member-workstations-as-fallback"><span class="header-section-number">9.4</span> Per-member workstations as fallback</h3>
<p>A natural shape for resilient infrastructure: each member’s own machine runs a small model as fallback when the colo endpoint is unavailable. Their harness (<a href="../notebook/local_llm_mac.html#pi">pi</a>, Hermes, Aider) auto-fails over from the collective backend to a local <a href="../notebook/local_llm_mac.html#ollama">Ollama</a> or <a href="../notebook/local_llm_mac.html#osaurus">Osaurus</a> endpoint. Smaller and slower, but it works on the day the cables are cut.</p>
<p>For Mac-equipped members, <a href="../notebook/local_llm_mac.html#models">running LLMs locally on a Mac</a> covers the per-member setup. The model picks overlap with this list — Hermes-4-14B and DeepSeek-R1-0528-Qwen3-8B are the obvious shared-tier choices.</p>
</section>
<section id="when-small-is-wrong" class="level3" data-number="9.5">
<h3 data-number="9.5" class="anchored" data-anchor-id="when-small-is-wrong"><span class="header-section-number">9.5</span> When small is wrong</h3>
<p>Three workloads make a single H100 painful, even with the right model picks:</p>
<ul>
<li><strong>Long-context agentic flows</strong> (&gt;128K context routinely). KV-cache pressure builds fast on 80 GB. Either disaggregate (small for short-context, big for long) or skip the H100 tier.</li>
<li><strong>Many simultaneous users</strong> (&gt;15 concurrent active sessions). The DGX’s 252 GB HBM is the difference, and there is no cheap substitute.</li>
<li><strong>Frontier-quality coding tasks</strong>. Qwen3-35B does not match Qwen3-235B on hard SWE-bench-style problems; a small box does not paper over that.</li>
</ul>
<p>If any of these dominates the workload, the small-hardware path is a stepping stone to the DGX tier, not the destination.</p>
</section>
</section>
<section id="auditioning-on-rented-hardware" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="auditioning-on-rented-hardware"><span class="header-section-number">10</span> Auditioning on rented hardware</h2>
<p>Test the full stack on rented compute before committing to a box.</p>
<p><strong>Phase 1 — quick test</strong> (1–2 days, ~$100–$200 AUD). Rent a single H100 80GB on <a href="https://www.runpod.io/?ref=danmackinlay.name">RunPod</a> (<a href="https://intuitionlabs.ai/articles/h100-rental-prices-cloud-comparison?ref=danmackinlay.name">$2–3 USD/hr</a>), deploy the smaller Qwen3–30B-A3B with vLLM, and check our actual workloads — agentic tool-calling, document processing, code — against the API.</p>
<p><strong>Phase 2 — full-scale test</strong> (1–2 weeks, ~$500–$1,000 AUD). Rent 4–8× H100s, deploy Qwen3–235B at AWQ 4-bit, run abliteration on it, then simulate 5–15 concurrent users with realistic agentic load and <em>measure</em> throughput, latency, and KV pressure. This is where the order-of-magnitude figures above get replaced with numbers for our own workload.</p>
<p><strong>Phase 3 — DGX Cloud validation</strong> (optional, ~$1,000–$2,000 AUD). Run the <a href="https://www.nvidia.com/en-us/data-center/dgx-cloud/?ref=danmackinlay.name">DGX Cloud</a> / NIM stack via a hyperscaler marketplace so the migration to physical hardware is seamless.</p>
<p><strong>Phase 4 — buy and deploy.</strong> Order through an Australian vendor (lead times are months, not weeks), finalise the de-censored weights on cloud while waiting, then on arrival transfer weights, stand up vLLM or NIM, configure access control, monitor for a week, and open it to the collective.</p>
</section>
<section id="what-it-costs" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="what-it-costs"><span class="header-section-number">11</span> What it costs</h2>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Item</th>
<th>One-time cost (AUD)</th>
<th>Ongoing monthly (AUD)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>DGX Station GB300</td>
<td>$135,000–$195,000</td>
<td>—</td>
</tr>
<tr class="even">
<td>UPS + electrical work</td>
<td>$2,000–$5,000</td>
<td>—</td>
</tr>
<tr class="odd">
<td>De-censoring (abliteration + DPO)</td>
<td>$1,500–$4,000</td>
<td>—</td>
</tr>
<tr class="even">
<td>Cloud audition (phases 1–3)</td>
<td>$800–$3,200</td>
<td>—</td>
</tr>
<tr class="odd">
<td><strong>Total one-time</strong></td>
<td><strong>~$140,000–$207,000</strong></td>
<td>—</td>
</tr>
<tr class="even">
<td>Electricity (1600W @ 24/7)</td>
<td>—</td>
<td>~$350</td>
</tr>
<tr class="odd">
<td>Hardware amortization (3yr)</td>
<td>—</td>
<td>~$4,500</td>
</tr>
<tr class="even">
<td>Internet (business NBN or colo, see risks)</td>
<td>—</td>
<td>$150–$1,000</td>
</tr>
<tr class="odd">
<td><strong>Total monthly</strong></td>
<td>—</td>
<td><strong>~$5,000–$5,900</strong></td>
</tr>
<tr class="even">
<td><strong>Per member/month (50 members)</strong></td>
<td>—</td>
<td><strong>~$100–$118</strong></td>
</tr>
</tbody>
</table>
<p>Amortised over three years, the one-time cost adds roughly $55–$75/month per member in a 50-person collective, for a total cost of ownership of <strong>$155–$193/month per member</strong> depending mostly on whether we host at home or in a colo. That’s competitive with commercial API access for serious users — with no rate limits, no per-query metering, and full data sovereignty.</p>
</section>
<section id="risks" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="risks"><span class="header-section-number">12</span> Risks</h2>
<section id="hardware-failure" class="level3" data-number="12.1">
<h3 data-number="12.1" class="anchored" data-anchor-id="hardware-failure"><span class="header-section-number">12.1</span> Hardware failure</h3>
<p>One box is a single point of failure. NVIDIA enterprise support through partners like <a href="https://xenon.com.au/product/nvidia-dgx-station/?ref=danmackinlay.name">XENON</a> buys next-business-day replacement for $10,000–$20,000/year; failing that, a collective on NIM can fail back to cloud inference seamlessly during downtime. In a serious geopolitical crisis the supply chain might be disrupted indefinitely and support might not deliver, so a spare is worth buying.</p>
</section>
<section id="model-obsolescence" class="level3" data-number="12.2">
<h3 data-number="12.2" class="anchored" data-anchor-id="model-obsolescence"><span class="header-section-number">12.2</span> Model obsolescence</h3>
<p>A frontier-class model today won’t be in 18 months. The hardware doesn’t share that fate — it runs whatever future models fit in its memory, and 784 GB of unified memory is generous enough to stay relevant across several generations of open weights.</p>
</section>
<section id="network" class="level3" data-number="12.3">
<h3 data-number="12.3" class="anchored" data-anchor-id="network"><span class="header-section-number">12.3</span> Network</h3>
<p>Australian residential broadband — older HFC and FTTN NBN especially — isn’t reliable enough for a production service: multiple brief outages a day, asymmetric upload (often 20–40 Mbps). Token generation needs only a few KB/s per user, so the problem is connection stability, not throughput. In ascending order of cost and reliability:</p>
<ul>
<li><strong>Residential NBN + 5G failover</strong> (~$150/month): a dual-WAN router rides out most brief outages. Good enough for a tolerant collective.</li>
<li><strong>Business-grade NBN</strong> (<a href="https://www.aussiebroadband.com.au/business/?ref=danmackinlay.name">ABB Business</a>, <a href="https://www.superloop.com/business/?ref=danmackinlay.name">Superloop Business</a>, ~$150–$250/month): better SLA, static IP, priority faults, still on shared infrastructure.</li>
<li><strong>Quarter-rack colocation</strong> (~$500–$1,000/month at <a href="https://www.equinix.com.au/data-centers/asia-pacific-colocation/australia-colocation/sydney-data-centers?ref=danmackinlay.name">Equinix SY1–SY5</a> or <a href="https://www.nextdc.com/data-centres/s2-sydney-data-centre?ref=danmackinlay.name">NEXTDC S2</a>): redundant fibre, generator backup, physical security. Loses the under-a-desk feel, gains dependable uptime.</li>
</ul>
<p>The right choice depends on how many members are remote versus local, and how tolerant the collective is of downtime mid-agentic-workflow.</p>
</section>
<section id="operations" class="level3" data-number="12.4">
<h3 data-number="12.4" class="anchored" data-anchor-id="operations"><span class="header-section-number">12.4</span> Operations</h3>
<p>Someone has to administer this. NIM keeps it at “run a Docker container”, updates pull from NGC, and access control is a standard reverse proxy (nginx + OAuth2). Weekend-sysadmin complexity, not a full-time ops team.</p>
</section>
<section id="legal-and-regulatory" class="level3" data-number="12.5">
<h3 data-number="12.5" class="anchored" data-anchor-id="legal-and-regulatory"><span class="header-section-number">12.5</span> Legal and regulatory</h3>
<p>Running modified models for a collective may carry obligations as Australian AI regulation develops. Stay informed, take part in consultation, and document a governance framework. A legal entity (next section) is what holds the asset and caps liability.</p>
</section>
</section>
<section id="legal-structure" class="level2" data-number="13">
<h2 data-number="13" class="anchored" data-anchor-id="legal-structure"><span class="header-section-number">13</span> Legal structure</h2>
<p>A compute collective needs a legal entity that can own a $160k asset, collect monthly contributions from members, enter contracts (hosting, internet, support), and limit personal liability. Australian law offers several options. None is perfect; here’s how they compare for this specific use case.</p>
<section id="legal-coop" class="level3" data-number="13.1">
<h3 data-number="13.1" class="anchored" data-anchor-id="legal-coop"><span class="header-section-number">13.1</span> Option 1: Cooperative under the Co-operatives National Law</h3>
<p>A <a href="https://business.gov.au/planning/business-structures-and-types/business-structures/co-operative?ref=danmackinlay.name">cooperative</a> is purpose-built for exactly this kind of thing: a group of people who jointly own and operate infrastructure for their mutual benefit. Under the <a href="https://www.nsw.gov.au/business-and-economy/co-operatives/about-co-operatives/co-operatives-national-law?ref=danmackinlay.name">Co-operatives National Law</a> (CNL), now harmonised across most states and territories, a cooperative:</p>
<ul>
<li>Requires a <a href="https://www.nsw.gov.au/business-and-economy/co-operatives/running-a-co-operative/co-operative-membership-rights-and-responsibilities?ref=danmackinlay.name">minimum of 5 active members</a> (our 25–50 is well above this)</li>
<li>Operates on one-member-one-vote, regardless of contribution level</li>
<li>Can hold assets, enter contracts, and collect member contributions</li>
<li>Can issue shares (members buy in) and pay limited returns on them</li>
<li>Requires members to be “active” — i.e.&nbsp;actually using the co-op’s services, which is exactly what we want</li>
<li>Cannot distribute profits to members beyond the limited share return, but can reinvest in better hardware, more capacity, etc.</li>
</ul>
<p>The <a href="https://fed.coop/co-operatives-in-australia-a-manual/part-one-understanding-co-operatives/legislation/?ref=danmackinlay.name">Co-op Federation</a> publishes a comprehensive manual on formation and governance. Registration is through the state registrar (e.g.&nbsp;<a href="https://www.nsw.gov.au/business-and-economy/co-operatives/about-co-operatives?ref=danmackinlay.name">NSW Fair Trading</a>).</p>
<p><strong>Pros:</strong> Philosophically the best fit — the legal form matches the actual relationship (members own infrastructure they use collectively). National law means consistent rules across states. Members have clear rights.</p>
<p><strong>Cons:</strong> More regulatory overhead than an incorporated association. Requires a formal formation meeting, a disclosure statement, and rules that comply with the CNL’s <a href="https://www.nsw.gov.au/business-and-economy/co-operatives/running-a-co-operative/co-operative-rules?ref=danmackinlay.name">model rules</a>. Annual reporting to the state registrar. If the collective is small and informal, this may feel heavy.</p>
<p><strong>Estimated setup cost:</strong> $1,000–$3,000 (registration fees + legal advice on rules).</p>
</section>
<section id="legal-incorp" class="level3" data-number="13.2">
<h3 data-number="13.2" class="anchored" data-anchor-id="legal-incorp"><span class="header-section-number">13.2</span> Option 2: Incorporated association</h3>
<p>An <a href="https://www.nsw.gov.au/business-and-economy/incorporated-associations/about-incorporated-associations?ref=danmackinlay.name">incorporated association</a> is the simplest and cheapest legal structure for a small not-for-profit group in Australia. Registration is at the state or territory level (<a href="https://www.nsw.gov.au/business-and-economy/incorporated-associations/running-an-incorporated-association?ref=danmackinlay.name">~$57/year in NSW</a>, similar in other states).</p>
<ul>
<li>Can hold assets, enter contracts, sue and be sued</li>
<li>Members have limited liability (capped at membership fee)</li>
<li>Cannot distribute profits or assets to members</li>
<li>Restricted to operating primarily in the home state (interstate operations <a href="https://www.hallpayne.com.au/blog/2023/april/nfp-business-structure/?ref=danmackinlay.name">may require ASIC registration</a> as a Registered Australian Body)</li>
<li>NSW imposes a <a href="https://www.nsw.gov.au/business-and-economy/incorporated-associations/about-incorporated-associations?ref=danmackinlay.name">$2M gross revenue/assets threshold</a> for registration as an association; our asset value is well under this</li>
</ul>
<p><strong>Pros:</strong> Cheapest, simplest to set up. Low annual compliance. Familiar form — thousands of community groups use this. Model constitutions available from state regulators.</p>
<p><strong>Cons:</strong> No share structure, so the “buy-in” mechanism is less natural — member contributions would be fees, not equity. Single-state restriction could matter if the collective spans Sydney and Melbourne. The form is designed for community groups and sporting clubs, not infrastructure-owning collectives; some state registrars <a href="https://betterboards.net/non-profit-fact-sheets/incorporated-association/?ref=danmackinlay.name">may query</a> whether a compute collective fits their intended scope.</p>
<p><strong>Estimated setup cost:</strong> $200–$500.</p>
</section>
<section id="legal-clg" class="level3" data-number="13.3">
<h3 data-number="13.3" class="anchored" data-anchor-id="legal-clg"><span class="header-section-number">13.3</span> Option 3: Company limited by guarantee (CLG)</h3>
<p>A <a href="https://betterboards.net/non-profit-fact-sheets/company-limited-by-guarantee/?ref=danmackinlay.name">company limited by guarantee</a> is a federal structure registered with ASIC. Members guarantee a small amount (often $10–$100) in the event of winding up, rather than holding shares.</p>
<ul>
<li>Can operate nationally without interstate registration issues</li>
<li><a href="https://boanco.com.au/2024/09/06/company-limited-by-guarantee-vs-incorporated-association-whats-best-for-your-not-for-profit/?ref=danmackinlay.name">Higher annual fees</a> (~$1,267/year to ASIC vs ~$57 for an association)</li>
<li>Subject to more rigorous <a href="https://www.mullinslawyers.com.au/resources/making-the-switch-part-1-pros-and-cons-of-a-company-limited-by-guarantee/?ref=danmackinlay.name">ASIC reporting requirements</a></li>
<li>Can apply for <a href="https://www.acnc.gov.au/tools/guides/registration-application-checklist-and-guide?ref=danmackinlay.name">ACNC registration</a> as a charity if the collective has a genuine charitable purpose</li>
</ul>
<p><strong>Pros:</strong> National scope. More credible structure if the collective grows or seeks grants. Clear governance framework under the Corporations Act.</p>
<p><strong>Cons:</strong> Overkill for a 50-person collective. Higher compliance cost. The guarantee structure doesn’t naturally map to “members buy shares in shared infrastructure.”</p>
<p><strong>Estimated setup cost:</strong> $1,500–$4,000 (ASIC fees + legal advice on constitution).</p>
</section>
<section id="what-about-acnc-registration-and-dgr-status" class="level3" data-number="13.4">
<h3 data-number="13.4" class="anchored" data-anchor-id="what-about-acnc-registration-and-dgr-status"><span class="header-section-number">13.4</span> What about ACNC registration and DGR status?</h3>
<p>Probably not. <a href="https://www.acnc.gov.au/for-charities/start-charity/you-start-charity/why-register?ref=danmackinlay.name">ACNC registration</a> requires a charitable purpose, and “a group of professionals sharing AI compute” isn’t obviously charitable. If the collective had an explicit community education or digital inclusion mission — e.g.&nbsp;providing AI access to under-resourced community organisations — ACNC registration might be possible, but it would constrain the collective’s operations significantly. <a href="https://www.ato.gov.au/businesses-and-organisations/not-for-profit-organisations/getting-started/getting-endorsed-for-tax-concessions-or-as-a-dgr/is-my-organisation-eligible-for-dgr-endorsement/rules-and-tests-for-dgr-endorsement/dgrs-required-to-be-a-registered-charity?ref=danmackinlay.name">DGR endorsement</a> (tax-deductible donations) is even harder to obtain. Let’s not plan around it.</p>
</section>
<section id="recommendation" class="level3" data-number="13.5">
<h3 data-number="13.5" class="anchored" data-anchor-id="recommendation"><span class="header-section-number">13.5</span> Recommendation</h3>
<p>For a compute collective of 25–50 people in a single city: <strong>start as an incorporated association</strong> for speed and simplicity, with an explicit plan to convert to a cooperative under the CNL if the model proves viable and the collective wants a more natural ownership structure. The <a href="https://www.minterellison.com/articles/structuring-for-nfp-success?ref=danmackinlay.name">conversion process</a> is well-documented and doesn’t require dissolving the original entity.</p>
<p>If the collective spans multiple states from the start, or if members want a share-based buy-in from day one, go straight to a cooperative.</p>
<p>Either way, let’s get a solicitor to review the constitution/rules before committing $160k of members’ money to a hardware purchase. Budget $2,000–$5,000 for initial legal advice — cheap insurance on a six-figure asset.</p>
</section>
</section>
<section id="next-steps" class="level2" data-number="14">
<h2 data-number="14" class="anchored" data-anchor-id="next-steps"><span class="header-section-number">14</span> Next steps</h2>
<p>If we’re interested in forming or joining a compute collective, the immediate actions are:</p>
<ol type="1">
<li><strong>Gauge interest</strong>: find 10–20 people who would commit to ~$150/month for sovereign AI access</li>
<li><strong>Run a cloud pilot</strong>: $200 buys a weekend of testing on rented GPUs</li>
<li><strong>Choose a legal structure</strong>: start as an incorporated association, convert to a cooperative if the model works (see legal structure section above)</li>
<li><strong>Order hardware</strong>: current lead times for DGX Station are “months, not weeks” — starting the order process early is important</li>
<li><strong>Document everything</strong>: the playbook we write becomes the replication kit for the next collective</li>
</ol>
<p>The <a href="../notebook/aus_sovereign_llm.html">companion post</a> makes the case for why. This post, I hope, makes the case for <em>how</em>.</p>
</section>
<section id="references" class="level2" data-number="15">
<h2 data-number="15" class="anchored" data-anchor-id="references"><span class="header-section-number">15</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Rafailov2023Direct" class="csl-entry">
Rafailov, Sharma, Mitchell, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2305.18290">Direct Preference Optimization: Your Language Model Is Secretly a Reward Model</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>community project</category>
  <category>cooperation</category>
  <category>DIY</category>
  <category>engineering</category>
  <category>faster pussycat</category>
  <category>institutions</category>
  <category>resilient tech</category>
  <category>slop</category>
  <category>straya</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/aus_sovereign_llm_technical.html</guid>
  <pubDate>Sun, 22 Mar 2026 01:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/colossal_elephant_section.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Hacking financial regulation for community mutual aid</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/neo_friendly_societies_technical.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-maggior-consiglio" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-maggior-consiglio-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/maggior_consiglio_RP-P-2018-1237.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/maggior_consiglio_RP-P-2018-1237.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-maggior-consiglio-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>This is a companion to <a href="../notebook/neo_friendly_societies.html">Who wants to found a friendly society?</a>, where I sketch the pitch for a neo-friendly society—a small mutual that invests in counter-cyclical assets as a hedge against state weakness. That post is the <em>why</em>. This one is the <em>how</em>, or the <em>can we even?</em></p>
<p><a href="https://woozy-page-39c.notion.site/32d398af2ab0805293a9dfb1561b4bdf?pvs=105&amp;ref=danmackinlay.name">Sign up for updates</a> if you’d like to know that answer.</p>
<p>The question: if 50 of us wanted to pool money, invest it in ETFs and catastrophe bonds, and provide mutual aid to members in hard times—what legal structure would we use in Australia, and could AI agents cut the compliance costs enough to make it viable?</p>
<p>Sounds interesting? <a href="../contact.html">Get in touch</a>.</p>
<div class="callout callout-style-simple callout-none no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>I set Claude to work researching this. It is a start. I have stopped here, with some directions for further research, and one worked-out war-game scenario where we go maximally ambitious.</p>
</div>
</div>
</div>
<section id="what-are-we-in-australian-law" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="what-are-we-in-australian-law"><span class="header-section-number">1</span> What are we in Australian law?</h2>
<p>This is the hard part. There are several possible legal structures, each with different trade-offs. I’ll try to sketch the landscape; this is emphatically not legal advice.</p>
<section id="option-1-apra-registered-friendly-society" class="level3" data-number="1.1">
<h3 data-number="1.1" class="anchored" data-anchor-id="option-1-apra-registered-friendly-society"><span class="header-section-number">1.1</span> Option 1: APRA-registered friendly society</h3>
<p>The purest path. In Australia, friendly societies are regulated under the <a href="https://www.apra.gov.au/legislation-for-life-insurers-and-friendly-societies?ref=danmackinlay.name">Life Insurance Act 1995</a> by <a href="https://www.apra.gov.au/life-insurance-and-friendly-societies?ref=danmackinlay.name">APRA</a>. They operate using a <a href="https://www.apra.gov.au/friendly-society-benefit-funds?ref=danmackinlay.name">“benefit fund” structure</a>—members’ contributions go into a fund that provides defined benefits.</p>
<p>The problems are severe for a small group:</p>
<ul>
<li>We’d have to be a <strong>corporation</strong> (APRA can’t register partnerships or unincorporated entities).</li>
<li>Even <em>using the words</em> “friendly society” in relation to a financial business <a href="https://www.apra.gov.au/licensing-guidelines-for-life-insurers-and-friendly-societies?ref=danmackinlay.name">requires APRA consent</a> under s16E of the Life Insurance Act.</li>
<li>The <a href="https://www.apra.gov.au/life-insurance-and-friendly-society-reporting-requirements?ref=danmackinlay.name">Financial Accountability Regime (FAR)</a>, which applies from March 2025, adds accountability, key personnel, and deferred remuneration obligations.</li>
<li>APRA compliance is designed for institutions with millions in assets and dedicated compliance teams. It is spectacularly inappropriate for 30 friends with a spreadsheet.</li>
</ul>
<p><strong>Verdict:</strong> Almost certainly not the right path for a small start-up mutual. But worth understanding, because the wargaming section below asks: <em>what if we tried anyway?</em></p>
</section>
<section id="option-2-co-operative-under-co-operatives-national-law" class="level3" data-number="1.2">
<h3 data-number="1.2" class="anchored" data-anchor-id="option-2-co-operative-under-co-operatives-national-law"><span class="header-section-number">1.2</span> Option 2: Co-operative under Co-operatives National Law</h3>
<p>Australia has harmonized <a href="https://www.nsw.gov.au/business-and-economy/co-operatives/about-co-operatives/co-operatives-national-law?ref=danmackinlay.name">Co-operatives National Law (CNL)</a>, adopted by all states and territories between 2012 and 2020. About 1,400 of Australia’s ~1,800 co-ops and mutuals are registered under CNL.</p>
<p>A co-op could collect member contributions and invest them, potentially through a managed fund or ETFs. The <a href="https://bccm.coop/?ref=danmackinlay.name">Business Council of Co-operatives and Mutuals (BCCM)</a> is a good resource here.</p>
<p>Key features:</p>
<ul>
<li>Democratic governance (one member, one vote) is built in—though notably the CNL is flexible about <em>how</em> that vote is conducted. The model rules are not compulsory; our co-op’s rules could specify preferential voting, STV, approval voting for board elections, or even sortition for committees and advisory panels. We can’t replace the statutory board election with pure sortition, but there’s wide scope for experimental governance below that level.</li>
<li>The <a href="https://www.legislation.gov.au/Details/C2019A00037?ref=danmackinlay.name">Treasury Laws Amendment (Mutual Reforms) Act 2019</a> introduced <strong>Mutual Capital Instruments (MCIs)</strong> — a bespoke share class for mutual entities that lets them raise capital without demutualizing. MCIs require the entity to be a public company, though, so this is more relevant if the model scales up.</li>
<li>Registration is with the state Registrar of Co-operatives; costs and complexity are much lower than APRA.</li>
</ul>
<p><strong>Catch:</strong> If the co-op pools member funds for investment, it might constitute a <a href="https://www.asic.gov.au/regulatory-resources/managed-funds/managed-investment-schemes/?ref=danmackinlay.name">managed investment scheme under the Corporations Act</a>. That triggers ASIC regulation—the responsible entity must be an Australian public company holding an AFSL. Heavy.</p>
</section>
<section id="option-3-small-scale-unregistered-managed-investment-scheme" class="level3" data-number="1.3">
<h3 data-number="1.3" class="anchored" data-anchor-id="option-3-small-scale-unregistered-managed-investment-scheme"><span class="header-section-number">1.3</span> Option 3: Small-scale unregistered managed investment scheme</h3>
<p>Here’s where it gets interesting for small groups. ASIC’s rules have a <a href="https://afslhouse.com.au/insights/do-you-need-an-afsl-to-offer-a-managed-investment-scheme-understanding-what-it-means/?ref=danmackinlay.name"><strong>“20/12 rule”</strong></a>: a managed investment scheme doesn’t need to be registered if it has <strong>20 or fewer members</strong> (in any 12-month period) and raises <strong>no more than $2 million</strong>. Alternatively, schemes open only to <strong>wholesale clients</strong> are exempt from registration.</p>
<p>An unregistered scheme still needs an operator with an AFS licence, generally. But the compliance burden is dramatically lower than a registered scheme. For a group of, say, 20 friends each contributing $200/month into a shared portfolio of ETFs, this <em>might</em> be workable.</p>
<p>This is probably the most promising path for the investment function specifically. But it caps group size at 20, which is tight.</p>
<p><strong>Research direction:</strong> How do the wholesale client tests work in practice for this kind of structure? The <a href="https://lawcouncil.au/?ref=danmackinlay.name">Law Council of Australia</a> has submissions on reforming wholesale investor tests. Could members qualify as wholesale clients if they individually meet the net assets or income tests?</p>
</section>
<section id="option-4-incorporated-association-individual-investments" class="level3" data-number="1.4">
<h3 data-number="1.4" class="anchored" data-anchor-id="option-4-incorporated-association-individual-investments"><span class="header-section-number">1.4</span> Option 4: Incorporated association + individual investments</h3>
<p>The lightest-touch option: form an <a href="https://www.acnc.gov.au/tools/topic-guides/incorporated-associations?ref=danmackinlay.name">incorporated association</a> under state law to handle governance, meetings, and collective bargaining (e.g.&nbsp;negotiating group rates on health insurance), but don’t pool investment funds directly. Instead, members invest individually in agreed-upon ETFs or funds, with the association providing education, coordination, and pooling research.</p>
<p>The catch is that incorporated associations must be not-for-profit—members can’t receive financial gain from the association’s activities. So the investment coordination would need to be clearly ancillary to the association’s mutual-aid purpose, and actual investment returns would flow to members individually, not through the association.</p>
</section>
<section id="option-5-spontaneous-gifts" class="level3" data-number="1.5">
<h3 data-number="1.5" class="anchored" data-anchor-id="option-5-spontaneous-gifts"><span class="header-section-number">1.5</span> Option 5: Spontaneous gifts</h3>
<p>The Dutch <a href="https://en.wikipedia.org/wiki/Broodfonds?ref=danmackinlay.name">Broodfonds model</a> cleverly avoids insurance regulation by structuring payouts as <strong>gifts</strong> between individuals, not as insurance benefits from a fund. Each member has their own dedicated account; when someone gets sick, other members voluntarily transfer money to them.</p>
<p>In Australia, the tax treatment of gifts between individuals is generally favourable (gifts are not income to the recipient unless they’re from an employer or business). But whether APRA or ASIC would accept this framing for a scheme that <em>looks</em> like mutual insurance is an open question. The ATO might also have views.</p>
<p><strong>Research direction:</strong> Has anyone tested a Broodfonds-style gift structure against Australian financial services law? What are the ATO implications of regular “gifts” between members of a structured group?</p>
</section>
<section id="summary-table" class="level3" data-number="1.6">
<h3 data-number="1.6" class="anchored" data-anchor-id="summary-table"><span class="header-section-number">1.6</span> Summary table</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 13%">
<col style="width: 13%">
<col style="width: 19%">
<col style="width: 27%">
<col style="width: 26%">
</colgroup>
<thead>
<tr class="header">
<th>Structure</th>
<th>Regulator</th>
<th>Min. complexity</th>
<th>Can pool investments?</th>
<th>Can provide benefits?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>APRA friendly society</td>
<td>APRA</td>
<td>Extreme</td>
<td>Yes (benefit funds)</td>
<td>Yes (life insurance)</td>
</tr>
<tr class="even">
<td>Co-operative (CNL)</td>
<td>State registrar</td>
<td>Moderate</td>
<td>Maybe (MIS risk)</td>
<td>Limited</td>
</tr>
<tr class="odd">
<td>Unregistered MIS (≤20 members)</td>
<td>ASIC (light)</td>
<td>Moderate</td>
<td>Yes</td>
<td>Via returns</td>
</tr>
<tr class="even">
<td>Incorporated association</td>
<td>State</td>
<td>Low</td>
<td>No (not-for-profit)</td>
<td>Mutual aid only</td>
</tr>
<tr class="odd">
<td>Gift-based (Broodfonds)</td>
<td>Unclear</td>
<td>Low</td>
<td>No</td>
<td>Gifts between members</td>
</tr>
</tbody>
</table>
</section>
<section id="hybrid-structures" class="level3" data-number="1.7">
<h3 data-number="1.7" class="anchored" data-anchor-id="hybrid-structures"><span class="header-section-number">1.7</span> Hybrid structures</h3>
<p>There’s a possible hybrid model worth considering: a co-operative or association handles governance and collective bargaining, while a separate (or connected) unit trust or managed fund handles the investment side. This adds complexity but might give the best of both worlds—democratic mutual-aid governance on top, proper investment infrastructure underneath.</p>
<p>The <a href="https://bccm.coop/help-advice/raise-finance/mutual-capital-instruments/?ref=danmackinlay.name">Mutual Capital Instruments</a> framework from the 2019 reforms is interesting here—it was designed to let mutuals raise external capital. Could it be repurposed for a small mutual wanting to invest member contributions? Unclear, but worth investigating.</p>
</section>
</section>
<section id="wargaming-maximum-ambition-the-apra-path" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="wargaming-maximum-ambition-the-apra-path"><span class="header-section-number">2</span> Wargaming maximum ambition: the APRA path</h2>
<p>OK. Let’s stop being sensible for a moment and ask: what if we went <em>all the way</em>? Not “pick the lightest-touch legal structure and hope nobody notices”, but “actually register as a proper APRA-regulated friendly society and use AI to make the compliance affordable.” This is the maximally ambitious version. Let’s wargame it.</p>
<section id="the-compliance-bill-itemized" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="the-compliance-bill-itemized"><span class="header-section-number">2.1</span> The compliance bill, itemized</h3>
<p>Here’s what APRA demands of a friendly society, and roughly what it costs if we’re doing it the traditional way. These numbers are rough estimates assembled from publicly available information on APRA levies, actuarial consulting market rates, and audit fees for small financial institutions. I would love to hear corrections from people with direct experience.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Requirement</th>
<th>Traditional cost (annual est.)</th>
<th>What it actually involves</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://treasury.gov.au/sites/default/files/2024-05/c2024-522482-dp.pdf?ref=danmackinlay.name">APRA supervisory levy</a></td>
<td>~$22,500 minimum</td>
<td>Non-negotiable. This is the floor; paused from increasing in 2024–26 but still brutal for a small org.</td>
</tr>
<tr class="even">
<td><a href="https://www.apra.gov.au/role-of-appointed-actuary?ref=danmackinlay.name">Appointed Actuary</a></td>
<td>$30,000–80,000</td>
<td>CPS 320 requires one. Must be FIAA-qualified. Prepares the annual Financial Condition Report. Actuarial consulting runs $200–600/hr; even a minimal engagement is expensive.</td>
</tr>
<tr class="odd">
<td><a href="https://www.apra.gov.au/sites/default/files/cpg_220_april_2018_version.pdf?ref=danmackinlay.name">Risk Management Framework</a></td>
<td>$10,000–30,000</td>
<td>CPS 220 requires a documented framework, board-approved risk appetite statement, annual review. Usually outsourced to consultants.</td>
</tr>
<tr class="even">
<td><a href="https://www.apra.gov.au/news-and-publications/apra-and-asic-issue-final-rules-and-information-for-financial-accountability?ref=danmackinlay.name">Financial Accountability Regime</a></td>
<td>$5,000–15,000</td>
<td>From March 2025: accountability maps, key personnel registration, deferred remuneration arrangements.</td>
</tr>
<tr class="odd">
<td>Regulatory returns &amp; reporting</td>
<td>$10,000–20,000</td>
<td>Quarterly and annual <a href="https://www.apra.gov.au/life-insurance-and-friendly-society-reporting-requirements?ref=danmackinlay.name">returns to APRA</a>. Financial statements. Benefit fund reporting under LPS 700.</td>
</tr>
<tr class="even">
<td>Audit</td>
<td>$15,000–30,000</td>
<td>Annual statutory audit by a registered company auditor.</td>
</tr>
<tr class="odd">
<td>Legal &amp; governance</td>
<td>$10,000–20,000</td>
<td>Constitution drafting, board minutes, member communications, AGM, compliance monitoring.</td>
</tr>
<tr class="even">
<td><strong>Total traditional</strong></td>
<td><strong>~$100,000–215,000/year</strong></td>
<td></td>
</tr>
</tbody>
</table>
<p>For a group of 50 members each contributing $200/month, that’s $120,000/year in contributions—almost entirely eaten by compliance. This is why nobody does this at small scale. The compliance cost <em>is</em> the moat that keeps friendly societies institutional.</p>
</section>
<section id="the-ai-compression-thesis" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="the-ai-compression-thesis"><span class="header-section-number">2.2</span> The AI compression thesis</h3>
<p>The question is: how much of that $100k–215k can AI agents compress to near-zero?</p>
<p>I’m going to sort APRA’s requirements into three tiers by how compressible they are with current (early 2026) AI capabilities.</p>
<section id="tier-1-highly-compressible-ai-does-8095-of-the-work" class="level4" data-number="2.2.1">
<h4 data-number="2.2.1" class="anchored" data-anchor-id="tier-1-highly-compressible-ai-does-8095-of-the-work"><span class="header-section-number">2.2.1</span> Tier 1: Highly compressible (AI does 80–95% of the work)</h4>
<p><strong>Governance documentation.</strong> Meeting minutes, board papers, risk appetite statements, compliance policies, member communications. This is where current LLMs already shine. An AI agent can draft board papers from a structured agenda, generate minutes from a recording, maintain a living compliance manual that updates when regulations change, and produce member communications. The human role shrinks to: review, approve, sign. Estimated cost with AI: a few hundred dollars in API costs plus a couple of hours of human review per month. Traditional cost saved: ~$10k–20k.</p>
<p><strong>Regulatory returns and reporting.</strong> APRA’s <a href="https://www.apra.gov.au/life-insurance-and-friendly-society-reporting-requirements?ref=danmackinlay.name">reporting forms (LRF 100 through LRF 800 series)</a> are structured data submissions. An AI agent connected to the fund’s accounting system could generate these automatically, flag anomalies, and prepare them for human sign-off. The XBRL/XML submission formats are well-documented. Estimated cost with AI: mostly engineering time to build the pipeline once, then near-zero marginal cost. Traditional cost saved: ~$10k–20k.</p>
<p><strong>Risk Management Framework.</strong> CPS 220 requires documentation, not headcount. An AI agent can maintain the framework document, update it when APRA issues new guidance, run scenario analyses on the fund’s (tiny, simple) portfolio, and generate the annual board risk report. The <em>substance</em> of risk management for a small, simple fund—50 members, index ETFs, maybe a catastrophe bond allocation, no exotic instruments—is genuinely not complex. It’s the documentation overhead that kills us. Traditional cost saved: ~$10k–30k.</p>
<p><strong>FAR compliance.</strong> Accountability maps and key personnel documentation are essentially structured data problems. An AI agent can maintain the accountability map, track personnel changes, generate the required notifications to APRA. Traditional cost saved: ~$5k–15k.</p>
</section>
<section id="tier-2-partially-compressible-ai-does-4060-human-expert-still-needed" class="level4" data-number="2.2.2">
<h4 data-number="2.2.2" class="anchored" data-anchor-id="tier-2-partially-compressible-ai-does-4060-human-expert-still-needed"><span class="header-section-number">2.2.2</span> Tier 2: Partially compressible (AI does 40–60%, human expert still needed)</h4>
<p><strong>Actuarial work.</strong> This is the hard one. APRA requires an Appointed Actuary who is a Fellow of the <a href="https://www.actuaries.asn.au/?ref=danmackinlay.name">Actuaries Institute</a>. We cannot replace this person with an AI—they must be a named, qualified individual who takes personal responsibility.</p>
<p>But we <em>can</em> radically reduce their billable hours. The Financial Condition Report for a simple friendly society (one benefit fund, index ETF investments, 50 members) is a constrained problem. An AI agent could prepare the entire draft FCR with all quantitative analysis done, run the liability projections, stress tests, and sensitivity analyses, produce the tables and charts, and draft the narrative sections. The Appointed Actuary then reviews, adjusts, and signs. Instead of 80–120 hours of actuarial time, maybe 10–20 hours of review. At $400/hr, that’s $4k–8k instead of $30k–80k.</p>
<p><strong>Audit.</strong> We still need a registered company auditor. But AI can prepare audit-ready financials, reconciliations, supporting schedules, and a complete audit file. If the auditor’s engagement is “review a clean, well-documented set of books” rather than “reconstruct what happened from a box of receipts”, the hours drop dramatically. Maybe $8k–12k instead of $15k–30k.</p>
</section>
<section id="tier-3-incompressible" class="level4" data-number="2.2.3">
<h4 data-number="2.2.3" class="anchored" data-anchor-id="tier-3-incompressible"><span class="header-section-number">2.2.3</span> Tier 3: Incompressible</h4>
<p><strong>APRA supervisory levy: ~$22,500.</strong> We pay this. There’s no way around it. It’s calculated on assets with a minimum floor, and no amount of automation changes it.</p>
<p><strong>The Appointed Actuary exists.</strong> Someone must hold this role. Even if their hours are minimal, they need professional indemnity insurance and will charge a retainer.</p>
<p><strong>The auditor exists.</strong> Same logic.</p>
<p><strong>ASIC fees.</strong> AFS licence application ($1,485), annual ASIC industry funding levy (variable but typically a few thousand), company registration and annual review fees.</p>
</section>
</section>
<section id="the-compressed-budget" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="the-compressed-budget"><span class="header-section-number">2.3</span> The compressed budget</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Requirement</th>
<th>AI-compressed cost (annual est.)</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>APRA supervisory levy</td>
<td>$22,500</td>
<td>Fixed floor</td>
</tr>
<tr class="even">
<td>Appointed Actuary (review only)</td>
<td>$4,000–8,000</td>
<td>AI prepares everything, actuary reviews + signs</td>
</tr>
<tr class="odd">
<td>Risk Management Framework</td>
<td>$500–1,000</td>
<td>AI-maintained, human review quarterly</td>
</tr>
<tr class="even">
<td>FAR compliance</td>
<td>$500–1,000</td>
<td>AI-maintained accountability maps</td>
</tr>
<tr class="odd">
<td>Regulatory returns</td>
<td>$500–1,000</td>
<td>Automated pipeline, human sign-off</td>
</tr>
<tr class="even">
<td>Audit (clean books)</td>
<td>$8,000–12,000</td>
<td>AI-prepared audit file</td>
</tr>
<tr class="odd">
<td>Legal &amp; governance</td>
<td>$1,000–2,000</td>
<td>AI drafts, humans review</td>
</tr>
<tr class="even">
<td>ASIC fees &amp; levies</td>
<td>$2,000–4,000</td>
<td>AFS licence costs, industry funding</td>
</tr>
<tr class="odd">
<td>AI infrastructure</td>
<td>$2,000–5,000</td>
<td>API costs, hosting, tooling</td>
</tr>
<tr class="even">
<td><strong>Total compressed</strong></td>
<td><strong>~$41,000–56,000/year</strong></td>
<td></td>
</tr>
</tbody>
</table>
<p>That’s roughly half to a quarter of the traditional cost. For 50 members at $200/month ($120k/year income), we’d spend 35–45% on compliance, leaving $65k–80k for actual investment and mutual aid. Not great, but not impossible—especially if the point isn’t to maximize returns in good times but to have assets that hold value in bad times.</p>
<p>At 100 members, we’re at $240k/year income with the same ~$50k compliance cost—now it’s about 20%, which starts to look viable. At 200 members, the compliance cost is noise.</p>
<p>The $22,500 APRA levy is doing most of the damage at small scale. If a dozen neo-friendly societies all registered and started lobbying for a proportionate micro-entity levy, that floor might eventually move. Unlikely, but stranger things have happened.</p>
</section>
<section id="the-shoestring-team" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="the-shoestring-team"><span class="header-section-number">2.4</span> The shoestring team</h3>
<p>Who do we actually need?</p>
<p><strong>Paid roles (part-time/fractional):</strong></p>
<ul>
<li><strong>Appointed Actuary</strong> (fractional, ~20–40 hrs/year): Reviews AI-generated FCR, signs off. We’d want someone sympathetic to the project—perhaps a recently retired actuary, or one who works with multiple small funds. This is the hardest role to fill cheaply.</li>
<li><strong>Auditor</strong> (engagement, ~40–60 hrs/year): Annual statutory audit. A smaller firm or sole practitioner who’s comfortable with well-automated books.</li>
<li><strong>Responsible Manager</strong> (for AFS licence): ASIC requires at least one “responsible manager” with relevant qualifications and experience. This person oversees the financial services. Could be a member with the right background.</li>
</ul>
<p><strong>Volunteer/member roles (AI-assisted):</strong></p>
<ul>
<li><strong>Treasurer/Secretary</strong>: Runs the AI compliance pipeline day-to-day. Reviews AI outputs before submission. Doesn’t need to be an accountant—needs to be detail-oriented and willing to learn. The AI does the technical work; this person does quality control.</li>
<li><strong>Board</strong> (3–5 members): Meets quarterly. Reviews AI-prepared board papers. Makes actual decisions (investment policy, benefit design, member applications). Could be done in 2–3 hours per quarter if the papers are good.</li>
<li><strong>Tech lead</strong>: Maintains the AI agent infrastructure. Keeps the regulatory reporting pipeline working. Updates the compliance system when APRA changes its standards. This is probably the most important volunteer role—the whole model depends on the automation working.</li>
</ul>
</section>
<section id="the-ai-stack" class="level3" data-number="2.5">
<h3 data-number="2.5" class="anchored" data-anchor-id="the-ai-stack"><span class="header-section-number">2.5</span> The AI stack</h3>
<p>We’d build a system something like: accounting software (Xero or similar, ~$50/month) as the source of truth for financials, connected to an AI agent layer that:</p>
<ul>
<li>Ingests APRA’s prudential standards and reporting requirements</li>
<li>Generates all compliance documentation</li>
<li>Prepares regulatory returns in the required XBRL/XML formats</li>
<li>Maintains the risk management framework as a living document</li>
<li>Drafts board papers and member communications</li>
<li>Monitors for regulatory changes (new APRA guidance, amended standards)</li>
<li>Alerts the human team when something needs attention or sign-off</li>
</ul>
<p>This is a purpose-built system, not an off-the-shelf product. The RegTech industry has enterprise products that do pieces of this, but they’re priced for institutions. Our approach would be more DIY—AI coding agents building custom tools against one specific regulatory regime, iterated and debugged against real APRA requirements.</p>
<p>The upfront engineering investment is significant—maybe 200–400 hours to build the initial stack. But it only needs building once. Every subsequent friendly society that uses the same toolkit gets it for free (modulo customization).</p>
</section>
<section id="why-bother-going-full-apra" class="level3" data-number="2.6">
<h3 data-number="2.6" class="anchored" data-anchor-id="why-bother-going-full-apra"><span class="header-section-number">2.6</span> Why bother going full APRA?</h3>
<p>Three reasons, given the framing from the parent post:</p>
<ol type="1">
<li><strong>Benefit funds.</strong> Only an APRA-registered friendly society can operate benefit funds that provide defined benefits to members—sickness, disability, death. This is the <em>actual thing</em> that makes it a crisis instrument rather than just an investment club. Without it, we can invest together, but we can’t promise members specific support when they need it.</li>
<li><strong>Trust and legitimacy.</strong> “We’re APRA-regulated” means something. Members can trust that there’s real prudential oversight. Potential partner organizations (health insurers, employers, other mutuals) take us seriously.</li>
<li><strong>The replication play.</strong> If we build the AI compliance stack for one friendly society and it works, we’ve built it for <em>all</em> of them. The marginal cost of spinning up society #2, #3, #N is dramatically lower. The compliance infrastructure becomes a platform. This is where the shoestring investment pays off—not within a single society, but across a network of many.</li>
</ol>
</section>
<section id="what-could-go-wrong" class="level3" data-number="2.7">
<h3 data-number="2.7" class="anchored" data-anchor-id="what-could-go-wrong"><span class="header-section-number">2.7</span> What could go wrong</h3>
<p>Plenty. This is the “maximally ambitious” scenario, after all.</p>
<ul>
<li><strong>APRA might say no.</strong> They have discretion over licensing. A group of 50 friends proposing to run a friendly society on AI agents and vibes might not inspire confidence. We’d need a very good application and probably some pre-engagement with APRA to test the waters.</li>
<li><strong>The actuary problem.</strong> Finding a qualified actuary willing to put their name on a tiny, AI-assisted fund for a modest fee is non-trivial. Professional indemnity insurance alone might make it uneconomic for them.</li>
<li><strong>Regulatory change risk.</strong> APRA could tighten requirements in ways that break the automation. New reporting standards, new prudential rules, new interpretation of existing rules—all could spike costs unpredictably.</li>
<li><strong>Single point of failure.</strong> If the tech lead burns out or the AI stack breaks, we’re suddenly trying to do manual compliance on a volunteer budget. This needs redundancy and good documentation.</li>
<li><strong>The $22,500 problem.</strong> That minimum levy is the real killer. Until APRA introduces a genuinely proportionate levy for micro-entities (unlikely but not impossible, especially if there are enough of them lobbying for it), this floor sets a hard minimum on viable fund size.</li>
</ul>
</section>
<section id="the-staged-approach" class="level3" data-number="2.8">
<h3 data-number="2.8" class="anchored" data-anchor-id="the-staged-approach"><span class="header-section-number">2.8</span> The staged approach</h3>
<p>Maybe we don’t start at APRA. Maybe the play is:</p>
<p><strong>Year 0–1:</strong> Incorporate as an association or small co-op. Pool knowledge, not money. Build the AI compliance toolkit against APRA’s <em>actual standards</em>, as if we were regulated, but without the cost. Test it. Break it. Fix it. Invest in the counter-cyclical portfolio individually, coordinated by the association.</p>
<p><strong>Year 1–2:</strong> Operate as an unregistered MIS (≤20 members, ≤$2M) to test the investment function with real money. Get real experience with ASIC’s lighter-touch regime. Keep building and refining the compliance stack.</p>
<p><strong>Year 2–3:</strong> If the model works and membership demand is there, apply for APRA registration. By this point we have a working compliance system, a track record, audited financials, and a demonstrated member base. The application is much stronger than “we have an idea and some code.”</p>
<p><strong>Year 3+:</strong> Publish the toolkit. Help others replicate. Lobby APRA for proportionate regulation of micro-friendly societies.</p>
<p>This staged approach means we never spend money we don’t have, we build capability incrementally, and we only take on APRA’s compliance burden when we’re confident we can handle it. It also means the crisis-hedge portfolio is running from Year 1—the APRA registration is about adding the formal benefit structure on top, not about starting the investment.</p>
</section>
</section>
<section id="what-i-still-dont-know" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="what-i-still-dont-know"><span class="header-section-number">3</span> What I still don’t know</h2>
<ul>
<li>Whether the Broodfonds gift-based model survives contact with Australian financial services law and ATO scrutiny.</li>
<li>The practical costs of running an unregistered MIS with ≤20 members—I have estimates above but no real-world data.</li>
<li>Whether any FIAA-qualified actuary would actually take on a tiny fund at the rates I’m projecting, or whether professional indemnity makes it uneconomic.</li>
<li>Whether APRA would even entertain a licence application from a micro-entity with this profile.</li>
<li>How to structure the investment function in a way that’s both legally sound and actually useful as a crisis hedge.</li>
<li>What existing Australian co-ops or mutuals are doing that’s close to this vision—and what we can learn from them.</li>
</ul>
<p>If we know about any of this, I’d love to hear from you.</p>


</section>

 ]]></description>
  <category>community project</category>
  <category>cooperation</category>
  <category>DIY</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>markets</category>
  <category>money</category>
  <category>straya</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/neo_friendly_societies_technical.html</guid>
  <pubDate>Sun, 08 Mar 2026 23:00:00 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/maggior_consiglio_RP-P-2018-1237.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>What even is “agency”?</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/agency.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p>We are very exercised about whether we have agency. What is it actually? What does it even mean to say that we have agency? Can we ground our intuition about agency in something precise?</p>
<p>Do we even want it, or is <a href="../notebook/growing_up.html">being the grown up</a> not necessarily <a href="../notebook/robot_regency.html">in our best interests</a>?</p>
<p>These are all questions I don’t have answers to.</p>
<p>Here I have collected a list of edge cases for “agency” that I hope might help me delineate its boundaries.</p>
<div id="fig-vanity" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-vanity-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/vanity_RP-P-OB-9922.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/vanity_RP-P-OB-9922.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-vanity-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<section id="cults-and-agency" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="cults-and-agency"><span class="header-section-number">1</span> Cults and agency</h2>
<p><a href="https://www.iheart.com/podcast/867-the-dream-29832329/episode/the-antidote-to-trickery-323584845/?ref=danmackinlay.name">The Antidote to Trickery</a> is an interview with <a href="https://ownyourbrain.org/about/diane-benscoter/?ref=danmackinlay.name">Diane Benscoter</a>, a de-radicalization expert, who argues that there is a distinction between being programmed by a cult and being de-programmed by a non-cult.</p>
<p>Is <a href="../notebook/multi_scale_agency.html">sublimating my goals to a larger group really a loss of agency?</a></p>
</section>
<section id="is-domestication-a-loss-of-agency" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="is-domestication-a-loss-of-agency"><span class="header-section-number">2</span> Is domestication a loss of agency?</h2>
<p>See <a href="../notebook/human_domestication.html">Human domestication</a>.</p>
</section>
<section id="coercive-control" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="coercive-control"><span class="header-section-number">3</span> Coercive control</h2>
<p>Researchers seem to agree that coercive control is a thing, and that it’s bad <span class="citation" data-cites="Lohmann2024Trauma">(Lohmann et al. 2024)</span>. So intuitively some of the things that <em>agency</em> should include are things that should be diminished by coercive control.</p>
<p>cf <a href="https://aifs.gov.au/resources/policy-and-practice-papers/what-research-evidence-tells-us-about-coercive-control?ref=danmackinlay.name">What the research evidence tells us about coercive control victimization</a>.</p>
</section>
<section id="addiction" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="addiction"><span class="header-section-number">4</span> Addiction</h2>
<p>Is <a href="../notebook/addiction.html">addiction</a> a loss of agency?</p>
</section>
<section id="high-agency-vs-npc" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="high-agency-vs-npc"><span class="header-section-number">5</span> “High-agency” vs NPC</h2>
<p><a href="https://jasmi.news/p/dictionary?ref=danmackinlay.name">Jasmine Sun decodes</a> the Bay Area use of the term</p>
<blockquote class="blockquote">
<p>You can be a top-ranked competitive coder or know every world leader’s birthday by heart, but the only real metric of success is whether you can build a life you’re happy with. Cold-emailing your way into a dream job is pretty high-agency; quitting it to become a strawberry farmer is even more so. Agency is initiative, resourcefulness, a high internal locus of control. Not stressing about roadblocks and assuming you’ll figure it out along the way.</p>
</blockquote>
</section>
<section id="attention-hacking" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="attention-hacking"><span class="header-section-number">6</span> Attention hacking</h2>
<p>If my <a href="../notebook/attention_economy.html">attention is being hacked</a>, do I lose agency?</p>
</section>
<section id="reward-tampering" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="reward-tampering"><span class="header-section-number">7</span> Reward tampering</h2>
<p>If my <a href="../notebook/human_reward_hacking.html">preferences are altered to be what some other entity wishes them to be</a>, do I lose agency?</p>
</section>
<section id="empowerment" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="empowerment"><span class="header-section-number">8</span> Empowerment</h2>
<p>If I am <a href="../notebook/intrinsic_motivation.html">empowered</a>, do I gain agency?</p>
</section>
<section id="incoming" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="incoming"><span class="header-section-number">9</span> Incoming</h2>
<ul>
<li><p><a href="https://github.com/dcsoft-yyf/CAAM/blob/main/README.md?ref=danmackinlay.name">dcsoft-yyf/CAAM</a></p>
<blockquote class="blockquote">
<p>The Carbon-based AI Analysis Method (CAAM) redefines human freedom as a “Non-Lock-in State” within a five-dimensional phase space—characterized by diffused goals, controllable leverage, native pain interpretation, accessible exit, and weak coupling. If CAAM withstands rigorous mathematical proof in the future, human rights, within this framework, cease to be abstract moral halos and become computable dynamical constraints for maximizing the accessible set in phase space.</p>
</blockquote></li>
</ul>
</section>
<section id="references" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="references"><span class="header-section-number">10</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Biehl2023Interpreting" class="csl-entry">
Biehl, and Virgo. 2023. <span>“<a href="https://doi.org/10.1007/978-3-031-28719-0_2">Interpreting Systems as Solving POMDPs: A Step Towards a Formal Understanding of Agency</a>.”</span> In.
</div>
<div id="ref-Bowles2003Coevolution" class="csl-entry">
Bowles, Choi, and Hopfensitz. 2003. <span>“<a href="https://doi.org/10.1016/S0022-5193(03)00060-2">The Co-Evolution of Individual Behaviors and Social Institutions</a>.”</span> <em>Journal of Theoretical Biology</em>.
</div>
<div id="ref-Bullock2025AGI" class="csl-entry">
Bullock, Hammond, and Krier. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2503.05710">AGI, Governments, and Free Societies</a>.”</span>
</div>
<div id="ref-Critch2022Cooperative" class="csl-entry">
Critch, Dennis, and Russell. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2208.07006">Cooperative and Uncooperative Institution Designs: Surprises and Problems in Open-Source Game Theory</a>.”</span>
</div>
<div id="ref-Farrell2025Large" class="csl-entry">
Farrell, Gopnik, Shalizi, et al. 2025. <span>“<a href="https://doi.org/10.1126/science.adt9819">Large AI Models Are Cultural and Social Technologies</a>.”</span> <em>Science</em>.
</div>
<div id="ref-Halpern2018Formal" class="csl-entry">
Halpern, and Kleiman-Weiner. 2018. <span>“<a href="https://doi.org/10.48550/arXiv.1810.05903">Towards Formal Definitions of Blameworthiness, Intention, and Moral Responsibility</a>.”</span>
</div>
<div id="ref-HerrmannDOI" class="csl-entry">
Herrmann, and Levinstein. n.d. <span>“Radical AI Interpretability.”</span>
</div>
<div id="ref-Kang2022AI" class="csl-entry">
Kang, and Lou. 2022. <span>“<a href="https://doi.org/10.1093/jcmc/zmac014">AI Agency Vs.&nbsp;Human Agency: Understanding Human–AI Interactions on TikTok and Their Implications for User Engagement</a>.”</span> <em>Journal of Computer-Mediated Communication</em>.
</div>
<div id="ref-Kenton2023Discovering" class="csl-entry">
Kenton, Kumar, Farquhar, et al. 2023. <span>“<a href="https://doi.org/10.1016/j.artint.2023.103963">Discovering Agents</a>.”</span> <em>Artificial Intelligence</em>.
</div>
<div id="ref-Kulveit2025Gradual" class="csl-entry">
Kulveit, Douglas, Ammann, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2501.16946">Gradual Disempowerment: Systemic Existential Risks from Incremental AI Development</a>.”</span>
</div>
<div id="ref-List2016Freedom" class="csl-entry">
List, and Valentini. 2016. <span>“<a href="https://doi.org/10.1086/686006">Freedom as Independence</a>.”</span> <em>Ethics</em>.
</div>
<div id="ref-Liu2024Attaining" class="csl-entry">
Liu, Wang, Li, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2405.16588">Attaining Human Desirable Outcomes in Human-AI Interaction via Structural Causal Games</a>.”</span>
</div>
<div id="ref-Loewith2025Mutual" class="csl-entry">
Loewith, and Street. 2025. <span>“<a href="https://doi.org/10.1162/ANTI.5CZK">Mutual Prediction in Human–AI Coevolution</a>.”</span> <em>Antikythera Digital Journal</em>.
</div>
<div id="ref-Lohmann2024Trauma" class="csl-entry">
Lohmann, Cowlishaw, Ney, et al. 2024. <span>“<a href="https://doi.org/10.1177/15248380231162972">The Trauma and Mental Health Impacts of Coercive Control: A Systematic Review and Meta-Analysis</a>.”</span> <em>Trauma, Violence &amp; Abuse</em>.
</div>
<div id="ref-Morris2015Foragers" class="csl-entry">
Morris. 2015. <em>Foragers, Farmers, and Fossil Fuels: How Human Values Evolve</em>. Edited by Stephen Macedo.
</div>
<div id="ref-Nguyen2020Games" class="csl-entry">
Nguyen. 2020. <em>Games: Agency As Art</em>. Thinking Art.
</div>
<div id="ref-Orseau2018Agents" class="csl-entry">
Orseau, McGill, and Legg. 2018. <span>“<a href="https://doi.org/10.48550/arXiv.1805.12387">Agents and Devices: A Relative Definition of Agency</a>.”</span>
</div>
<div id="ref-Pang2026Markovian" class="csl-entry">
Pang. 2026. <span>“On the Markovian Dynamics of Computational Systems.”</span>
</div>
<div id="ref-Qiu2025Lockin" class="csl-entry">
Qiu, He, Chugh, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2506.06166">The Lock-in Hypothesis: Stagnation by Algorithm</a>.”</span> In.
</div>
<div id="ref-Ward2024Reasons" class="csl-entry">
Ward, MacDermott, Belardinelli, et al. 2024. <span>“<a href="https://doi.org/10.48550/arXiv.2402.07221">The Reasons That Agents Act: Intention and Instrumental Goals</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>adaptive</category>
  <category>agents</category>
  <category>AI safety</category>
  <category>cooperation</category>
  <category>economics</category>
  <category>evolution</category>
  <category>extended self</category>
  <category>game theory</category>
  <category>incentive mechanisms</category>
  <category>learning</category>
  <category>mind</category>
  <category>networks</category>
  <category>utility</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/agency.html</guid>
  <pubDate>Mon, 02 Mar 2026 23:46:12 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/vanity_RP-P-OB-9922.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>New project: an Alignment Journal</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/post/announcing_alignment_journal.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-konstlyke-dingen" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-konstlyke-dingen-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/konstlyke_dingen_RP-P-OB-44.330.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/konstlyke_dingen_RP-P-OB-44.330.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-konstlyke-dingen-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>One massive project over the last few weeks has been founding the <a href="https://alignmentjournal.org/?ref=danmackinlay.name">Alignment Journal</a>, a new open-access journal for research on <a href="../notebook/alignment_problems.html">AI alignment</a> and related topics.</p>
<p>I am one of the two founding managing editors, along with <a href="https://blog.jessriedel.com/?ref=danmackinlay.name">Jess Riedel</a>, and we have a fantastic team. We’re also really excited about the board members who’ve come on to give us guidance and help us with the work of running the journal.</p>
<p>There are all kinds of things to be done. We have a mandate to experiment with <a href="../notebook/science_peer_review.html">alternate peer review processes</a>, which is a long-running obsession of mine.</p>
<p>Please see the <a href="https://blog.alignmentjournal.org/post/coming-soon/?ref=danmackinlay.name">initial announcement post</a> for more details, and subscribe to the blog for updates on the journal, including calls for papers and announcements of accepted papers.</p>
<p>Is this a good idea? We don’t know yet. The goal here is to do a one-year pilot and see if we are making the world better, or not. We’d love for people to participate in the experiment by submitting papers, reviewing papers, or just following along with the journal and giving us feedback. <a href="https://woozy-page-39c.notion.site/304398af2ab08029b8c4dbcf79c0c393?pvs=105&amp;ref=danmackinlay.name">Subscribe to our mailing list here</a> for updates.</p>
<p>I personally am pleased to thank <a href="../post/pibbss_x_iliad.html">the PIBBSS × ILIAD</a> program for giving me the opportunity to work on this project.</p>



 ]]></description>
  <category>academe</category>
  <category>AI safety</category>
  <category>collective knowledge</category>
  <category>conference</category>
  <category>economics</category>
  <category>faster pussycat</category>
  <category>game theory</category>
  <category>how do science</category>
  <category>incentive mechanisms</category>
  <category>innovation</category>
  <category>institutions</category>
  <category>machine learning</category>
  <category>mind</category>
  <category>networks</category>
  <category>neural nets</category>
  <category>provenance</category>
  <category>sociology</category>
  <category>statistics</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/post/announcing_alignment_journal.html</guid>
  <pubDate>Mon, 02 Mar 2026 08:18:21 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/konstlyke_dingen_RP-P-OB-44.330.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Evolution strategies</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/evolution_strategy.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-hat-evolution" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-hat-evolution-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/hat_evolution_RP-P-OB-30.633.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/hat_evolution_RP-P-OB-30.633.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-hat-evolution-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>It’s a <a href="../notebook/biomimetic_algorithms.html">nature-inspired</a> <a href="../notebook/nn_ensemble.html">ensemble</a> strategy for optimization that uses <a href="../notebook/evolution.html">evolution-like</a> approaches. I ignored this for ages, but it includes lots of interesting and suggestive ideas. It’s also not <em>much</em> like other, more earnest attempts to copy evolutionary behaviour, such as the <a href="../notebook/genetic_programming.html">genetic programming</a> approach to symbolic regression, which has not been that useful. Instead, it’s perhaps best thought of as a clever <a href="../notebook/monte_carlo.html">Monte Carlo</a> method for optimization. It resembles <a href="../notebook/stein_vgd.html">Stein VGD</a> if we squint at it funny.</p>
<p>ES (Evolution Strategies) is a fairly generic optimization method, but let’s talk about it in terms of neural networks, because that is what I’m thinking about, and also because the older literature needs updating.</p>
<section id="evolution-strategies-for-beginners" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="evolution-strategies-for-beginners"><span class="header-section-number">1</span> Evolution Strategies for beginners</h2>
<p>If we’ve trained neural networks with backprop, we’re used to this workflow:</p>
<ol type="1">
<li>Pick a loss <img src="https://latex.codecogs.com/png.latex?L(%5Ctheta)"> for parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.</li>
<li>Compute <img src="https://latex.codecogs.com/png.latex?%5Cnabla_%5Ctheta%20L(%5Ctheta)"> by differentiating through the network.</li>
<li>Update <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20%5Cleftarrow%20%5Ctheta%20-%20%5Ceta%20%5Cnabla_%5Ctheta%20L(%5Ctheta)">.</li>
</ol>
<p>Evolution Strategies are an alternative way of handling step 2, with the remarkable possibility of working even when backprop is painful or impossible: when the system is non-differentiable, gradients are too noisy, the “model” includes discrete choices or simulator code, or we want to optimize something we only observe as an outcome (a score, a reward, an accuracy).</p>
<p>The neat trick is that ES can still look “gradient-like” in the end: we perturb parameters, evaluate the perturbed models, and combine those evaluations into an update that points (in expectation) in a direction of improvement. This technique was used alongside backprop for much of the field’s history, before backprop became dominant in the 2000s <span class="citation" data-cites="Beyer1995Theory Beyer2002Evolution Rechenberg1978Evolutionsstrategien">(Beyer 1995; Beyer and Schwefel 2002; Rechenberg 1978)</span>.</p>
</section>
<section id="a-running-example" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="a-running-example"><span class="header-section-number">2</span> A running example</h2>
<p>Let us consider the challenge of <a href="../notebook/nn_backprop_free.html">training a tiny neural net <em>without</em> backprop</a>.</p>
<p>Suppose we have a simple classifier <img src="https://latex.codecogs.com/png.latex?f_%5Ctheta(x)"> (say, a two-layer MLP). The standard supervised objective is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5Ctheta)%20=%20%5Cmathbb%7BE%7D_%7B(x,y)%5Csim%20%5Cmathcal%7BD%7D%7D%5Cbig%5B%5Cell(f_%5Ctheta(x),%20y)%5Cbig%5D.%0A"></p>
<p>Backpropagation gives us <img src="https://latex.codecogs.com/png.latex?%5Cnabla_%5Ctheta%20L(%5Ctheta)">. ES assumes we <em>won’t</em> use it, and instead treats “run the model forward, then compute the loss” as a black box we can call:</p>
<ul>
<li>Input: the parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta"></li>
<li>Output: a scalar score (loss, reward, accuracy, and so on)</li>
</ul>
<p>To match the usual ES maximization framing, we define a <a href="../notebook/utility_fitness.html"><em>fitness</em> function</a> <img src="https://latex.codecogs.com/png.latex?F(%5Ctheta)"> that we <em>maximize</em>; for example:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AF(%5Ctheta)%20=%20-L(%5Ctheta).%0A"></p>
<p>The ES loop looks like this:</p>
<ol type="1">
<li>Sample random perturbations <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon_i">.</li>
<li>Evaluate fitness at the perturbed parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon_i">.</li>
<li>Combine the results into an update for <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.</li>
</ol>
<p>Here <img src="https://latex.codecogs.com/png.latex?%5Csigma%3E0"> is the “noise scale” (how far we probe).</p>
</section>
<section id="basic-version" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="basic-version"><span class="header-section-number">3</span> Basic version</h2>
<p>A standard choice is Gaussian perturbations (why is it always Gaussian, anyway? It feels a bit like <a href="../notebook/mcmc_langevin.html">SGLD</a>):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cvarepsilon%20%5Csim%20%5Cmathcal%7BN%7D(0,%20I).%0A"></p>
<p>Let’s define the <em>smoothed objective</em>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ(%5Ctheta)%20=%20%5Cmathbb%7BE%7D_%7B%5Cvarepsilon%20%5Csim%20%5Cmathcal%7BN%7D(0,I)%7D%5Cbig%5BF(%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon)%5Cbig%5D.%0A"></p>
<p>Backprop optimizes <img src="https://latex.codecogs.com/png.latex?F(%5Ctheta)"> directly (when it can). ES typically optimizes a <em>Gaussian-smoothed</em> version, <img src="https://latex.codecogs.com/png.latex?J(%5Ctheta)">.</p>
<p>We’ve seen this before in <a href="../notebook/nn_enkf.html">NN training by Kalman filters</a>, which also optimizes a smoothed objective, but with a fixed update ensemble.</p>
<p>We often point to that smoothing as having lots of benefits: it makes discontinuities and jagged landscapes more manageable, at the cost of a bias we control with <img src="https://latex.codecogs.com/png.latex?%5Csigma">.</p>
<p>Fun fact: <img src="https://latex.codecogs.com/png.latex?J(%5Ctheta)"> has a gradient even if <img src="https://latex.codecogs.com/png.latex?F"> isn’t differentiable, and we can write it without differentiating through <img src="https://latex.codecogs.com/png.latex?F">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla_%5Ctheta%20J(%5Ctheta)%20=%20%5Cfrac%7B1%7D%7B%5Csigma%7D%5C,%5Cmathbb%7BE%7D_%7B%5Cvarepsilon%7D%5Cbig%5BF(%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon)%5C,%5Cvarepsilon%5Cbig%5D.%0A"></p>
<p>This is a <a href="../notebook/score_function_estimator.html">score-function / log-likelihood trick</a> specialised to Gaussians. We can read it as:</p>
<ul>
<li>“If perturbations in direction <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon"> tend to increase fitness, we move <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> in direction <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon">.”</li>
</ul>
<p>A Monte Carlo estimate using <img src="https://latex.codecogs.com/png.latex?N"> samples is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%7Bg%7D%20=%20%5Cfrac%7B1%7D%7BN%5Csigma%7D%5Csum_%7Bi=1%7D%5EN%20F(%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon_i)%5C,%5Cvarepsilon_i,%0A%5Cqquad%0A%5Ctheta%20%5Cleftarrow%20%5Ctheta%20+%20%5Ceta%20%5Chat%7Bg%7D.%0A"></p>
</section>
<section id="variance-reduction-with-antithetic-sampling" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="variance-reduction-with-antithetic-sampling"><span class="header-section-number">4</span> Variance reduction with antithetic sampling</h2>
<p>A variance reduction trick we can almost always use is <em>antithetic pairs</em>. For each <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon_i">, evaluate both <img src="https://latex.codecogs.com/png.latex?%5Ctheta+%5Csigma%5Cvarepsilon_i"> and <img src="https://latex.codecogs.com/png.latex?%5Ctheta-%5Csigma%5Cvarepsilon_i">. Then take their difference:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%7Bg%7D_%7B%5Cpm%7D%20=%20%5Cfrac%7B1%7D%7B2N%5Csigma%7D%5Csum_%7Bi=1%7D%5EN%20%5Cbig(F(%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon_i)%20-%20F(%5Ctheta%20-%20%5Csigma%20%5Cvarepsilon_i)%5Cbig)%5C,%5Cvarepsilon_i.%0A"></p>
<p>Intuition: symmetric noise cancels a lot of baseline effects and reduces estimator variance.</p>
<p>If we want a drop-in recipe for gradient descent:</p>
<ul>
<li>Replace “backward pass” with “evaluate <img src="https://latex.codecogs.com/png.latex?2N"> forward passes with perturbed weights”.</li>
<li>Replace “gradient tensor” with “a weighted sum of perturbation tensors”.</li>
</ul>
<p>What stays the same:</p>
<ul>
<li>We still run forward passes.</li>
<li>We still optimize a scalar objective.</li>
<li>We still do iterative updates.</li>
</ul>
<p>What changes:</p>
<ol type="1">
<li>No computational graph / no chain rule. Our model can include discrete operations, integer arithmetic, if statements, simulators, non-differentiable reward shaping—anything.</li>
<li>We pay in evaluations. ES typically needs a <em>population</em> of evaluations per update to get a decent gradient estimate.</li>
<li>Parallelism looks different. Each population member is independent: we evaluate on different devices, report back one scalar fitness value each, then combine centrally (or via all-reduce on scalars).</li>
</ol>
<p>A useful way to frame it:</p>
<ul>
<li>Backprop cost is “one forward + one backward” (plus distributed gradient comms).</li>
<li>ES cost is “many forwards, no backward” (plus scalar comms).</li>
</ul>
<p>So ES tends to make sense when:</p>
<ul>
<li>forward passes are cheap or massively parallelizable,</li>
<li>backward passes are awkward/expensive/impossible,</li>
<li>the objective is noisy or outcome-only,</li>
<li>we want robustness to nasty landscapes (e.g., long-horizon RL, recurrent instability).</li>
</ul>
<p>And ES tends to be a bad deal when:</p>
<ul>
<li>gradients are available and stable,</li>
<li>we’re compute-limited and can’t parallelize lots of forwards,</li>
<li>we need the sample efficiency of gradient methods.</li>
</ul>
</section>
<section id="practical-es-details" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="practical-es-details"><span class="header-section-number">5</span> Practical ES details</h2>
<p>An LLM advises me that I’ll need to <a href="../notebook/mc_variance_reduction.html">stabilize variance</a> using some (standard) tricks:</p>
<ol type="1">
<li>Fitness normalization / shaping. Replace raw <img src="https://latex.codecogs.com/png.latex?F_i"> with normalized scores (subtract mean and divide by standard deviation), or rank-transform them. This reduces sensitivity to outliers and scale.</li>
<li>Common random numbers. Use the same minibatch/data for evaluating the population in one generation so that differences are due to parameters, not data noise. <em>This sounds like a spanner in the works for me — see my argument with an LLM under <a href="../notebook/nn_evolution_strategy.html#large-data">large data</a></em>.</li>
<li>Control variates / baselines. The antithetic form is one; subtracting the mean fitness is another.</li>
<li>Step size and <img src="https://latex.codecogs.com/png.latex?%5Csigma"> scheduling. <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is both a smoothing radius and can affect both bias and variance.</li>
</ol>
<p>So <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is a free parameter. What does it do?</p>
<ul>
<li>ES is optimizing <img src="https://latex.codecogs.com/png.latex?J(%5Ctheta)=%5Cmathbb%7BE%7D%5BF(%5Ctheta+%5Csigma%5Cvarepsilon)%5D">.</li>
<li>If <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is large, we are <em>not</em> optimizing the original objective tightly; we are optimizing a blurred version of it.</li>
<li>If <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is tiny, the estimator variance can blow up unless <img src="https://latex.codecogs.com/png.latex?N"> is large.</li>
</ul>
</section>
<section id="es-meets-bayes" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="es-meets-bayes"><span class="header-section-number">6</span> ES meets Bayes</h2>
<p>ES looks “Bayesian” in the sense that it maintains and updates a probability distribution. And it reuses many classic Bayesian sampling tricks, such as quasi-Langevin dynamics. Let us make the correspondence more precise, though.</p>
<ol type="1">
<li>Bayesian optimization (BO) puts a Bayesian posterior on the <em>unknown objective function</em> <img src="https://latex.codecogs.com/png.latex?f(%5Ccdot)"> (often a GP), then chooses evaluation points via an acquisition rule balancing exploration/exploitation <span class="citation" data-cites="Brochu2010Tutorial">(Brochu, Cora, and Freitas 2010)</span>.</li>
<li>Evolution Strategies (ES) (and especially NES/CMA-ES-style methods) put a distribution on <em>candidate solutions</em> <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> (or search steps), and update that distribution using fitness evaluations. There is typically no posterior over the function <img src="https://latex.codecogs.com/png.latex?f"> itself.</li>
</ol>
<p>What follows are some ways the “Bayes connection” has been made precise in the literature.</p>
<section id="what-is-the-smoothing-search-device-distribution" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="what-is-the-smoothing-search-device-distribution"><span class="header-section-number">6.1</span> What is the “smoothing / search-device” distribution?</h3>
<p>In vanilla ES (and in “variational optimization”), we introduce an <em>auxiliary</em> distribution over parameters and optimize the expected objective under that distribution. This distribution is not a Bayesian posterior, but rather something we choose as part of the optimizer.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?F(%5Ctheta)"> be the fitness we want to maximize (e.g.&nbsp;negative loss). Pick a parametric <em>search distribution</em> <img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cphi(%5Ctheta)%0A"> and define the ES/VO objective. <img src="https://latex.codecogs.com/png.latex?%0AJ(%5Cphi)%20%5C;=%5C;%20%5Cmathbb%7BE%7D_%7B%5Ctheta%5Csim%20q_%5Cphi%7D%5Cbig%5BF(%5Ctheta)%5Cbig%5D.%0A"></p>
<p>Content: Content: - <strong>Search device:</strong> when we sample <img src="https://latex.codecogs.com/png.latex?%5Ctheta%5Csim%20q_%5Cphi">, we generate the “population” of candidate networks we evaluate. - <strong>Smoothing device:</strong> <img src="https://latex.codecogs.com/png.latex?J(%5Cphi)"> is a <em>smoothed</em> version of <img src="https://latex.codecogs.com/png.latex?F">, because it averages <img src="https://latex.codecogs.com/png.latex?F"> locally according to <img src="https://latex.codecogs.com/png.latex?q_%5Cphi">.</p>
<p>A common “vanilla ES” choice is an isotropic Gaussian centred on a mean parameter vector <img src="https://latex.codecogs.com/png.latex?%5Cmu">: <img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cphi(%5Ctheta)=%5Cmathcal%7BN%7D(%5Ctheta;%5Cmu,%5Csigma%5E2%20I),%20%5Cquad%20%5Cphi=(%5Cmu,%5Csigma).%0A"> Then the smoothed objective as a function of <img src="https://latex.codecogs.com/png.latex?%5Cmu"> is: <img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20F(%5Cmu)%20%5C;=%5C;%20%5Cmathbb%7BE%7D_%7B%5Cvarepsilon%5Csim%5Cmathcal%7BN%7D(0,I)%7D%5Cbig%5BF(%5Cmu+%5Csigma%5Cvarepsilon)%5Cbig%5D%0A%5C;=%5C;%20(F%20*%20%5Cmathcal%7BN%7D(0,%5Csigma%5E2%20I))(%5Cmu),%0A"> That is, a convolution with a Gaussian kernel: a bigger <img src="https://latex.codecogs.com/png.latex?%5Csigma"> means heavier smoothing.</p>
<p><img src="https://latex.codecogs.com/png.latex?q_%5Cphi"> is an <em>algorithmic knob</em> (where we probe and how widely we average), not a belief distribution.</p>
<section id="contrast-with-bayesian-posterior-inference" class="level4" data-number="6.1.1">
<h4 data-number="6.1.1" class="anchored" data-anchor-id="contrast-with-bayesian-posterior-inference"><span class="header-section-number">6.1.1</span> Contrast with Bayesian posterior inference</h4>
<p>In Bayesian inference, we specify a generative model and then compute a posterior. <img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctheta%5Cmid%20D)%20%5Cpropto%20p(D%5Cmid%20%5Ctheta)%5C,p(%5Ctheta),%0A"> This represents <em>uncertainty about <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> given data</em>.</p>
<p>In ES/VO, we do <strong>not</strong> posit a likelihood <img src="https://latex.codecogs.com/png.latex?p(D%5Cmid%5Ctheta)"> (or a prior), and we do <strong>not</strong> try to approximate <img src="https://latex.codecogs.com/png.latex?p(%5Ctheta%5Cmid%20D)">. We just choose <img src="https://latex.codecogs.com/png.latex?q_%5Cphi"> so that:</p>
<ul>
<li>sampling from it gives us candidate parameters to try, and</li>
<li>adjusting <img src="https://latex.codecogs.com/png.latex?%5Cphi"> increases <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D_%7B%5Ctheta%5Csim%20q_%5Cphi%7D%5BF(%5Ctheta)%5D">.</li>
</ul>
<p>So <img src="https://latex.codecogs.com/png.latex?q_%5Cphi"> is a <em>proposal/search distribution</em> (and a smoothing kernel), not a posterior approximation.</p>
<p>If we later <em>repurpose</em> ES/NES to optimize an ELBO for variational Bayes, then we really do have a posterior target—but that’s a different use case than “ES as an optimizer.”</p>
</section>
</section>
<section id="es-as-kl-optimization-in-distribution-space" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="es-as-kl-optimization-in-distribution-space"><span class="header-section-number">6.2</span> ES as KL optimization in distribution space</h3>
<p>A clean way to think about it is that ES optimizes over a parametric search distribution <img src="https://latex.codecogs.com/png.latex?p_%5Cphi(%5Ctheta)"> rather than over a single point <img src="https://latex.codecogs.com/png.latex?%5Ctheta">. We define expected fitness <img src="https://latex.codecogs.com/png.latex?%0AJ(%5Cphi)%20=%20%5Cmathbb%7BE%7D_%7B%5Ctheta%20%5Csim%20p_%5Cphi%7D%5BF(%5Ctheta)%5D%0A"> We then update <img src="https://latex.codecogs.com/png.latex?%5Cphi"> to increase <img src="https://latex.codecogs.com/png.latex?J(%5Cphi)">. Natural Evolution Strategies (NES) use the <a href="../notebook/gd_natural.html">natural gradient</a> for this update, which makes the step invariant to reparameterizations of <img src="https://latex.codecogs.com/png.latex?%5Cphi"> <span class="citation" data-cites="Wierstra2008Natural">(Wierstra et al. 2008)</span>.</p>
<p>The Information-Geometric Optimization (IGO) framework generalizes this: it defines a canonical natural-gradient flow for <em>any</em> smooth parametric family <img src="https://latex.codecogs.com/png.latex?p_%5Cphi">, and recovers NES and variants of CMA-ES as special cases <span class="citation" data-cites="Ollivier2017Informationgeometric">(Ollivier et al. 2017)</span>.</p>
<p>The “Bayesian flavour” here is geometric: we can view each update as nudging the distribution a small amount in KL/Fisher distance while improving expected (quantile-shaped) fitness. That is mathematically close to the trust-region and natural-gradient methods used in variational inference, even though we’re aiming for “good solutions” rather than a data-driven posterior.</p>
</section>
<section id="es-as-variational-optimization" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="es-as-variational-optimization"><span class="header-section-number">6.3</span> ES as “variational optimization”</h3>
<p>Another explicit bridge is variational optimization (VO): we replace a hard (possibly non-differentiable) objective <img src="https://latex.codecogs.com/png.latex?F(%5Ctheta)"> with its expectation under a noise distribution. <img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20F(%5Cmu)%20=%20%5Cmathbb%7BE%7D_%7B%5Cvarepsilon%20%5Csim%20q%7D%20%5BF(%5Cmu%20+%20%5Csigma%20%5Cvarepsilon)%5D,%0A"> Then we optimize <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20F">. This is exactly the Gaussian-smoothing view of ES, and we can write the gradient using a <a href="../notebook/score_function_estimator.html">score-function identity</a> (no backprop through <img src="https://latex.codecogs.com/png.latex?F">) <span class="citation" data-cites="Staines2012Variational">(Staines and Barber 2012)</span>.</p>
<p>Why it looks like Bayes: VO and variational inference both optimize an objective defined by an expectation under a parameterized distribution, and both naturally use score-function and natural-gradient machinery. The difference is what we’re approximating: in VO/ES we’re not approximating a posterior <img src="https://latex.codecogs.com/png.latex?p(%5Ctheta%5Cmid%20%5Ctext%7Bdata%7D)">; we’re using a distribution as a <em>smoothing/search device</em>.</p>
</section>
<section id="using-neses-as-a-tool-for-bayesian-posterior-inference" class="level3" data-number="6.4">
<h3 data-number="6.4" class="anchored" data-anchor-id="using-neses-as-a-tool-for-bayesian-posterior-inference"><span class="header-section-number">6.4</span> Using NES/ES as a tool for Bayesian posterior inference</h3>
<p>The most literal connection is using ES/NES as the gradient estimator inside variational Bayesian inference. Here the objective is explicitly Bayesian (e.g.&nbsp;the ELBO), but gradients may be hard to compute (non-reparameterizable families, awkward simulators, etc.). <span class="citation" data-cites="Amin2023Natural">Amin (2023)</span> proposes an NES-based black-box estimator for stochastic variational inference (including sequential settings), positioning NES as the workhorse for optimizing a variational posterior when standard low-variance gradients aren’t available.</p>
</section>
</section>
<section id="incoming" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="incoming"><span class="header-section-number">7</span> Incoming</h2>
<ul>
<li><p><a href="https://github.com/google/evojax/?ref=danmackinlay.name">google/evojax</a></p></li>
<li><p>EvoTorch</p>
<ul>
<li><a href="https://github.com/nnaisense/evotorch?ref=danmackinlay.name">nnaisense/evotorch: Advanced evolutionary computation library built directly on top of PyTorch, created at NNAISENSE.</a></li>
<li><a href="https://evotorch.ai/?ref=danmackinlay.name">EvoTorch</a></li>
</ul></li>
</ul>
</section>
<section id="references" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="references"><span class="header-section-number">8</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Amin2023Natural" class="csl-entry">
Amin. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2308.08053">Natural Evolution Strategies as a Black Box Estimator for Stochastic Variational Inference</a>.”</span>
</div>
<div id="ref-Benhamou2019BCMAES" class="csl-entry">
Benhamou, Saltiel, Verel, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1904.01401">BCMA-ES: A Bayesian Approach to CMA-ES</a>.”</span>
</div>
<div id="ref-Beyer1995Theory" class="csl-entry">
Beyer. 1995. <span>“<a href="https://doi.org/10.1162/evco.1995.3.3.311">Toward a Theory of Evolution Strategies: Self-Adaptation</a>.”</span> <em>Evolutionary Computation</em>.
</div>
<div id="ref-Beyer2002Evolution" class="csl-entry">
Beyer, and Schwefel. 2002. <span>“<a href="https://doi.org/10.1023/A:1015059928466">Evolution Strategies – A Comprehensive Introduction</a>.”</span> <em>Natural Computing</em>.
</div>
<div id="ref-Brochu2010Tutorial" class="csl-entry">
Brochu, Cora, and Freitas. 2010. <span>“<a href="https://doi.org/10.48550/arXiv.1012.2599">A Tutorial on Bayesian Optimization of Expensive Cost Functions, with Application to Active User Modeling and Hierarchical Reinforcement Learning</a>.”</span>
</div>
<div id="ref-Floreano2008BioInspired" class="csl-entry">
Floreano, and Mattiussi. 2008. <em>Bio-Inspired Artificial Intelligence: Theories, Methods, and Technologies (Intelligent Robotics and Autonomous Agents)</em>.
</div>
<div id="ref-Gallagher2007Bayesianb" class="csl-entry">
Gallagher, Wood, Keith, et al. 2007. <span>“<a href="https://doi.org/10.1109/CEC.2007.4424463">Bayesian Inference in Estimation of Distribution Algorithms</a>.”</span> In <em>2007 IEEE Congress on Evolutionary Computation</em>.
</div>
<div id="ref-Glasmachers2010Exponential" class="csl-entry">
Glasmachers, Schaul, Yi, et al. 2010. <span>“<a href="https://doi.org/10.1145/1830483.1830557">Exponential Natural Evolution Strategies</a>.”</span> In <em>Proceedings of the 12th Annual Conference on Genetic and Evolutionary Computation</em>. GECCO ’10.
</div>
<div id="ref-Hansen2023CMA" class="csl-entry">
Hansen. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.1604.00772">The CMA Evolution Strategy: A Tutorial</a>.”</span>
</div>
<div id="ref-Hansen2001Completely" class="csl-entry">
Hansen, and Ostermeier. 2001. <span>“<a href="https://doi.org/10.1162/106365601750190398">Completely Derandomized Self-Adaptation in Evolution Strategies</a>.”</span> <em>Evolutionary Computation</em>.
</div>
<div id="ref-Lenc2019NonDifferentiable" class="csl-entry">
Lenc, Elsen, Schaul, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1906.03139">Non-Differentiable Supervised Learning with Evolution Strategies and Hybrid Methods</a>.”</span>
</div>
<div id="ref-Ollivier2017Informationgeometric" class="csl-entry">
Ollivier, Arnold, Auger, et al. 2017. <span>“<a href="http://arxiv.org/abs/1106.3708">Information-Geometric Optimization Algorithms: A Unifying Picture via Invariance Principles</a>.”</span> <em>J. Mach. Learn. Res.</em>
</div>
<div id="ref-Rechenberg1978Evolutionsstrategien" class="csl-entry">
Rechenberg. 1978. <span>“<a href="https://doi.org/10.1007/978-3-642-81283-5_8">Evolutionsstrategien</a>.”</span> In <em>Simulationsmethoden in Der Medizin Und Biologie</em>. Medizinische Informatik Und Statistik.
</div>
<div id="ref-Staines2012Variational" class="csl-entry">
Staines, and Barber. 2012. <span>“<a href="http://arxiv.org/abs/1212.4507">Variational Optimization</a>.”</span>
</div>
<div id="ref-Sun2009Efficient" class="csl-entry">
Sun, Wierstra, Schaul, et al. 2009. <span>“<a href="https://doi.org/10.1145/1569901.1569976">Efficient Natural Evolution Strategies</a>.”</span> In <em>Proceedings of the 11th Annual Conference on Genetic and Evolutionary Computation</em>. GECCO ’09.
</div>
<div id="ref-Whitley1990Genetic" class="csl-entry">
Whitley, Starkweather, and Bogart. 1990. <span>“<a href="https://doi.org/10.1016/0167-8191(90)90086-O">Genetic Algorithms and Neural Networks: Optimizing Connections and Connectivity</a>.”</span> <em>Parallel Computing</em>.
</div>
<div id="ref-Wierstra2008Natural" class="csl-entry">
Wierstra, Schaul, Peters, et al. 2008. <span>“<a href="https://doi.org/10.1109/CEC.2008.4631255">Natural Evolution Strategies</a>.”</span> In <em>2008 IEEE Congress on Evolutionary Computation (IEEE World Congress on Computational Intelligence)</em>.
</div>
</div>


</section>

 ]]></description>
  <category>Bayes</category>
  <category>distributed</category>
  <category>dynamical systems</category>
  <category>likelihood free</category>
  <category>linear algebra</category>
  <category>machine learning</category>
  <category>Monte Carlo</category>
  <category>neural nets</category>
  <category>nonparametric</category>
  <category>optimization</category>
  <category>particle</category>
  <category>probabilistic algorithms</category>
  <category>probability</category>
  <category>sciml</category>
  <category>signal processing</category>
  <category>sparser than thou</category>
  <category>statistics</category>
  <category>statmech</category>
  <category>uncertainty</category>
  <guid>https://danmackinlay.name/notebook/evolution_strategy.html</guid>
  <pubDate>Mon, 16 Feb 2026 11:41:46 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/hat_evolution_RP-P-OB-30.633.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Evolution strategies</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/nn_evolution_strategy.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-pitcher-plants" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pitcher-plants-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/pitcher_plants.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/pitcher_plants.png" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-pitcher-plants-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>A <a href="../notebook/biomimetic_algorithms.html">nature-inspired</a> <a href="../notebook/nn_ensemble.html">ensemble</a> strategy for training neural nets that uses <a href="../notebook/genetic_programming.html">evolution-like</a> approaches, even though we usually assume that <a href="../notebook/nn_backprop_free.html">scale can only be achieved via backprop</a>.</p>
<p>For the basics, see <a href="../notebook/evolution_strategy.html#evolution-strategies-for-beginners">Evolution Strategies for beginners</a>.</p>
<section id="a-running-example" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="a-running-example"><span class="header-section-number">1</span> A running example</h2>
<p>We’re reusing the running example from the ES-for-beginners post. Let us consider the challenge of <a href="../notebook/nn_backprop_free.html">training a tiny neural net <em>without</em> backprop</a>.</p>
<p>We have a simple classifier <img src="https://latex.codecogs.com/png.latex?f_%5Ctheta(x)"> (say, a two-layer MLP). The standard supervised objective is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5Ctheta)%20=%20%5Cmathbb%7BE%7D_%7B(x,y)%5Csim%20%5Cmathcal%7BD%7D%7D%5Cbig%5B%5Cell(f_%5Ctheta(x),%20y)%5Cbig%5D.%0A"></p>
<p>Backpropagation gives us <img src="https://latex.codecogs.com/png.latex?%5Cnabla_%5Ctheta%20L(%5Ctheta)">. ES assumes we <em>won’t</em> use that, and instead treats “run the model forward, then compute the loss” as a black box we can call:</p>
<ul>
<li>Input: the parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta"></li>
<li>Output: a scalar score (loss, reward, accuracy, etc.)</li>
</ul>
<p>To match the usual ES maximization setup, we define a <a href="../notebook/utility_fitness.html"><em>fitness</em> function</a> <img src="https://latex.codecogs.com/png.latex?F(%5Ctheta)"> to <em>maximize</em>, for example:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AF(%5Ctheta)%20=%20-L(%5Ctheta).%0A"></p>
<p>The ES loop looks like this:</p>
<ol type="1">
<li>Sample random perturbations <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon_i">.</li>
<li>Evaluate fitness on the perturbed parameters <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon_i">.</li>
<li>Combine the results to update <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.</li>
</ol>
<p>Here <img src="https://latex.codecogs.com/png.latex?%5Csigma%3E0"> is the “noise scale” (how far we probe). A standard choice is Gaussian perturbations (why is it always Gaussian? It makes it feel like we’re doing <a href="../notebook/mcmc_langevin.html">SGLD</a>):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cvarepsilon%20%5Csim%20%5Cmathcal%7BN%7D(0,%20I).%0A"></p>
<p>Define a <em>smoothed objective</em>. This is what ES targets.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ(%5Ctheta)%20=%20%5Cmathbb%7BE%7D_%7B%5Cvarepsilon%20%5Csim%20%5Cmathcal%7BN%7D(0,I)%7D%5Cbig%5BF(%5Ctheta%20+%20%5Csigma%20%5Cvarepsilon)%5Cbig%5D.%0A"></p>
<p>Backprop optimizes <img src="https://latex.codecogs.com/png.latex?F(%5Ctheta)"> directly (when it can). ES typically optimizes a <em>Gaussian-smoothed</em> version <img src="https://latex.codecogs.com/png.latex?J(%5Ctheta)">.</p>
</section>
<section id="large-data" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="large-data"><span class="header-section-number">2</span> Large data variants</h2>
<p>So far this seems reasonable, but it also seems to assume full-batch losses rather than stochastic mini-batches, which, as <a href="../notebook/gd_1st_order_stochastic.html">SGD</a> argues, are the secret to NN scaling. In the basic version, it sounds like we need the full-data likelihood, which is a non-starter.</p>
<p>What does ES do instead to scale to large data? What are the analogues of mini-batches and SGD updates?</p>
<p>I asked an LLM to soothe my qualms, and it did, citing <span class="citation" data-cites="Lenc2019NonDifferentiable Salimans2017Evolution">(Lenc et al. 2019; Salimans et al. 2017)</span></p>
<blockquote class="blockquote">
<p>In practice you do the same thing you do with SGD: replace the true objective (an expectation over the data distribution) with a stochastic estimate from a minibatch, and treat the resulting extra noise as part of your gradient-estimation noise.</p>
<p>Write the population objective as an expectation over data <img src="https://latex.codecogs.com/png.latex?z%5Csim%20%5Cmathcal%7BD%7D"> (example <img src="https://latex.codecogs.com/png.latex?z=(x,y)">):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AF(%5Ctheta)%20%5C;=%5C;%20%5Cmathbb%7BE%7D_%7Bz%5Csim%5Cmathcal%7BD%7D%7D%5Cbig%5Bf(%5Ctheta;z)%5Cbig%5D%0A"></p>
<p>(For supervised learning you can think <img src="https://latex.codecogs.com/png.latex?f(%5Ctheta;z)=-%5Cell(f_%5Ctheta(x),y)">.)</p>
<p>Given a minibatch <img src="https://latex.codecogs.com/png.latex?B=%5C%7Bz_j%5C%7D_%7Bj=1%7D%5Eb">, define the minibatch fitness</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%20F(%5Ctheta;B)%20%5C;=%5C;%20%5Cfrac%7B1%7D%7Bb%7D%5Csum_%7Bj=1%7D%5Eb%20f(%5Ctheta;z_j).%0A"></p>
<p>Now you just plug <img src="https://latex.codecogs.com/png.latex?%5Chat%20F"> into the standard ES estimator. With antithetic pairs:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%20g(%5Ctheta;B)=%5Cfrac%7B1%7D%7B2N%5Csigma%7D%5Csum_%7Bi=1%7D%5EN%0A%5CBig(%5Chat%20F(%5Ctheta+%5Csigma%5Cvarepsilon_i;B)-%5Chat%20F(%5Ctheta-%5Csigma%5Cvarepsilon_i;B)%5CBig)%5C,%5Cvarepsilon_i,%0A%5Cqquad%20%5Cvarepsilon_i%5Csim%5Cmathcal%20N(0,I).%0A"></p>
<p>and update <img src="https://latex.codecogs.com/png.latex?%5Ctheta%5Cleftarrow%20%5Ctheta%20+%20%5Ceta%5C,%5Chat%20g">.</p>
<p>Key practical detail: <strong>use the same minibatch <img src="https://latex.codecogs.com/png.latex?B"> for every member of the population within an iteration</strong>, and for both <img src="https://latex.codecogs.com/png.latex?+%5Cvarepsilon_i"> and <img src="https://latex.codecogs.com/png.latex?-%5Cvarepsilon_i">. This “common random numbers” idea at the data sampling level makes the <em>difference</em> <img src="https://latex.codecogs.com/png.latex?%5Chat%20F(%5Ctheta+%5Csigma%5Cvarepsilon_i;B)-%5Chat%20F(%5Ctheta-%5Csigma%5Cvarepsilon_i;B)"> cancel a lot of minibatch noise and makes antithetic sampling actually do its job. If each perturbation sees a different minibatch, we inject extra variance that antithetic pairing cannot cancel.</p>
<p>A minimal “SGD-like” recipe:</p>
<ol type="1">
<li>Sample a minibatch <img src="https://latex.codecogs.com/png.latex?B">.</li>
<li>Sample perturbations <img src="https://latex.codecogs.com/png.latex?%5C%7B%5Cvarepsilon_i%5C%7D_%7Bi=1%7D%5EN"> (and use antithetic pairs).</li>
<li>Evaluate <img src="https://latex.codecogs.com/png.latex?%5Chat%20F(%5Ctheta%5Cpm%5Csigma%5Cvarepsilon_i;B)"> in parallel.</li>
<li>Form <img src="https://latex.codecogs.com/png.latex?%5Chat%20g(%5Ctheta;B)">, optionally normalize/center fitness within the population.</li>
<li>Update <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.</li>
</ol>
<p>This looks like SGD, but the gradient is estimated by perturb-and-evaluate rather than backprop. The minibatch adds stochasticity exactly the way it does in SGD: it doesn’t “break” the algorithm, but it affects the variance and therefore the batch size / population size you need for stable progress.</p>
</blockquote>
</section>
<section id="es-at-very-large-scale" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="es-at-very-large-scale"><span class="header-section-number">3</span> ES at very large scale</h2>
<p><a href="https://eshyperscale.github.io/?ref=danmackinlay.name">Evolution Strategies at the Hyperscale</a> <span class="citation" data-cites="Sarkar2025Evolution">(Sarkar et al. 2025)</span> pushes this really far, and I’m pretty fascinated by what the authors pulled off. More soon.</p>
</section>
<section id="incoming" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="incoming"><span class="header-section-number">4</span> Incoming</h2>
<ul>
<li><p><a href="https://github.com/ESHyperscale/HyperscaleES?ref=danmackinlay.name">ESHyperscale/HyperscaleES: JAX Codebase for Evolutionary Strategies at the Hyperscale</a></p></li>
<li><p><a href="https://github.com/google/evojax/?ref=danmackinlay.name">google/evojax</a></p></li>
<li><p>evotorch</p>
<ul>
<li><a href="https://github.com/nnaisense/evotorch?ref=danmackinlay.name">nnaisense/evotorch: Advanced evolutionary computation library built directly on top of PyTorch, created at NNAISENSE.</a></li>
<li><a href="https://evotorch.ai/?ref=danmackinlay.name">EvoTorch</a></li>
</ul></li>
</ul>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Beyer1995Theory" class="csl-entry">
Beyer. 1995. <span>“<a href="https://doi.org/10.1162/evco.1995.3.3.311">Toward a Theory of Evolution Strategies: Self-Adaptation</a>.”</span> <em>Evolutionary Computation</em>.
</div>
<div id="ref-Beyer2002Evolution" class="csl-entry">
Beyer, and Schwefel. 2002. <span>“<a href="https://doi.org/10.1023/A:1015059928466">Evolution Strategies – A Comprehensive Introduction</a>.”</span> <em>Natural Computing</em>.
</div>
<div id="ref-Bown2006ContinuousTime" class="csl-entry">
Bown, and Lexer. 2006. <span>“<a href="http://link.springer.com/chapter/10.1007/11732242_62">Continuous-Time Recurrent Neural Networks for Generative and Interactive Musical Performance</a>.”</span> In <em>Applications of Evolutionary Computing</em>. Lecture Notes in Computer Science 3907.
</div>
<div id="ref-Floreano2008BioInspired" class="csl-entry">
Floreano, and Mattiussi. 2008. <em>Bio-Inspired Artificial Intelligence: Theories, Methods, and Technologies (Intelligent Robotics and Autonomous Agents)</em>.
</div>
<div id="ref-Hansen2023CMA" class="csl-entry">
Hansen. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.1604.00772">The CMA Evolution Strategy: A Tutorial</a>.”</span>
</div>
<div id="ref-Hansen2001Completely" class="csl-entry">
Hansen, and Ostermeier. 2001. <span>“<a href="https://doi.org/10.1162/106365601750190398">Completely Derandomized Self-Adaptation in Evolution Strategies</a>.”</span> <em>Evolutionary Computation</em>.
</div>
<div id="ref-Lenc2019NonDifferentiable" class="csl-entry">
Lenc, Elsen, Schaul, et al. 2019. <span>“<a href="https://doi.org/10.48550/arXiv.1906.03139">Non-Differentiable Supervised Learning with Evolution Strategies and Hybrid Methods</a>.”</span>
</div>
<div id="ref-Rechenberg1978Evolutionsstrategien" class="csl-entry">
Rechenberg. 1978. <span>“<a href="https://doi.org/10.1007/978-3-642-81283-5_8">Evolutionsstrategien</a>.”</span> In <em>Simulationsmethoden in Der Medizin Und Biologie</em>. Medizinische Informatik Und Statistik.
</div>
<div id="ref-Salimans2017Evolution" class="csl-entry">
Salimans, Ho, Chen, et al. 2017. <span>“<a href="http://arxiv.org/abs/1703.03864">Evolution Strategies as a Scalable Alternative to Reinforcement Learning</a>.”</span>
</div>
<div id="ref-Sarkar2025Evolution" class="csl-entry">
Sarkar, Fellows, Duque, et al. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2511.16652">Evolution Strategies at the Hyperscale</a>.”</span>
</div>
<div id="ref-Vanchurin2021Theory" class="csl-entry">
Vanchurin, Wolf, Katsnelson, et al. 2021. <span>“<a href="https://doi.org/10.1101/2021.11.03.466494">Towards a Theory of Evolution as Multilevel Learning</a>.”</span>
</div>
<div id="ref-Whitley1990Genetic" class="csl-entry">
Whitley, Starkweather, and Bogart. 1990. <span>“<a href="https://doi.org/10.1016/0167-8191(90)90086-O">Genetic Algorithms and Neural Networks: Optimizing Connections and Connectivity</a>.”</span> <em>Parallel Computing</em>.
</div>
</div>


</section>

 ]]></description>
  <category>Bayes</category>
  <category>distributed</category>
  <category>dynamical systems</category>
  <category>likelihood free</category>
  <category>linear algebra</category>
  <category>machine learning</category>
  <category>Monte Carlo</category>
  <category>neural nets</category>
  <category>nonparametric</category>
  <category>optimization</category>
  <category>particle</category>
  <category>probabilistic algorithms</category>
  <category>probability</category>
  <category>sciml</category>
  <category>signal processing</category>
  <category>sparser than thou</category>
  <category>statistics</category>
  <category>statmech</category>
  <category>uncertainty</category>
  <guid>https://danmackinlay.name/notebook/nn_evolution_strategy.html</guid>
  <pubDate>Mon, 16 Feb 2026 11:41:46 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/pitcher_plants.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Boundaries and blankets</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/boundaries_blankets.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-lady-arrows" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-lady-arrows-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/lady_arrows_KOG-ZG-1-16-175.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/lady_arrows_KOG-ZG-1-16-175.avif" id="fig-lady-arrows" class="figure illustration right img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-lady-arrows-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>If we have a big complicated system — a brain, a corporation, an ecosystem, an AI agent loop — <a href="../notebook/which_self.html">where do we draw the line around “the thing”</a>?</p>
<p>One formalization is the <em>Markov blanket</em>, a piece of standard <a href="../notebook/graphical_models.html">graphical models</a> machinery which has recently been conscripted into service as a theory of selfhood, agency, and alignment. I want to walk through how that happened, where I think it goes wrong, and what the recent formalising-boundaries workshop programme has done about it.</p>
<section id="markov-blankets" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="markov-blankets"><span class="header-section-number">1</span> Markov blankets</h2>
<p>Pick a node <img src="https://latex.codecogs.com/png.latex?X"> in a graphical model. Its <em>Markov blanket</em> <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7BMB%7D(X)"> is the smallest set of other nodes such that <img src="https://latex.codecogs.com/png.latex?X"> is conditionally independent of everything else given <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7BMB%7D(X)">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AX%20%5Cperp%5C!%5C!%5C!%5Cperp%20%5Cbigl(V%20%5Csetminus%20(%5C%7BX%5C%7D%20%5Ccup%20%5Coperatorname%7BMB%7D(X))%5Cbigr)%20%5C;%5CBig%7C%5C;%20%5Coperatorname%7BMB%7D(X)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?V"> is the full set of nodes. In an <a href="../notebook/graphical_models_undirected.html">undirected graph</a> this is just the neighbours of <img src="https://latex.codecogs.com/png.latex?X">. In a <a href="../notebook/graphical_models_directed.html">DAG</a> it is <img src="https://latex.codecogs.com/png.latex?X">’s parents, <img src="https://latex.codecogs.com/png.latex?X">’s children, and the other parents of <img src="https://latex.codecogs.com/png.latex?X">’s children (the “co-parents” or “spouses” — nodes that share a child with <img src="https://latex.codecogs.com/png.latex?X">).</p>
<p>If we know everything in the blanket, then learning anything else about the rest of the universe tells us <em>nothing</em> new about <img src="https://latex.codecogs.com/png.latex?X">. The blanket is an informational shield. Pearl’s term for it is a statistical boundary condition: everything the outside world can tell us about <img src="https://latex.codecogs.com/png.latex?X"> has to pass through the blanket first.</p>
</section>
<section id="friston-blankets-as-selfhood" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="friston-blankets-as-selfhood"><span class="header-section-number">2</span> Friston: blankets as selfhood</h2>
<p>Karl Friston and collaborators <span class="citation" data-cites="Kirchhoff2018Markov">(Kirchhoff et al. 2018)</span> take this statistical gadget and do something ambitious with it: they propose that <em>having a Markov blanket</em> is roughly what it means to be a “thing” that persists in an environment. A cell membrane is a blanket separating internal states from the outside world; skin separates physiological states from the weather; the boundary of a firm separates internal operations from the market.</p>
<p>They further partition the blanket itself into <em>sensory</em> states (outside influences inside) and <em>active</em> states (inside influences outside). Write <img src="https://latex.codecogs.com/png.latex?%5Cmu"> for internal states, <img src="https://latex.codecogs.com/png.latex?%5Ceta"> for external, <img src="https://latex.codecogs.com/png.latex?s"> for sensory, <img src="https://latex.codecogs.com/png.latex?a"> for active. The blanket is <img src="https://latex.codecogs.com/png.latex?b%20=%20s%20%5Ccup%20a">, and the claim is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu%20%5Cperp%5C!%5C!%5C!%5Cperp%20%5Ceta%20%5Cmid%20b,%20%5Cqquad%0A%5Cdot%7B%5Cmu%7D%20=%20f(%5Cmu,%20s),%20%5Cqquad%0A%5Cdot%7Ba%7D%20=%20g(%5Cmu,%20a)%0A"></p>
<p>Internal states evolve based only on themselves and sensory input; active states evolve based only on internal states and themselves. So far this is just the conditional independence property dressed up in dynamical systems notation.</p>
<p>The substantive claim comes from the <a href="../notebook/predictive_coding.html#free-energy">free energy principle</a>: anything that persists — anything that doesn’t just dissolve into thermal noise — must act <em>as if</em> it is minimising surprise at its blanket boundary. Note the slippage: we have gone from a static graphical model property to a claim about the flow of a stochastic differential equation. Whether the machinery of graphical models straightforwardly applies in that continuous-time setting is part of what makes the whole free-energy research programme hard to evaluate.</p>
</section>
<section id="the-trouble-with-blankets" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-trouble-with-blankets"><span class="header-section-number">3</span> The trouble with blankets</h2>
<p>Bruineberg et al. <span class="citation" data-cites="Bruineberg2022Emperors">(Bruineberg et al. 2022)</span> raise the obvious worry: we can <em>always</em> find a Markov blanket partition if we look hard enough. Any partition of states into “inside” and “outside” with some intermediary layer will satisfy the conditional independence criterion, given sufficiently accommodating dynamics. So the existence of a blanket does not, by itself, tell us that the thing inside is autonomous in any interesting sense. A hurricane has a blanket. A rock has a blanket. My coffee cup, right now, has a blanket.</p>
<p>This is the central problem, and it is the same problem we run into with <a href="../notebook/agency.html">agency</a> more generally: the formal criterion is too easy to satisfy. What we want is some way to say that <em>this</em> blanket is <em>the interesting one</em> — that this particular partition carves the world at its joints. Choosing a Markov blanket partition is a modelling decision, the same way choosing <a href="../notebook/which_self.html">which self</a> to identify with is a modelling decision. The blanket formalism does not solve the boundary problem; it reframes it in graphical-model language. That reframing is useful — it lets us be precise about what information crosses the boundary, and it connects to actual mathematical tools — but the hard question of <em>which partition to pick</em> remains, and it is doing all the work.</p>
</section>
<section id="other-ways-of-drawing-the-line" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="other-ways-of-drawing-the-line"><span class="header-section-number">4</span> Other ways of drawing the line</h2>
<p>Several researchers are attacking the carving problem from outside the Friston paradigm.</p>
<p><span class="citation" data-cites="Levin2019Computational">Levin (2019)</span> argues that the “self” in a developing organism is scale-free: cells, tissues, organs, and the whole organism each operate as <a href="../notebook/multi_scale_agency.html">agents with their own goals</a>, nested inside each other. There is no single correct blanket; there are blankets at every scale, and the interesting question is how they compose. (Cf. <a href="../notebook/multi_agent_self.html">multi-agent selves</a>, <a href="../notebook/which_self.html">which self</a>.)</p>
<p><span class="citation" data-cites="Fontana1996Barrier">Fontana and Buss (1996)</span> come at it from autopoiesis and formal chemistry. A <em>barrier of objects</em> is what allows bounded organizations to persist; the boundary isn’t just informational but <em>constructive</em> — it’s the set of processes that maintain themselves. This is a stronger condition than conditional independence, and possibly closer to what we actually mean when we say something is a “self.”</p>
<p><span class="citation" data-cites="Lewandowski2025World">Lewandowski et al. (2025)</span> consider what happens when the world is too big for the agent to model. If we can’t represent the whole environment, our effective Markov blanket is partly a function of our computational limitations — we’re not so much discovering the boundary as being <em>forced into one</em> by our own finitude. The boundary of the self is, in part, a boundary of affordance — cf <a href="../notebook/causally_embedded_agency.html">causally embedded agency</a>.</p>
</section>
<section id="critch-boundaries-before-preferences" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="critch-boundaries-before-preferences"><span class="header-section-number">5</span> Critch: boundaries before preferences</h2>
<p>The most sustained attempt at a formal theory comes from Andrew Critch, whose <a href="https://www.lesswrong.com/s/LWJsgNYE8wzv49yEc/p/8oMF8Lv5jiGaQSFvo?ref=danmackinlay.name">«Boundaries» sequence</a> argues that standard utility theory has no formal concept of where the agent stops and the environment starts. In a game-theoretic model, the partition of the world into <em>players</em> — who has which action space, which observation space, which utility function — is given exogenously. We just declare it. That declaration is doing enormous work, and if we get it wrong (or if it is ambiguous, or if it shifts over time) then the whole decision-theoretic apparatus is sitting on sand.</p>
<p>Critch’s bet, as I understand it, is that boundaries are <em>more fundamental than preferences</em> — they should be the primitive we build up from, not something we derive from utility functions. Friston says: the blanket is wherever the conditional independence structure puts it. Critch says: we don’t even have the right formalism to ask the question properly within utility theory. Both are pointing at the same hole from opposite sides.</p>
<p>In <a href="https://www.lesswrong.com/posts/HrtqLy46Fx7xqRrMo/boundaries-part-3a-defining-boundaries-as-directed-markov?ref=danmackinlay.name">Part 3a of the sequence</a>, Critch proposes a specific formalization. Partition the world’s variables into four groups — <strong>V</strong>iscera, <strong>A</strong>ctive boundary, <strong>P</strong>assive boundary, <strong>E</strong>nvironment — and treat this as a Bayesian network.<sup>1</sup> The viscera are the guts of the agent, the states it has close to full causal control over. The active boundary A is influenced primarily by the viscera — interpretable as actions. The passive boundary P is influenced primarily by the environment — interpretable as perceptions. The boundary <img src="https://latex.codecogs.com/png.latex?B%20=%20A%20%5Ccup%20P"> is then a <em>directed</em> Markov blanket: it approximately d-separates V from E, with a discernible direction of inward (perception) and outward (action) information flow.</p>
<p><em>Infiltration</em> is information leaking from the environment into the active boundary and viscera; <em>exfiltration</em> is information leaking from the viscera into the passive boundary and environment. When both are zero, we have a perfect boundary. In practice they are nonzero, and measuring how far we are from zero gives us a graded notion of boundary integrity — which is to say, an approximate, graded notion of <a href="../notebook/agency.html">agency</a>.</p>
<p>VAPE is recognisably a descendant of Friston’s sensory/active partition, reframed in causal DAG language rather than stochastic differential equations. Whether the causal DAG framing is the right one is <a href="https://www.alignmentforum.org/posts/vmfNaKbZ6urMdQrv2/agent-boundaries-aren-t-markov-blankets-unless-they-re-non?ref=danmackinlay.name">contested</a> — in a model with complete state information, everything becomes trivially independent, so the blanket concept may need a non-causal or non-equilibrium reformulation.</p>
<p>The payoff Critch is after is in <a href="../notebook/alignment_problems.html">alignment</a>. Many things we try to capture as “minimise side effects” or “avoid over-optimization” can be stated more precisely as <em>respect for boundaries</em>: don’t infiltrate other agents’ viscera, don’t exfiltrate past their passive boundaries. The claim is that these coordination norms are <a href="https://www.lesswrong.com/posts/ndNHxbkStrfLfQHuA/respect-for-boundaries-as-non-arbirtrary-coordination-norms?ref=danmackinlay.name">not arbitrary</a> — they transfer across species and scales of organization, unlike preference-based norms which need to be elicited per agent.</p>
<p>But if we’re choosing who to become — making <a href="../notebook/intertemporal_decisions.html">intertemporal decisions</a> on behalf of a future person who may not share our current preferences — then the boundary of the agent is not stable over time. The self doing the optimising and the self being optimised-for are not obviously the same system, and AFAICT no current formalism handles that.</p>
</section>
<section id="the-formalising-boundaries-workshops" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="the-formalising-boundaries-workshops"><span class="header-section-number">6</span> The formalising-boundaries workshops</h2>
<p>Chris Lakin, Manuel Baltieri, and Evan Miyazono organised a <a href="https://formalizingboundaries.ai/?ref=danmackinlay.name">Conceptual Boundaries Workshop</a> (Austin, February 2024), then a <a href="https://blog.atlascomputing.org/p/mathematical-boundaries-retro?ref=danmackinlay.name">Mathematical Boundaries Workshop</a> (April 2024, five days). Attendees included Critch, davidad, Scott Garrabrant, Abram Demski, and a mix of people from applied category theory, artificial life, and alignment — roughly 40/40/20 by <a href="https://formalizingboundaries.substack.com/p/evans-retrospective-on-mathematical?ref=danmackinlay.name">Miyazono’s estimate</a>. The conceptual workshop explored what we’d <em>want</em> from a boundaries formalism; the mathematical workshop tried to actually build one.</p>
<p>The main thing that went wrong, per the Topos Institute’s <a href="https://topos.institute/blog/2024-04-25-ontological-commitments-for-boundaries/?ref=danmackinlay.name">post-workshop writeup</a>, is that participants couldn’t agree on the underlying ontology. One person was thinking in terms of sets and Cartesian products; another in terms of fibrations; davidad in terms of polynomial functors. Each translation carried implicit commitments about what “state”, “boundary”, and “dynamics” meant, and these commitments were hard to make explicit, let alone reconcile. This is a familiar problem from <a href="../notebook/graphical_models.html">graphical models</a> generally — Bayesian nets, factor graphs, and Markov random fields all express the same independences but carry different ontological baggage, and people develop strong loyalties to their preferred notation. At the boundaries workshop it was worse, because the objects being modelled (agents, selves, cells) are themselves contested.</p>
<p>Breakout sessions explored a cocategorical formalism (specifying things via the wholes they participate in, rather than by composing parts) and an attempt to formalise <a href="https://en.wikipedia.org/wiki/Glider_(Conway%27s_Game_of_Life)?ref=danmackinlay.name">gliders</a> as non-deterministic closed dynamical systems. AFAICT neither attempt produced a formalism that the full group adopted afterwards.</p>
<p>The boundary problem, then, appears hard in a specific way: not that we lack mathematical tools, but that we lack agreement on what the tools should be tools <em>for</em>. The statistical tradition wants conditional independence; the game-theoretic tradition wants player partitions; the category-theoretic tradition wants compositional structure; the autopoietic tradition wants self-maintaining processes. All reasonable desiderata, but they pull in different directions. Nobody has a framework that satisfies more than one or two simultaneously.</p>
<p>The <a href="https://www.lesswrong.com/w/boundaries-membranes-technical?ref=danmackinlay.name">LessWrong «Boundaries/Membranes» tag</a> and <a href="https://www.lesswrong.com/posts/fjgoMaBenyXcRDrbX/boundaries-and-ai-safety-compilation?ref=danmackinlay.name">compilation post</a> collect the full archipelago of related work.</p>
</section>
<section id="wish-list" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="wish-list"><span class="header-section-number">7</span> Wish list</h2>
<p>A satisfying theory would, given a dynamical system and some criterion of interest (a loss function, a fitness measure, a <a href="../notebook/utility_if_we_must.html">utility</a>), <em>discover</em> the partition that best predicts or controls the thing we care about, rather than impose one by fiat. That feels adjacent to <a href="../notebook/graphical_models_learning.html">learning graphical structure from data</a> and maybe <a href="../notebook/causal_inference.html">causal discovery</a>, but I do not know of anyone who has made the connection rigorous. It would also have an account of nesting — blankets within blankets, cells within organs within firms within polities — which looks like it should connect to <a href="../notebook/coarse_graining.html">coarse-graining</a> and renormalization, but I have not seen that analogy do real work yet.</p>
<p>And it would, I hope, let us say something precise about <a href="../notebook/ai_agency.html">AI agents</a>. Right now we define their boundaries architecturally — this is the agent, that is the tool, here is the environment. But an LLM with tool access that can read and write its own system prompt has a rather different blanket than one that cannot, even if the architecture diagram looks identical. Which of these boundaries matters for <a href="../notebook/alignment_problems.html">alignment</a>? IMO both, in ways we don’t yet have the formalism to state.</p>
</section>
<section id="incoming" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="incoming"><span class="header-section-number">8</span> Incoming</h2>
<ul>
<li>Jan Kulveit, <a href="https://boundedlyrational.substack.com/p/the-pando-problem?ref=danmackinlay.name">The Pando Problem</a></li>
</ul>
</section>
<section id="references" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="references"><span class="header-section-number">9</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Biehl2023Interpreting" class="csl-entry">
Biehl, and Virgo. 2023. <span>“<a href="https://doi.org/10.1007/978-3-031-28719-0_2">Interpreting Systems as Solving POMDPs: A Step Towards a Formal Understanding of Agency</a>.”</span> In.
</div>
<div id="ref-Bruineberg2022Emperors" class="csl-entry">
Bruineberg, Dołęga, Dewhurst, et al. 2022. <span>“<a href="https://doi.org/10.1017/S0140525X21002351">The Emperor’s New Markov Blankets</a>.”</span> <em>Behavioral and Brain Sciences</em>.
</div>
<div id="ref-Fontana1996Barrier" class="csl-entry">
Fontana, and Buss. 1996. <span>“The Barrier of Objects: From Dynamical Systems to Bounded Organizations.”</span> In <em>Boundaries and Barriers</em>.
</div>
<div id="ref-Kirchhoff2018Markov" class="csl-entry">
Kirchhoff, Parr, Palacios, et al. 2018. <span>“<a href="https://doi.org/10.1098/rsif.2017.0792">The Markov Blankets of Life: Autonomy, Active Inference and the Free Energy Principle</a>.”</span> <em>Journal of The Royal Society Interface</em>.
</div>
<div id="ref-Levin2019Computational" class="csl-entry">
Levin. 2019. <span>“<a href="https://doi.org/10.3389/fpsyg.2019.02688">The Computational Boundary of a “Self”: Developmental Bioelectricity Drives Multicellularity and Scale-Free Cognition</a>.”</span> <em>Frontiers in Psychology</em>.
</div>
<div id="ref-Lewandowski2025World" class="csl-entry">
Lewandowski, Ramesh, Meyer, et al. 2025. <span>“<a href="https://openreview.net/forum?id=gJclyLFSdU">The World Is Bigger: A Computationally-Embedded Perspective on the Big World Hypothesis</a>.”</span> In.
</div>
<div id="ref-Virgo2021Interpreting" class="csl-entry">
Virgo, Biehl, and McGregor. 2021. <span>“<a href="https://doi.org/10.48550/arXiv.2112.13523">Interpreting Dynamical Systems as Bayesian Reasoners</a>.”</span>
</div>
</div>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Chris Lakin’s <a href="https://www.lesswrong.com/posts/z4o4iAFgnmaBmksN2/formalizing-boundaries-with-markov-blankets?ref=danmackinlay.name">conceptual explainer</a> is a good companion read.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>agents</category>
  <category>economics</category>
  <category>ethics</category>
  <category>extended self</category>
  <category>gene</category>
  <category>graphical models</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>mind</category>
  <category>probability</category>
  <category>quagmire</category>
  <category>utility</category>
  <guid>https://danmackinlay.name/notebook/boundaries_blankets.html</guid>
  <pubDate>Thu, 05 Feb 2026 09:43:23 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/lady_arrows_KOG-ZG-1-16-175.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Categorical systems theory</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/categorical_systems_theory.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-monochrom-spiderflower" class="illustration figure right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-monochrom-spiderflower-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/monochrom_spiderflower_RP-P-1957-523.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/monochrom_spiderflower_RP-P-1957-523.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-monochrom-spiderflower-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p><a href="../notebook/category_theory.html">Category theory</a> for <a href="../notebook/dynamical_systems.html">systems</a>.</p>
<p><span class="citation" data-cites="Myers2023Categorical">Myers (2023)</span>:</p>
<blockquote class="blockquote">
<p>Categorical systems theory is an emerging field of mathematics which seeks to apply the methods of category theory to general systems theory. General systems theory is the study of systems — ways things can be and change, and models thereof — in full generality. The difficulty is that there doesn’t seem to be a single core idea of what it means to be a “system”. Different people have, for different purposes, come up with a vast array of different modeling techniques and definitions that could be called “systems”. There is often little the same in the precise content of these definitions, though there are still strong, if informal, analogies to be made accross these different fields. This makes coming up with a mathematical theory of general systems tantalizing but difficult: what, after all, is a system in general?</p>
<p>Category theory has been describe as the mathematics of formal analogy making. It allows us to make analogies between fields by focusing not on content of the objects of those fields, but by the ways that the objects of those fields relate to one another. Categorical systems theory applies this idea to general systems theory, avoiding the issue of not having a contentful definition of system by instead focusing on the ways that systems interact with eachother and their environment.</p>
<p>These are the main ideas of categorical systems theory: 1. Any system interacts with its environment through an interface, which can be described separately from the system itself. 2. All interactions of a system with its environment take place through its interface, so that from the point of view of the environment, all we need to know about a system is what is going on at the interface. 3. Systems interact with other systems through their respective interfaces. So, to understand complex systems in terms of their component subsystems, we need to understand the ways that interfaces can be connected. We call these ways that interfaces can be connected composition patterns. 4. Given a composition pattern describing how some interface are to be connected, and some systems with those interfaces, we should have a composite system which consists of those subsystems interacting according to the composition pattern. The ability to form composite systems of interacting component systems is called modularity, and is a well known boon in the design of complex systems.</p>
<p>In a sense, the definitions of categorical systems theory are all about modularity how can systems be composed of subsystems. On the other hand, the theorems of categorical systems theory often take the form of compositionality results. These say that certain facts and features of composite systems can be understood or calculated in terms of their component systems and the composition pattern.</p>
</blockquote>
<p>See also <span class="citation" data-cites="Capucci2025Notes">Capucci (2025)</span>.</p>
<section id="string-diagrams" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="string-diagrams"><span class="header-section-number">1</span> String diagrams</h2>
<p>Why would we bother with this? I saw Martin Biehl present work <span class="citation" data-cites="Biehl2023Interpreting Virgo2021Interpreting">(Biehl and Virgo 2023; Virgo, Biehl, and McGregor 2021)</span> that used the “string diagrams” formalism to run computations over <a href="../notebook/graphical_models_directed.html">Bayesian networks</a>, and he argued it might be simpler to work with complex systems in this style.</p>
<p>Are <a href="../notebook/patchers.html">patchers</a> a special case of string diagrams? A sloppy generalization?</p>
</section>
<section id="tools" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="tools"><span class="header-section-number">2</span> Tools</h2>
<ul>
<li><a href="https://docs.discopy.org/en/main/?ref=danmackinlay.name">DisCoPy</a>, “DisCoPy is a Python toolkit for computing with string diagrams.”</li>
<li><a href="https://tikZit.github.io/?ref=danmackinlay.name">TikZiT</a> looks like a reasonably good alternative GUI for <a href="../notebook/tikz.html">tikz</a> with string diagram support.</li>
</ul>
</section>
<section id="references" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="references"><span class="header-section-number">3</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Biehl2023Interpreting" class="csl-entry">
Biehl, and Virgo. 2023. <span>“<a href="https://doi.org/10.1007/978-3-031-28719-0_2">Interpreting Systems as Solving POMDPs: A Step Towards a Formal Understanding of Agency</a>.”</span> In.
</div>
<div id="ref-Capucci2025Notes" class="csl-entry">
Capucci. 2025. <span>“<a href="https://github.com/mattecapu/categorical-systems-theory/blob/master/main.pdf">Notes on Categorical Systems Theory</a>.”</span>
</div>
<div id="ref-Felice2022Categorical" class="csl-entry">
Felice. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2212.06636">Categorical Tools for Natural Language Processing</a>.”</span>
</div>
<div id="ref-Felice2021DisCoPy" class="csl-entry">
Felice, Toumi, and Coecke. 2021. <span>“<a href="https://doi.org/10.4204/EPTCS.333.13">DisCoPy: Monoidal Categories in Python</a>.”</span>
</div>
<div id="ref-Myers2023Categorical" class="csl-entry">
Myers. 2023. <span>“Categorical Systems Theory.”</span>
</div>
<div id="ref-Toumi2022Category" class="csl-entry">
Toumi. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2212.06615">Category Theory for Quantum Natural Language Processing</a>.”</span>
</div>
<div id="ref-Toumi2022DisCoPy" class="csl-entry">
Toumi, Felice, and Yeung. 2022. <span>“<a href="https://doi.org/10.48550/arXiv.2205.05190">DisCoPy for the Quantum Computer Scientist</a>.”</span>
</div>
<div id="ref-Toumi2021Functorial" class="csl-entry">
Toumi, and Koziell-Pipe. 2021. <span>“<a href="https://doi.org/10.48550/arXiv.2103.14411">Functorial Language Models</a>.”</span>
</div>
<div id="ref-Toumi2021Diagrammatic" class="csl-entry">
Toumi, Yeung, and Felice. 2021. <span>“<a href="https://doi.org/10.4204/EPTCS.343.7">Diagrammatic Differentiation for Quantum Machine Learning</a>.”</span>
</div>
<div id="ref-Toumi2023DisCoPy" class="csl-entry">
Toumi, Yeung, Poór, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2311.10608">DisCoPy: The Hierarchy of Graphical Languages in Python</a>.”</span>
</div>
<div id="ref-Virgo2021Interpreting" class="csl-entry">
Virgo, Biehl, and McGregor. 2021. <span>“<a href="https://doi.org/10.48550/arXiv.2112.13523">Interpreting Dynamical Systems as Bayesian Reasoners</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>calculus</category>
  <category>dynamical systems</category>
  <category>geometry</category>
  <category>Hilbert space</category>
  <category>how do science</category>
  <category>Lévy processes</category>
  <category>machine learning</category>
  <category>neural nets</category>
  <category>PDEs</category>
  <category>physics</category>
  <category>regression</category>
  <category>sciml</category>
  <category>SDEs</category>
  <category>signal processing</category>
  <category>statistics</category>
  <category>statmech</category>
  <category>stochastic processes</category>
  <category>sync</category>
  <category>time series</category>
  <category>uncertainty</category>
  <guid>https://danmackinlay.name/notebook/categorical_systems_theory.html</guid>
  <pubDate>Wed, 04 Feb 2026 17:58:32 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/monochrom_spiderflower_RP-P-1957-523.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>Intentional language</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/intentional_language.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<p>The <a href="https://en.wikipedia.org/wiki/Wason_selection_task?ref=danmackinlay.name">Wason selection task</a> is a logic puzzle that uses a set of cards Figure&nbsp;1.</p>
<div id="fig-wason-cards" class="figure illustration foreign right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-wason-cards-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/Wason_selection_task_cards.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: Each card has a number on one side and a patch of colour on the other. Which card or cards must be turned over to test the idea that if a card shows an even number on one face, then its opposite face is red?"><img src="https://danmackinlay.name/images/Wason_selection_task_cards.svg" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-wason-cards-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: <span class="caption">Each card has a number on one side and a patch of colour on the other. Which card or cards must be turned over to test the idea that if a card shows an even number on one face, then its opposite face is red?</span>
</figcaption>
</figure>
</div>
<p>In its abstract form, the task is notoriously difficult. To test the rule “If P, then Q”, logic says we need to check the <em>P</em> case (to see whether it has Q) and the <em>not Q</em> case (to see whether it has P). Most people fail to flip the “not Q” card and fall prey to <a href="https://en.wikipedia.org/wiki/Confirmation_bias?ref=danmackinlay.name">confirmation bias</a>.</p>
<p>But when we frame the task as a social problem instead of a logic puzzle, we suddenly get much better at it Figure&nbsp;2.</p>
<div id="fig-wason-drinking" class="figure illustration foreign right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-wason-drinking-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/Wason_selection_task_cards_-_drinking_variant.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: Each card has an age on one side and a drink on the other. Which card(s) must be turned over to test the idea that if you are drinking alcohol, then you must be over 18?"><img src="https://danmackinlay.name/images/Wason_selection_task_cards_-_drinking_variant.svg" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-wason-drinking-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: <span class="caption">Each card has an age on one side and a drink on the other. Which card(s) must be turned over to test the idea that if you are drinking alcohol, then you must be over 18?</span>
</figcaption>
</figure>
</div>
<p>Now that the logical problem is framed as a social problem, it becomes easy for our social-ape brains. Almost everyone knows to check the beer drinker (P) and the 16-year-old (not-Q).</p>
<p>Why is the second task so much easier? Evolutionary psychologists like Leda Cosmides and John Tooby <span class="citation" data-cites="Cosmides1992Cognitive Cosmides1994Better">(Cosmides and Tooby 1992, 1994)</span> argue that human beings haven’t evolved to be general-purpose logic processors. Instead, we’ve evolved specialized “modules” for social exchange: <a href="../notebook/social_brain.html">our brains are optimized for reasoning about social contracts, intentions, and agency.</a></p>
<p>For our ancestors, survival depended on stuff like <a href="../notebook/cooperation_evolution.html">the ability to detect “cheaters”</a>—those who take a benefit without paying the cost or following the rule. It’s totally reasonable to suppose that we tend to be good at reasoning about the intentions, obligations, and transgressions of other agents.</p>
<section id="the-intentional-stance" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-intentional-stance"><span class="header-section-number">1</span> The Intentional Stance</h2>
<p>If our most powerful cognitive hardware is dedicated to social reasoning, it follows that we might reason <em>better</em> about complex systems when we treat them as social agents.</p>
<p>This is what philosopher Daniel Dennett calls <a href="https://en.wikipedia.org/wiki/Intentional_stance?ref=danmackinlay.name"><em>The Intentional Stance</em></a>. Dennett argues <span class="citation" data-cites="Dennett1998Intentional">(Dennett 1998)</span> that there are three ways to predict the behaviour of a system:</p>
<ol type="1">
<li><em>The Physical Stance:</em> Predicting based on physics and chemistry (e.g., “The water will boil because of heat”).</li>
<li><em>The Design Stance:</em> Predicting based on how something was built (e.g., “The alarm will go off because the timer is set”).</li>
<li><em>The Intentional Stance:</em> Predicting based on what the system “wants” or “believes” (e.g., “The chess computer is trying to protect its Queen”).</li>
</ol>
<p>While the physical stance is often claimed to be the most “accurate”, it’s kinda unwieldy. If we want to predict how a Large Language Model (LLM) or a complex algorithm will behave, talking about it purely algorithmically is clunky and unintuitive (e.g., “The model will output token T with probability P given context C because of the weights W and the training data D…”). The Design Stance is better, but still complex (e.g., “The model is designed to predict the next token based on patterns in its training data…”). But saying “The chess computer is trying to protect its Queen”, in an intentional style, seems pretty compact.</p>
<p>FWIW, I’m not even convinced the intentional stance is especially “inaccurate” as such; we don’t really <a href="../notebook/causally_embedded_agency.html">know what intentions are for general physical systems</a>.</p>
<p>But! Social convention says we don’t get away with using intentional language, at least for some classes of objects. If I talk in a biology class about dogs as thinking, or on the internet about <a href="../notebook/stochastic_parrotology.html">LLMs as thinking</a>, then I’m likely to be accused of <em>anthropomorphism</em>—the attribution of human traits to non-human things. This is commonly regarded as a “category error” or a sign of intellectual laziness.</p>
<p>OTOH, the Wason task suggests that anthropomorphism might actually be a form of cognitive ergonomics. Maybe I see intent everywhere because it helps me think better about systems. By using intentional language—words like <em>wants, knows, believes, tries</em>—we are “porting” a complex technical problem into our brain’s most high-performance processor: the social reasoning module.</p>
<p>Just as we find it easier to spot a “cheater” in a bar than a logic error on a card, we find it easier to debug a system by imagining its “intentions.” Admitting intentional language into our technical descriptions isn’t necessarily a claim that the machine is “conscious”; rather, it is an admission that we are human and we think best when we pretend the world is looking back at us.</p>
<p>Maybe even <a href="../notebook/dunning_kruger_theory_of_mind.html">our reasoning about humans is <em>also</em> anthropomorphizing</a>, in the same sense? I am unsure about this, but I cannot find a crisp way of making the distinction.</p>
</section>
<section id="intentional-language-is-not-the-same-as-moral-patienthood" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="intentional-language-is-not-the-same-as-moral-patienthood"><span class="header-section-number">2</span> Intentional language is not the same as moral patienthood</h2>
<p>OK, so I ascribe a system “intents” and “wants”. Does that mean that I’m treating the system as a moral patient, with rights and interests that I should respect?</p>
<p>Nah, we need to be careful about that too. There’s a whole other question about whose wants and needs deserve consideration, and whether they have “conscious experience” or “qualia”, and whether that means we need to care about them. I have relatively few thoughts about that. Sounds interesting, but also like a philosophical tarpit I don’t want to step into.</p>
</section>
<section id="overshoot" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="overshoot"><span class="header-section-number">3</span> Overshoot</h2>
<p>What does it look like when we lean too hard into intentionality? Our brains are so well-optimized for social pattern-detection that we often see “agency” where there is only noise. This is <em>Pareidolia</em>—the tendency to see faces in clouds or burnt toast—and it can lead us down a rabbit hole of metaphysical overreach.</p>
<p>If we find the intentional stance useful for a dog, then a computer, then a “smart” thermostat, where does it end? We risk sliding into a functional <em>Panpsychism</em>, the belief that consciousness or mind-like qualities are fundamental properties of all matter. Or perhaps we find ourselves returning to an ancient <em>Animism</em>, treating the river, the forest, and the thunderstorm as entities with their own agendas.</p>
<p>Is that bad though? Is this “overshooting” an actual problem? From a strict materialist perspective, it might be a delusion (although, once again, <a href="../notebook/ai_agency.html">what even are intentions?</a>). Pragmatically, if an animistic view of a forest leads a community to manage its resources more sustainably than a “resource-extraction” view, then the “error” brings biological utility. Similarly, if treating a complex neural network as a “personality” allows a researcher to predict its failure modes better than a statistical analysis would, the anthropomorphism has paid its way. We may have to accept that our brains are biased toward seeing ghosts in the machine, but maybe that works just fine.</p>
</section>
<section id="and-thus-marx" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="and-thus-marx"><span class="header-section-number">4</span> And thus Marx</h2>
<p>Now I have talked myself into contradicting another statement I made on this blog. Under <a href="../notebook/conflict_theorists.html">conflict theory</a> I mentioned I was not well-disposed to theories that assume an “us and them” might be in conflict, because it doesn’t seem credible to me that the social groups in most conflict theories actually organize.</p>
<p>But maybe it is a “good enough” cognitive crutch to talk about “workers” and “capital” as if they each were <a href="../notebook/human_superorganisms.html">egregores</a> with intentions and agency, even if they are really just metaphors for complex social dynamics?</p>
<p>It seems possible that by opening the door to talking about LLM agents as if they were coherent intelligent agents, I might also open the door to describing the proletariat as if it were a coherent agent?</p>
<p>Unclear. I need to think on this.</p>
</section>
<section id="references" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="references"><span class="header-section-number">5</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-Cosmides1992Cognitive" class="csl-entry">
Cosmides, and Tooby. 1992. <span>“<a href="https://www.cep.ucsb.edu/papers/Cogadapt.pdf">Cognitive Adaptations for Social Exchange</a>.”</span> <em>The Adapted Mind: Evolutionary Psychology and the Generation of Culture</em>.
</div>
<div id="ref-Cosmides1994Better" class="csl-entry">
———. 1994. <span>“Better Than Rational: Evolutionary Psychology and the Invisible Hand.”</span> <em>The American Economic Review</em>.
</div>
<div id="ref-Dennett1998Intentional" class="csl-entry">
Dennett. 1998. <em>The Intentional Stance</em>. A Bradford Book.
</div>
<div id="ref-Dresow2023Teleonomy" class="csl-entry">
Dresow, and Love. 2023. <span>“<a href="https://doi.org/10.1007/s13752-022-00424-y">Teleonomy: Revisiting a Proposed Conceptual Replacement for Teleology</a>.”</span> <em>Biological Theory</em>.
</div>
<div id="ref-Foss1994Evolution" class="csl-entry">
Foss. 1994. <span>“<a href="https://doi.org/10.1080/00201749408602355">On the Evolution of Intentionality as Seen from the Intentional Stance</a>.”</span> <em>Inquiry</em>.
</div>
<div id="ref-Ji2024Demystify" class="csl-entry">
Ji. 2024. <span>“<a href="https://doi.org/10.60690/cee03e53">Demystify ChatGPT: Anthropomorphism Around Generative AI</a>.”</span> <em>GRACE: Global Review of AI Community Ethics</em>.
</div>
<div id="ref-MishraHuman" class="csl-entry">
Mishra, and Oster. n.d. <span>“Human or AI? Understanding the Learning Implications of Anthropomorphized Generative AI.”</span>
</div>
<div id="ref-Sperber2002Use" class="csl-entry">
Sperber, and Girotto. 2002. <span>“<a href="https://doi.org/10.1016/S0010-0277(02)00125-7">Use or Misuse of the Selection Task? Rejoinder to Fiddick, Cosmides, and Tooby</a>.”</span> <em>Cognition</em>.
</div>
<div id="ref-Sperber2012Reasoning" class="csl-entry">
Sperber, and Mercier. 2012. <span>“<a href="https://doi.org/10.1017/CBO9780511846427.016">Reasoning as a Social Competence</a>.”</span> In <em>Collective Wisdom</em>.
</div>
</div>


</section>

 ]]></description>
  <category>adaptive</category>
  <category>adversarial</category>
  <category>bounded compute</category>
  <category>collective knowledge</category>
  <category>cooperation</category>
  <category>culture</category>
  <category>distributed</category>
  <category>economics</category>
  <category>ethics</category>
  <category>evolution</category>
  <category>extended self</category>
  <category>gene</category>
  <category>incentive mechanisms</category>
  <category>institutions</category>
  <category>mind</category>
  <category>networks</category>
  <category>neuron</category>
  <category>rhetoric</category>
  <category>snarks</category>
  <category>social graph</category>
  <category>sociology</category>
  <category>wonk</category>
  <guid>https://danmackinlay.name/notebook/intentional_language.html</guid>
  <pubDate>Fri, 23 Jan 2026 16:42:36 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/elegant_greek_succour.thumbnail.avif" medium="image" type="image/avif"/>
</item>
<item>
  <title>History-Based Reinforcement Learning</title>
  <dc:creator>Dan MacKinlay</dc:creator>
  <link>https://danmackinlay.name/notebook/reinforcement_learning_history_based.html</link>
  <description><![CDATA[ 




<div class="quarto-title-meta">

    </div>





<div id="fig-winding-path" class="figure illustration right quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-winding-path-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="../images/winding_path_RP-P-OB-62.365.avif" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: "><img src="https://danmackinlay.name/images/winding_path_RP-P-OB-62.365.avif" class="img-fluid figure-img"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-winding-path-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
<p>An interesting alternative formulation that relaxes the Markovian assumptions in mainstream RL, though I don’t know much about it. It seems to come out of <a href="../notebook/aixi.html">AIXI</a> and be similarly intractable, but it’s not purely theoretical; computable approximations do exist <span class="citation" data-cites="DaswaniQlearning Gao2023Fast Guez2013Efficient HamiltonEfficient Sunehag2014Feature Tennenholtz2023Reinforcement">(Daswani et al., n.d.; Gao et al. 2023; Guez, Silver, and Dayan 2013; Hamilton, Fard, and Pineau, n.d.; Sunehag 2014; Tennenholtz et al. 2023)</span>.</p>
<p>TBD</p>
<section id="references" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="references"><span class="header-section-number">1</span> References</h2>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-DaswaniQlearning" class="csl-entry">
Daswani, Sunehag, Sunehag, et al. n.d. <span>“Q-Learning for History-Based Reinforcement Learning.”</span>
</div>
<div id="ref-Eberhard2025Partially" class="csl-entry">
Eberhard, Muehlebach, and Vernade. 2025. <span>“<a href="https://doi.org/10.48550/arXiv.2503.15200">Partially Observable Reinforcement Learning with Memory Traces</a>.”</span>
</div>
<div id="ref-Gao2023Fast" class="csl-entry">
Gao, Zhang, Yang, et al. 2023. <span>“<a href="https://doi.org/10.1609/aaai.v37i6.25924">Fast Counterfactual Inference for History-Based Reinforcement Learning</a>.”</span> <em>Proceedings of the AAAI Conference on Artificial Intelligence</em>.
</div>
<div id="ref-Guez2013Efficient" class="csl-entry">
Guez, Silver, and Dayan. 2013. <span>“<a href="https://doi.org/10.48550/arXiv.1205.3109">Efficient Bayes-Adaptive Reinforcement Learning Using Sample-Based Search</a>.”</span>
</div>
<div id="ref-HamiltonEfficient" class="csl-entry">
Hamilton, Fard, and Pineau. n.d. <span>“Eﬃcient Learning and Planning with Compressed Predictive States.”</span>
</div>
<div id="ref-Leike2016Nonparametric" class="csl-entry">
Leike. 2016. <span>“Nonparametric General Reinforcement Learning.”</span>
</div>
<div id="ref-Sunehag2014Feature" class="csl-entry">
Sunehag. 2014. <span>“<a href="https://cdn.aaai.org/ocs/ws/ws1209/8791-37973-1-PB.pdf">Feature Reinforcement Learning: State of the Art</a>.”</span>
</div>
<div id="ref-Tennenholtz2023Reinforcement" class="csl-entry">
Tennenholtz, Merlis, Shani, et al. 2023. <span>“<a href="https://doi.org/10.48550/arXiv.2302.02061">Reinforcement Learning with History-Dependent Dynamic Contexts</a>.”</span>
</div>
</div>


</section>

 ]]></description>
  <category>adaptive</category>
  <category>agents</category>
  <category>bandit problems</category>
  <category>control</category>
  <category>incentive mechanisms</category>
  <category>learning</category>
  <category>networks</category>
  <category>stochastic processes</category>
  <category>time series</category>
  <category>utility</category>
  <guid>https://danmackinlay.name/notebook/reinforcement_learning_history_based.html</guid>
  <pubDate>Fri, 16 Jan 2026 11:03:38 GMT</pubDate>
  <media:content url="https://danmackinlay.name/generated_thumbnails/site_previews/images/winding_path_RP-P-OB-62.365.thumbnail.avif" medium="image" type="image/avif"/>
</item>
</channel>
</rss>
