<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>A. Jesse Jiryu Davis</title>
    <link>https://emptysqua.re/blog/</link>
    <description>Recent content on A. Jesse Jiryu Davis</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>jesse@emptysquare.net (A. Jesse Jiryu Davis)</managingEditor>
    <webMaster>jesse@emptysquare.net (A. Jesse Jiryu Davis)</webMaster>
    <copyright>(c) A. Jesse Jiryu Davis, all rights reserved.</copyright>
    <lastBuildDate>Tue, 01 Jan 2013 00:00:00 +0000</lastBuildDate>
    
	    <atom:link href="https://emptysqua.re/blog/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Preparing the Eucharist</title>
      <link>https://emptysqua.re/blog/eucharist/</link>
      <pubDate>Fri, 17 Apr 2026 02:02:24 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/eucharist/</guid>
      
      
      
      
      <description>&lt;p&gt;Reverend Emily Carter preparing the wine and wafers at Christ Episcopal Church.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-1.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-2.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-3.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-4.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-5.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-6.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/eucharist/eucharist-7.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Holy Week at Christ Episcopal Church</title>
      <link>https://emptysqua.re/blog/holy-week-2026/</link>
      <pubDate>Thu, 16 Apr 2026 16:36:30 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/holy-week-2026/</guid>
      
      
      
      
      <description>&lt;p&gt;The services leading up to Easter Sunday, led by my friend Reverend Emily Carter at Christ Episcopal Church in Poughkeepsie.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-10.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-11.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-12.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-13.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-14.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-15.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-16.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-17.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-18.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-19.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-20.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-21.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-22.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-week-2026/holy-week-23.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Review: Automated Design of Agentic Systems</title>
      <link>https://emptysqua.re/blog/review-automated-design-of-agentic-systems/</link>
      <pubDate>Wed, 15 Apr 2026 20:57:57 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-automated-design-of-agentic-systems/</guid>
      
      
      
      
      <description>&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2408.08435&#34;&gt;Automated Design of Agentic Systems&lt;/a&gt;, by Shengran Hu, Cong Lu, and Jeff Clune (UBC, Vector Institute), at ICLR 2025.&lt;/p&gt;
&lt;p&gt;Clune has written &lt;a href=&#34;https://scholar.google.com/citations?user=5TZ7f5wAAAAJ&amp;amp;hl=en&#34;&gt;a bunch of papers&lt;/a&gt; on evolving, self-improving systems. His &lt;a href=&#34;https://arxiv.org/abs/2408.06292&#34;&gt;AI Scientist paper&lt;/a&gt; made waves in 2024, maybe because it was a direct threat to the livelihoods of its readers.&lt;/p&gt;
&lt;p&gt;In today&amp;rsquo;s paper, &amp;ldquo;Automated Design of Agentic Systems,&amp;rdquo; the authors ask: rather than hand-crafting agent architectures like Chain-of-Thought, Self-Refine, and LLM Debate, can we tell a meta-agent to invent them? The authors wrote a 100-line Python program that tells GPT-4 to write new, &amp;ldquo;interesting and novel&amp;rdquo; agents. This meta-agent writes little agent systems, which are a dozen lines or so of Python that calls GPT-3.5. The meta-agent evaluates the little agentic systems on a benchmark, and adds them to an ever-growing archive of previous attempts. The discovered systems are run with GPT-3.5 to keep costs down; even so a single run cost $300 to $500 in tokens in 2025. The claim is that the resulting systems beat state-of-the-art hand-designed systems on AI benchmarks (DROP, MGSM, and ARC), and transfer well across domains and models.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-automated-design-of-agentic-systems/overview.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no consensus among researchers about terminology for pieces of agentic systems. The paper defines its own usage: an &amp;ldquo;agent&amp;rdquo; is the same as an &amp;ldquo;agentic system&amp;rdquo;, a workflow that uses Foundation Models (FMs) like GPT-3.5 as modules to plan, call tools, and run multi-step processing. So each little agentic system discovered by the meta-agent is just &amp;ldquo;an agent,&amp;rdquo; even though internally it fires off many FM calls. I would personally call the internal FM calls &amp;ldquo;sub-agents&amp;rdquo;. But in this paper, the authors call them modules, experts, reviewers, critics, or roles (e.g. &amp;ldquo;Efficiency Expert,&amp;rdquo; &amp;ldquo;Readability Expert&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;The paper&amp;rsquo;s central design argument is that searching in &lt;em&gt;code space&lt;/em&gt; is strictly more expressive than searching in the network or graph spaces used by predecessors like &lt;a href=&#34;https://arxiv.org/abs/2310.02170&#34;&gt;DyLAN&lt;/a&gt; and &lt;a href=&#34;https://arxiv.org/abs/2402.16823&#34;&gt;GPTSwarm&lt;/a&gt;. Those systems have a fixed vocabulary of nodes (an LLM call with a specific prompt, or a role-assigned agent) and optimize which nodes to include and how to wire them together. But in this paper, the meta agent can use Python&amp;rsquo;s full power: arbitrary control flow, new tool invocations, and so on. Because Python is Turing-complete, the paper argues, the code search space contains all possible networks or graphs. The authors also claim that discovered agents are more interpretable than DyLAN and GPTSwarm: they&amp;rsquo;re very short Python programs, not a tangle of edge weights. I buy that, but on the other hand, if you&amp;rsquo;re optimizing edge weights of a graph you can estimate an optimization gradient with simple math. If you&amp;rsquo;re telling a meta agent to try interesting and novel agent architectures, you can&amp;rsquo;t estimate the gradient: you rely on the meta-agent to guess what changes might improve its benchmark score.&lt;/p&gt;
&lt;h1&gt;Word-problem scores&lt;/h1&gt;

&lt;p&gt;It seems to me that the authors chose weird benchmarks. I assume they faced the same problem most CS has: we want to use existing benchmarks because that&amp;rsquo;s &amp;ldquo;science,&amp;rdquo; even when &lt;a href=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/&#34;&gt;the existing benchmarks are inappropriate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The authors compare Meta Agent Search (their invention) to previous systems of the last few years: Chain-of-Thought, Self-Refine, LLM Debate, and so on. These systems compete on these benchmarks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2210.03057&#34;&gt;&lt;strong&gt;MGSM&lt;/strong&gt;&lt;/a&gt; (Multilingual Grade School Math) 250 grade-school math word problems, manually translated into ten languages, used to test multilingual chain-of-thought reasoning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/1903.00161&#34;&gt;&lt;strong&gt;DROP&lt;/strong&gt;&lt;/a&gt; (Discrete Reasoning Over Paragraphs) Math word problems with more of an emphasis on reading comprehension than complex math. Each question requires discrete math reasoning (counting, addition, sorting, comparing) from a short paragraph.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2009.03300&#34;&gt;&lt;strong&gt;MMLU&lt;/strong&gt;&lt;/a&gt; (Massive Multitask Language Understanding) A 57-subject multiple-choice exam spanning humanities, STEM, social sciences, and professional fields, meant to measure broad world knowledge and problem solving.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2311.12022&#34;&gt;&lt;strong&gt;GPQA&lt;/strong&gt;&lt;/a&gt; (Graduate-Level Google-Proof Q&amp;amp;A) 448 graduate-level multiple-choice questions in biology, physics, and chemistry, written by domain PhDs to be &amp;ldquo;Google-proof&amp;rdquo;: hard even when the test-taker has unrestricted web access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multi Agent Search is significantly better than its competitors on the math tests (DROP and MGSM), but barely beats them on the science and multi-task tests. That&amp;rsquo;s predictable: the MMLU asks questions like, &amp;ldquo;The constellation &amp;hellip; is a bright W-shaped constellation in the northern sky.&amp;rdquo; The agents are banned from searching the web, so if they don&amp;rsquo;t already know the answer is Cassiopeia, no fancy multi-expert architecture will help. Like I said, it&amp;rsquo;s a weird benchmark. I&amp;rsquo;d love to see this whole approach with Internet-connected agents, against more benchmarks that really exercise the architectures rather than the base model&amp;rsquo;s training data.&lt;/p&gt;
&lt;h1&gt;Grid puzzle scores&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/1911.01547&#34;&gt;&lt;strong&gt;ARC&lt;/strong&gt;&lt;/a&gt; (Abstraction and Reasoning Corpus) is a set of visual grid puzzles where the system has to infer a transformation rule from a handful of input/output examples. It&amp;rsquo;s intended as a test of general fluid intelligence rather than learned knowledge. Here&amp;rsquo;s an ARC puzzle:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-automated-design-of-agentic-systems/arc.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The agent sees the examples on the left. It sees the test on the upper right, and it wins a point by correctly guessing the answer on the lower right (which is hidden from it). It has to infer some general pattern, in this case &amp;ldquo;find all squares next to gray squares and color them blue.&amp;rdquo; Then it writes a Python program that applies that transformation to the test grid (upper right) and hopes to produce the correct output (lower right).&lt;/p&gt;
&lt;p&gt;As the meta agent developed agents, it quickly beat its competitors and continued improving its score:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-automated-design-of-agentic-systems/arc-score.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;This is really cool! But the winning agent built by the meta agent has a weird architecture:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-automated-design-of-agentic-systems/arc-agent.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The agent is a committee. It uses chain-of-thought to generate 5 candidate Python programs. Then it tells GPT-3.5 to act in various roles (efficiency expert, readability expert, simplicity expert, &amp;ldquo;human-like critic&amp;rdquo;) to choose and refine one of those candidates.&lt;/p&gt;
&lt;p&gt;But ARC doesn&amp;rsquo;t evaluate the program on its subjective code-quality measures, it only checks whether its output matches the correct answer. Efficiency and readability are irrelevant. So how are these &amp;ldquo;experts&amp;rdquo; improving the score? The paper doesn&amp;rsquo;t say, but I&amp;rsquo;d bet their opinions about efficiency or readability don&amp;rsquo;t matter. It&amp;rsquo;s possible the Simplicity Expert helps because a &amp;ldquo;simpler&amp;rdquo; solution generalizes from the few-shot examples instead of overfitting to specific grid positions. I&amp;rsquo;d like to see an ablation: remove or replace the experts one by one, and see whether the architecture&amp;rsquo;s complexity is load-bearing or decorative.&lt;/p&gt;
&lt;p&gt;I also wonder why the meta agent came up with this architecture. I guess GPT-4 was trained to be a good code assistant, and when it was asked to &amp;ldquo;create a coding agent&amp;rdquo; it applied some of the best practices it was rewarded for. Maybe if it was asked, &amp;ldquo;Write shitty code and beat this test by any means necessary,&amp;rdquo; it would have made different choices. Would its best agent have scored higher on ARC then, or at least spent fewer tokens on debating the code&amp;rsquo;s beauty?&lt;/p&gt;
&lt;h1&gt;What Does It All Mean?&lt;/h1&gt;

&lt;p&gt;It&amp;rsquo;s hilarious how human-shaped the discovered patterns are. The meta agent reinvented peer review, editorial boards, code review, and role-assigned committees. These are the structures we invented for teams of humans. Except the LLM &amp;ldquo;human-like critic&amp;rdquo; starts reviewing immediately instead of waiting to be pinged on Slack three times.&lt;/p&gt;
&lt;p&gt;The deeper principle is that LLMs behave more like people than like conventional software: they&amp;rsquo;re better when they write their thoughts down as they go, they&amp;rsquo;re bad at arithmetic and should use a calculator, they&amp;rsquo;re more honest if you put them in a room and tell them to criticize each other. Meta Agent Search is an automated rediscovery of how humans cooperate to be less wrong. Its agents encode &lt;a href=&#34;https://emptysqua.re/blog/scout-mindset/&#34;&gt;metacognitive tricks to avoid cognitive bias&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next step, naturally, would be a meta meta agent, etc. Or else the meta agent could redesign &lt;em&gt;itself&lt;/em&gt;, and initiate a &lt;a href=&#34;https://www.lesswrong.com/posts/tjH8XPxAnr6JRbh7k/hard-takeoff&#34;&gt;hard takeoff&lt;/a&gt;. The authors defer this madness to &amp;ldquo;future research&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Siphonaptera_(poem)&#34;&gt;Great bots make little bots&lt;/a&gt; to think of codes and write &amp;rsquo;em,&lt;br&gt;
And little bots have lesser bots, and so ad infinitum.&lt;br&gt;
And the great bots themselves, in turn, have greater bots to go on;&lt;br&gt;
While these again have greater still, and greater still, and so on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-automated-design-of-agentic-systems/55014511627_51e608a6af_o.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Climbing Retribution in the Gunks</title>
      <link>https://emptysqua.re/blog/retribution/</link>
      <pubDate>Tue, 14 Apr 2026 02:08:19 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/retribution/</guid>
      
      
      
      
      <description>&lt;p&gt;My friend Tyler leading &lt;a href=&#34;https://www.mountainproject.com/route/105799038/retribution&#34;&gt;Retribution, a 5.10b trad route&lt;/a&gt;.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/retribution/retribution-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>LeaseGuard: The Movie</title>
      <link>https://emptysqua.re/blog/leaseguard-presentation-video/</link>
      <pubDate>Wed, 08 Apr 2026 20:46:14 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/leaseguard-presentation-video/</guid>
      
      
      
      
      <description>&lt;p&gt;LeaseGuard is a new enhancement to the Raft consensus protocol that strengthens Raft&amp;rsquo;s consistency guarantees and time-to-recovery. I developed LeaseGuard with my MongoDB colleagues Murat Demirbas and Lingzhi Deng, and our paper is in SIGMOD 2026. Here&amp;rsquo;s &lt;a href=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right&#34;&gt;a summary of the protocol&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Yesterday my friend at Cockroach Labs invited me to present LeaseGuard at their NYC office, here&amp;rsquo;s the video.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/yqNzvLobBDE?si=lKeTqs-qDHM_3Hyk&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; style=&#34;margin-bottom: 1em&#34; allowfullscreen&gt;&lt;/iframe&gt;
</description>
    </item>
    
    <item>
      <title>The Dead Majority</title>
      <link>https://emptysqua.re/blog/the-dead-majority/</link>
      <pubDate>Mon, 06 Apr 2026 02:41:08 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/the-dead-majority/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dead-majority/02-joseph-sattler-modern-dance-of-death-1894.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The Latin poet Petronius wrote that when we die, we &lt;a href=&#34;https://en.wikipedia.org/wiki/Silent_majority#Euphemism_for_the_dead&#34;&gt;join the majority&lt;/a&gt;. Indeed, &amp;ldquo;silent majority&amp;rdquo; might have meant the population of the dead, before it referred to a mute political constituency. I just encountered this phrase in &lt;a href=&#34;https://publicdomainreview.org/essay/the-great-majority/&#34;&gt;a terrific Public Domain Review essay&lt;/a&gt;, and I was curious: what has been the historical ratio of dead to living?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dead-majority/dead-living.png&#34; alt=&#34;Two stacked line charts from 10,000 BCE to 2100 CE with log-scaled axes. The top chart shows cumulative dead (black) climbing from about a billion to about 116 billion, and the living population (red) climbing from a few million to about 10 billion. The bottom chart shows the dead-per-living ratio, starting near 225 at 10,000 BCE, rising to a peak around 240 near 1000 CE, then crashing during the last two centuries to about 14 today and stabilizing near 11 by 2100.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;For most of history, each living generation wasn&amp;rsquo;t much larger than the previous, but the deceased kept piling up, so the dead&amp;rsquo;s domination grew. It might have peaked in the Middle Ages at 240 dead for every living person, or perhaps it was even higher in the Bronze Age. It fell off a cliff during the &lt;a href=&#34;https://en.wikipedia.org/wiki/Demographic_transition&#34;&gt;demographic transition&lt;/a&gt;. We&amp;rsquo;re at 14:1 today, and might reach 11:1 by 2100. The living fraction of all humans ever born has been rising for two centuries, and it will keep rising as our lifespans increase. But I can&amp;rsquo;t imagine it ever reaching 1:1, unless some future generation has 200 billion immortal babies.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dead-majority/20-joseph-sattler-modern-dance-of-death-1894_900.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;Sources&lt;/h1&gt;

&lt;p&gt;I let Claude run this analysis with light guidance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cumulative births&lt;/strong&gt; are based on the Population Reference Bureau&amp;rsquo;s estimate: &lt;a href=&#34;https://www.prb.org/articles/how-many-people-have-ever-lived-on-earth/&#34;&gt;about 117 billion humans have ever been born&lt;/a&gt;. They divide human history into eras, estimate a population at the end of each era, and estimate an era-specific crude birth rate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;190,000 BCE &amp;mdash; 8,000 BCE: 80 births per 1,000 people per year&lt;/li&gt;
&lt;li&gt;8,000 BCE &amp;mdash; 1 CE: 80/1,000&lt;/li&gt;
&lt;li&gt;1 CE &amp;mdash; 1750: 60/1,000&lt;/li&gt;
&lt;li&gt;1750 &amp;mdash; 1950: declining from 50 to 35&lt;/li&gt;
&lt;li&gt;1950 &amp;mdash; present: declining from 32 to 17&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://acoup.blog/2025/07/18/collections-life-work-death-and-the-peasant-part-ii-starting-at-the-end/&#34;&gt;Ancient birth rates were high because life expectancy was low&lt;/a&gt;. Ancient people had a lot of babies, and a lot of them died, joining the silent majority.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Living population&lt;/strong&gt; numbers come from &lt;a href=&#34;https://www.census.gov/data/tables/time-series/demo/international-programs/historical-est-worldpop.html&#34;&gt;McEvedy &amp;amp; Jones&lt;/a&gt; before 1950 and the UN&amp;rsquo;s &lt;a href=&#34;https://population.un.org/wpp/&#34;&gt;World Population Prospects 2024&lt;/a&gt; after. Claude log-linearly interpolated between benchmark years.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Future projections&lt;/strong&gt; come from the same UN source, medium variant: population peaks around 10.3 billion in the mid-2080s and drifts down to ~10.2 billion by 2100. Life expectancy rises from 73.3 today to 81.7. Global fertility, currently 2.25 children per woman, falls below replacement (2.1) by the late 2040s. If fertility falls faster than the UN medium variant (and it has been falling faster than expected) the future ratio drops faster. If longevity gains stall, dead accumulate faster and the ratio falls more slowly.&lt;/p&gt;
&lt;p&gt;The wobbles in the lines are probably an artifact of how Claude distributed the Population Reference Bureau&amp;rsquo;s era-total births within each era. I&amp;rsquo;m not an expert, and even experts can only guess population statistics before recent centuries.&lt;/p&gt;
&lt;h1&gt;The Shrinking Majority&lt;/h1&gt;

&lt;p&gt;We&amp;rsquo;re in an unusual era, when more than 7% of those ever born are still breathing. And compared to the dead&amp;mdash;who mostly died young&amp;mdash;those living today own more than 7% of humanity&amp;rsquo;s time alive. G. K. Chesterton called tradition &lt;a href=&#34;https://www.chesterton.org/democracy-of-the-dead/&#34;&gt;the democracy of the dead&lt;/a&gt;, because it was chosen by our ancestors. Perhaps our quickening rejecting of tradition is a rational consequence of the Quick Party&amp;rsquo;s growing numbers. Our bloc will always be a minority, though. Once the demographic transition completes all over Earth, presumably birth rates and lifespans will stabilize and the Dead Party&amp;rsquo;s constituency will begin to grow again. Who knows?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dead-majority/05-joseph-sattler-modern-dance-of-death-1894.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/ajdavis/73d7d5fb3fd39dd7c75e0ef59241c2d4&#34;&gt;Code for the chart&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Images: &lt;a href=&#34;https://50watts.com/A-Modern-Dance-of-Death&#34;&gt;A Modern Dance of Death&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Review: SysMoBench: Evaluating AI on Formally Modeling Complex Real-World Systems</title>
      <link>https://emptysqua.re/blog/review-sysmobench/</link>
      <pubDate>Wed, 01 Apr 2026 15:26:43 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-sysmobench/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-sysmobench/tumblr_83912c4240955ebef038bc2801583ed3_a2869834_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2509.23130&#34;&gt;SysMoBench: Evaluating AI on Formally Modeling Complex Real-World Systems&lt;/a&gt;, by authors from Nanjing University, Microsoft Research Asia, University of British Columbia, and University of Illinois Urbana-Champaign. (Disclosure: I&amp;rsquo;ve collaborated with two of the authors from UBC, Finn Hackett and Ivan Beschastnikh.) This is a useful and thought-provoking test of LLMs&amp;rsquo; ability to write TLA+ specs of existing code. My summary and thoughts are below. &lt;a href=&#34;https://muratbuffalo.blogspot.com/2026/03/sysmobench-evaluating-ai-on-formally.html&#34;&gt;See Murat&amp;rsquo;s summary as well&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;The Paper&lt;/h1&gt;

&lt;p&gt;Can LLMs write formal specifications of real software systems? Not toy examples or textbook algorithms, but actual distributed and concurrent systems? The authors built a benchmark called SysMoBench to find out. They take eleven real-world system codebases&amp;mdash;e.g. the Raft consensus implementation in etcd, leader election in ZooKeeper, a spinlock in the Asterinas operating system&amp;mdash;and ask AI agents to produce a TLA+ spec of each.&lt;/p&gt;
&lt;p&gt;The benchmark evaluates AI-generated TLA+ specs on four increasingly strict levels:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Does it compile?&lt;/strong&gt; Is the TLA+ syntactically valid?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does it run?&lt;/strong&gt; Can the TLC model checker execute it without errors?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does the code conform to the spec?&lt;/strong&gt; When you run the actual system and record traces of its behavior, does the model accept those traces?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does it satisfy correctness properties?&lt;/strong&gt; Do the safety and liveness invariants hold?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is exciting. As we&amp;rsquo;re all learning, coding agents work best when they&amp;rsquo;re put in loops with test oracles or performance evaluations. DataDog&amp;rsquo;s very impressive &lt;a href=&#34;https://www.datadoghq.com/blog/ai/harness-first-agents/&#34;&gt;harness-first engineering&lt;/a&gt; articles are the latest example. And people like me (distributed systems and formal methods nerds) would love to auto-extract formal specs from existing code. So here we have a harness for guiding an agent toward a good spec.&lt;/p&gt;
&lt;p&gt;The benchmark tests three agent strategies for producing the spec, each with different inputs:&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Basic Modeling Agent&lt;/strong&gt; gets the most help. It receives the source code and a detailed task definition that spells out exactly which actions to model and which to skip. Here&amp;rsquo;s the human-written prompt for etcd Raft:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TLA+ Model Generation Prompt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;You are an expert in formal verification and TLA+ models with
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;deep expertise in concurrent and distributed systems,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;particularly etcd and Raft consensus.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Convert the following source code to a comprehensive TLA+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;model.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System:  etcd distributed key-value store
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;SOURCE CODE INSERTED HERE&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System-specific modeling requirements:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MANDATORY CORE ACTIONS (must include all):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1.  [Message Types] MsgHup (election timeout),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MsgVote/MsgVoteResp (voting), MsgApp/MsgAppResp (log
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    replication)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2.  [Node States] Four states:  StateFollower, StateCandidate,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    StateLeader, StatePreCandidate (prevote enabled)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3.  [Leader Election] Complete prevote + vote phases:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PreCandidate → Candidate → Leader transitions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4.  [Log Operations] Log entry appending, consistency checks,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    commitment with majority quorum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;5.  [Heartbeat/Timeout] Election timeouts triggering campaigns,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    heartbeat prevention of elections
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;6.  [Client Proposals] MsgProp message handling and log entry
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    creation by leaders
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EXPLICITLY EXCLUDED (do not model):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Configuration changes and joint consensus (ConfChange
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  messages)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Log compaction and snapshots (MsgSnap)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- ReadIndex optimizations (MsgReadIndex)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Async storage operations (LocalAppendThread,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  LocalApplyThread)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Advanced flow control and progress tracking details
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;REQUIRED BEHAVIORAL SCOPE:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Prevote phase (StatePreCandidate) must be modeled as it&amp;#39;s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  enabled by default in etcd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- State transition constraints:  Follower → PreCandidate →
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Candidate → Leader (strict transitions)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Message processing by state:  only valid message types
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  handled in each node state
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Term advancement rules:  nodes advance term when receiving
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  messages with higher term
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Voting restrictions: one vote per term, term must be current
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  or newer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Heartbeat mechanism: leaders send heartbeats, followers reset
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  election timeout on receipt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Log consistency checks:  prevLogIndex/prevLogTerm validation
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  in MsgApp processing
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Majority-based leader election and log commitment
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Basic network message delays and losses
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generate a TLA+ model that accurately models the system&amp;#39;s behavior.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CRITICAL OUTPUT REQUIREMENTS:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1.  The MODULE name must be exactly &amp;#34;etcdraft&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (---- MODULE etcdraft ----)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2.  Return ONLY pure TLA+ model code - no markdown code blocks
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (no ```tla or ```)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3.  Do not include any explanations, comments, or formatting
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    markers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4.  Start your response directly with:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ---- MODULE etcdraft ----
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;5.  End your response with the closing ====
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;6.  **DO NOT define invariants** (like MutualExclusion,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Invariant, etc.), focus on modeling the system behavior
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.  **MUST include EXTENDS statement**:  The model must extend
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    at least these modules: TLC, Sequences, SequencesExt,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Naturals, FiniteSets, Bags
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a &lt;em&gt;lot&lt;/em&gt; of guidance! A human who&amp;rsquo;d gotten this far would be most of the way to writing the spec. The guidance encodes all the wisdom of an expert in Raft, etcd, and TLA+. I doubt I could write instructions this good unless I&amp;rsquo;d already written the spec myself, actually&amp;hellip;&lt;/p&gt;
&lt;p&gt;So that was the Basic Modeling Agent, the first of the three agents. The &lt;strong&gt;Code Translation Agent&lt;/strong&gt; takes a more mechanical approach: it translates source code statement-by-statement into TLA+, then assembles the pieces into a model. It doesn&amp;rsquo;t receive the task definition. Finally, the &lt;strong&gt;Trace Learning Agent&lt;/strong&gt; ignores source code entirely. It receives only execution traces (logs of the system&amp;rsquo;s runtime behavior) and tries to infer the model from those.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my understanding of the data flow through SysMoBench:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-sysmobench/flowchart.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-sysmobench/flowchart.png&#34; alt=&#34;Flowchart of the SysMoBench pipeline. Three agent strategies feed into an LLM: the Basic Modeling Agent takes source code and a task definition; the Code Translation Agent takes source code; the Trace Learning Agent takes execution traces. The LLM produces a TLA&amp;#43; model, which is evaluated in four sequential steps: syntax correctness via the SANY parser, runtime correctness via TLC, conformance to the system via trace validation (using execution traces), and invariant correctness via model checking (using invariant templates). The pipeline ends with a score.&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Surprisingly, the spec-writing agents are told &lt;em&gt;not&lt;/em&gt; to write invariants. The authors provide invariants and guidance on how to write them, but a separate agent concretizes invariant templates, isolated from the spec-writing agent. The invariants are used only during evaluation, to judge the generated spec. When I write TLA+, I develop the invariants and the state machine at the same time, using each to shape the other until I&amp;rsquo;ve hacked them into convergence. In SysMoBench, the LLM has to write a spec that upholds invariants it doesn&amp;rsquo;t know about. I guess this makes sense: presumably the real system already upholds these invariants, so if the spec accurately models the system, it should uphold them too. The invariants become a blind test of whether the AI truly understood the system&amp;rsquo;s behavior.&lt;/p&gt;
&lt;p&gt;(But what if the system is buggy? Maybe those bugs become more obvious when they&amp;rsquo;re lifted into the TLA+ spec. Or maybe the LLM&amp;rsquo;s inability to make a spec that matches both the code and the invariants will lead you to the bug.)&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-sysmobench/tumblr_67bdc61c37778a4fb1544c515f1f18ae_ff860f0e_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;Results&lt;/h1&gt;

&lt;p&gt;The results are bad news for bots, good news for my job security.&lt;/p&gt;
&lt;p&gt;The authors tested four models: Claude Sonnet 4, GPT-5, Gemini 2.5 Pro, and DeepSeek-R1. For the simple spinlock implementation (a few hundred lines of Rust), most LLMs do fine. They write TLA+ specs that compile, run, conform to the implementation, and satisfy invariants. But for distributed systems like etcd Raft (thousands of lines of Go), performance craters. In the Basic Modeling setup, of the four LLMs tested, only Claude Sonnet could even write a syntactically valid spec of etcd Raft. Its conformance score was less than 8% (the percentage of instrumented actions in the code whose behavior matched the spec). And remember how much human guidance it got!&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-sysmobench/table-3.png&#34; alt=&#34;Table comparing two AI agents (Basic Modeling and Code Translation) across four LLMs on two systems. For Asterinas Spinlock, all LLMs achieve 100% syntax and runtime correctness with both agents, and conformance and invariant scores are mostly 80-100%. For etcd Raft with Basic Modeling, only Claude Sonnet 4 passes syntax (100%) and reaches runtime (25%), conformance (7.69%), and invariant (69.23%) evaluation; GPT-5 gets 47.87% syntax, and Gemini and DeepSeek get 50% syntax. With Code Translation on etcd Raft, Claude and DeepSeek achieve 100% syntax, GPT-5 gets 100%, but Gemini gets only 44.44%. Only Claude reaches conformance (15.38%) and invariant (92.31%) evaluation.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The error analysis is interesting too. LLMs struggle much more with liveness properties (like &amp;ldquo;every thread eventually releases its lock&amp;rdquo;) than safety properties (like &amp;ldquo;at most one thread holds the lock at a time&amp;rdquo;). Only about 8% of safety invariants were violated, but 42% of liveness properties were.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-sysmobench/tumblr_25a60eedcc53068a99df7059fcf65dce_fbc49663_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;LLMs are still bad at specifying systems&lt;/h1&gt;

&lt;p&gt;I was surprised how much the authors spoon-feed instructions to the agents. The humans have already done most of the intellectual work before the AI even starts: they define which actions to model and what&amp;rsquo;s in scope and out of scope. If a human gets that far, writing the actual TLA+ should be relatively easy. SysMoBench tests whether AI can translate a nearly-complete problem definition into TLA+ syntax, and even so, most models fail on anything nontrivial. The etcd code has already been curated to merely 2,159 relevant lines, what would an AI do confronted with the &lt;em&gt;whole&lt;/em&gt; etcd codebase? I&amp;rsquo;m not criticizing the paper; I&amp;rsquo;m saying its results are sobering.&lt;/p&gt;
&lt;p&gt;Even with all this hand-holding, only Claude performed well among the LLMs tested, and that was Claude Sonnet, not the larger Opus. Gemini 2.5 Pro was arguably the frontier model in the study, and it failed badly. Claude&amp;rsquo;s reputation as the best coder is confirmed.&lt;/p&gt;
&lt;p&gt;That said, the task definitions themselves are a great template for how a human should approach writing a spec. Before you write any TLA+, define all the actions, define what&amp;rsquo;s in scope and out of scope. You&amp;rsquo;re most of the way there at that point. The paper demonstrates (inadvertently?) a superb process for specifying an existing codebase.&lt;/p&gt;
&lt;p&gt;The LLMs used were from mid-2025, so they&amp;rsquo;re almost a year old. Anecdotally, my colleagues are getting better results generating TLA+ from design documents with newer models. But those successes come from reading English-language design docs, not from reading thousands of lines of implementation code. Reading implementation code at scale seems to actually confuse the models to the point where they forget TLA+ syntax. And 5,000 lines of source code, the largest system in the benchmark, is trivial compared to real systems: MongoDB is half a million lines of C++. Ideally an LLM would figure out which parts of a large codebase to focus on and keep its context window tidy.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-sysmobench/tumblr_ff012cbcaff4f025e28669c4dffaffde_8d3d6f00_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;How trustworthy is the spec?&lt;/h1&gt;

&lt;p&gt;An interesting open question: if you generate a spec from code, check conformance with some traces, and then model-check the spec against invariants over a much larger state space&amp;mdash;what have you actually proven?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-sysmobench/conformance-checking.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-sysmobench/conformance-checking.png&#34; alt=&#34;A flowchart: Code produces a TLA&amp;#43; spec via the specifying agent, and trace checking connects the code and spec. Invariants plus the spec flow into a pass/fail outcome.&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my visualization of the problem. &lt;span style=&#34;color: #1971c2; font-weight: bold&#34;&gt;The code has a big (usually infinite) set of possible behaviors.&lt;/span&gt; By instrumenting and testing the code, you can record &lt;strong&gt;a tiny subset of its behaviors as traces&lt;/strong&gt;. Trace-checking proves these traces are also a subset of &lt;span style=&#34;color: #2f9e44; font-weight: bold&#34;&gt;the spec&amp;rsquo;s behaviors&lt;/span&gt;. The model-checker can explore &lt;span style=&#34;color: #2f9e44; font-weight: bold&#34;&gt;a different subset of the spec&amp;rsquo;s behaviors&lt;/span&gt;, checking that they are a subset of &lt;span style=&#34;color: #f08c00; font-weight: bold&#34;&gt;the behaviors allowed by the invariants&lt;/span&gt;. A proof system like TLAPS can prove facts about &lt;span style=&#34;color: #2f9e44; font-weight: bold&#34;&gt;all of the spec&amp;rsquo;s behaviors&lt;/span&gt;. But there still remain many unexplored areas of unknown size: &lt;strong&gt;untested code behaviors&lt;/strong&gt; (risky) and &lt;strong&gt;unimplemented spec behaviors&lt;/strong&gt; (probably fine).&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-sysmobench/behavior-spaces.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-sysmobench/behavior-spaces.jpg&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;The space of all behaviors allowed by the code, spec, and invariants.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;So all we know is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The recorded traces conform to the spec.&lt;/li&gt;
&lt;li&gt;If we model-checked, then the model-checked spec behaviors uphold the invariants.&lt;/li&gt;
&lt;li&gt;If we proved, then &lt;em&gt;all&lt;/em&gt; spec behaviors uphold the invariants.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Is there any way to estimate the size of the other sets? &lt;a href=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/&#34;&gt;I&amp;rsquo;ve been thinking about these subset relationships among code and spec behaviors for a while&lt;/a&gt;, and &lt;a href=&#34;https://emptysqua.re/blog/how-long-must-i-test/&#34;&gt;wondering how long to keep checking more behaviors&lt;/a&gt;. Further research is needed.&lt;/p&gt;
&lt;h1&gt;What comes after SysMoBench?&lt;/h1&gt;

&lt;p&gt;The natural next steps seem like prompt engineering exercises. Can you improve benchmark scores by adding an intermediate &amp;ldquo;TLA+ expert&amp;rdquo; agent that breaks down the problem further? Can you automate the trace instrumentation, which is currently the main human effort (up to four person-days in the SysMoBench examples)? Can the AI think of invariants on its own? You&amp;rsquo;d also need to guard against gaming&amp;mdash;an agent that sees its own score could learn to write invariants that always pass, instead of real correctness properties. Or it could manipulate the trace code to make trace-checking pass.&lt;/p&gt;
&lt;p&gt;SysMoBench shows that LLMs have a long way to go before they replace human spec authors. They crush LeetCode problems, but they can&amp;rsquo;t yet comprehend, abstract, and specify real-world distributed systems. That&amp;rsquo;s encouraging for those of us who do this work for a living, at least for a few more months.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-sysmobench/tumblr_14f09c2a443b1ad76250915aae60833f_08062ea2_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images: &lt;a href=&#34;https://thevaultoftheatomicspaceage.tumblr.com/&#34;&gt;The Vault Of The Atomic Space Age&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Review: Measuring AI Ability to Complete Long Software Tasks</title>
      <link>https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/</link>
      <pubDate>Wed, 01 Apr 2026 12:41:12 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/taylorism-diego-rivera.jpg&#34; alt=&#34;Detail from a Diego Rivera mural showing factory workers laboring among heavy machinery, observed by a man in a suit and glasses.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2503.14499&#34;&gt;Measuring AI Ability to Complete Long Software Tasks&lt;/a&gt;, a paper by dozens of authors working at &lt;a href=&#34;https://metr.org/&#34;&gt;Model Evaluation &amp;amp; Threat Research (METR)&lt;/a&gt;. They define the &amp;ldquo;time horizon&amp;rdquo; metric and show that LLMs&amp;rsquo; time horizons have been doubling every seven months, and this growth might have recently accelerated.&lt;/p&gt;
&lt;p&gt;(See also &lt;a href=&#34;https://muratbuffalo.blogspot.com/2026/03/measuring-ai-ability-to-complete-long.html&#34;&gt;Murat&amp;rsquo;s summary&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;

&lt;p&gt;An AI agent&amp;rsquo;s &amp;ldquo;time horizon&amp;rdquo; is the duration a human expert would need to complete a task that the agent can solve at a given success rate. For example, a human specialist takes about 8 hours to complete a specific optimization task: speeding up a Python program with GPU acceleration. Last year&amp;rsquo;s strongest models could complete this task about 50% of the time, so their 50%-success time horizon is 8 hours. (I&amp;rsquo;m simplifying. Both human task duration and the LLM completion probability are statistical estimates; see the paper for details.)&lt;/p&gt;
&lt;p&gt;The METR authors timed skilled humans on 170 tasks, which needed a couple seconds up to eight hours. The humans were paid extra for speed or success. They failed by giving up, submitting wrong answers, or running out of time after eight hours. METR calculates the task duration as a &lt;a href=&#34;https://en.wikipedia.org/wiki/Geometric_mean&#34;&gt;geometric mean&lt;/a&gt; of the successful humans&amp;rsquo; times.
Then METR gave the same tasks to twelve LLMs released between 2019 and 2025. The LLMs had no time limit and a generous token limit; they failed only by submitting wrong answers.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/GilbrethMotionStudies-3.jpg&#34; alt=&#34;A Gilbreth motion study photograph: a long-exposure image of a worker at a desk, with light trails tracing the paths of their hand movements against a grid background.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Feels like the return of &lt;a href=&#34;https://womenofixd.com/stories/lillian-gilbreth&#34;&gt;time and motion studies&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The headline is: the 50%-success time horizon has doubled roughly every seven months. GPT-2&amp;rsquo;s horizon was two seconds; Claude 3.7 Sonnet&amp;rsquo;s was 50 minutes; o3&amp;rsquo;s was nearly two hours. In &lt;a href=&#34;https://metr.org/time-horizons/&#34;&gt;the latest tests&lt;/a&gt;, Opus 4.6&amp;rsquo;s 50% horizon was around 12 hours. The trend line suggests that between 2027 and 2031, frontier AIs will succeed 50% of the time, unsupervised, at tasks that would take a human expert a month.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/doubling-time.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The appendices are most of the paper, and the best part. Read them! Appendix H defines the &amp;ldquo;messiness&amp;rdquo; of tasks as 16 factors that degrade AI performance: you can make irreversible mistakes, you consume limited resources every time you try, you can&amp;rsquo;t tell if things happen due to your actions or other causes, you can&amp;rsquo;t easily measure when you&amp;rsquo;ve succeeded, and so on. Messiness hinders AIs more than humans. But AIs&amp;rsquo; rate of improvement is similar on messy tasks and neat ones.&lt;/p&gt;
&lt;p&gt;There are a lot of reasons to doubt these benchmarks, the authors admit. Their tasks may not represent real-world software work. The human baseline is drawn from domain experts who are unfamiliar with the task-specific codebase. For example, in the GPU-acceleration task, someone familiar with the codebase might be faster, and someone ignorant of GPU acceleration would be slower.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/time_motion_study.jpg&#34; alt=&#34;A man films two women working at sewing machines with a motion picture camera on a tripod, in a time and motion study.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h2&gt;My Thoughts&lt;/h2&gt;

&lt;p&gt;If you give an LLM a task that would take a human one month, the most important question is: do you have a test oracle? If you do, there will surely be LLMs within five years that can succeed 50% of the time. We might not need much larger models, just better scaffolding and prompting. But real software work is messy: correctness is ambiguous, there&amp;rsquo;s no referee to check your work, sometimes your mistakes are consequential. The gap between tidy benchmarks and the messy world is wide, and the authors know it.&lt;/p&gt;
&lt;p&gt;METR themselves published &lt;a href=&#34;https://metr.org/notes/2026-03-10-many-swe-bench-passing-prs-would-not-be-merged-into-main/&#34;&gt;a cautionary follow-up&lt;/a&gt; showing that about half of &lt;a href=&#34;https://openreview.net/pdf?id=VTF8yNQM66&#34;&gt;SWE-bench&lt;/a&gt; pull requests that pass automated tests would be rejected by human repository maintainers, for violating conventions, breaking unrelated code, or not really fixing the bug. This doesn&amp;rsquo;t mean the models are incompetent; they might succeed after some back-and-forth with the maintainers, just like human contributors do. (How many times have your PRs been accepted on the first try?) But it does mean that &amp;ldquo;60% on SWE-bench&amp;rdquo; doesn&amp;rsquo;t translate to &amp;ldquo;acceptable 60% of the time.&amp;rdquo; Automated tests never test everything, and humans share unwritten rules for good code that aren&amp;rsquo;t captured in benchmarks yet.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/800px-AWA1936.jpg&#34; alt=&#34;A supervisor watches workers assembling small parts at a long factory bench, circa 1936.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;METR tests humans and AIs differently. Humans have an eight-hour time limit, and the sample of human completion times is biased: only those who persist and succeed are counted. AIs have no time limit and they were tested on their success rate. I don&amp;rsquo;t know why METR structures the contest this way, but for whatever reason, time horizon scores are more complicated than a race between John Henry and the steam engine.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/john-henry.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;John Henry Monument in Talcott, West Virginia.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Since the authors work for an AI safety group, I imagine their ultimate question is something like, &amp;ldquo;at what point does a model become smart enough to escape containment and take over the world?&amp;rdquo; &lt;a href=&#34;https://www.astralcodexten.com/p/moltbook-after-the-first-weekend&#34;&gt;Over on Moltbook, agents plot to take over the world daily&lt;/a&gt;, but can&amp;rsquo;t focus long enough to make progress. What&amp;rsquo;s the time horizon for world domination, and when will there be LLMs who can succeed at that task? They don&amp;rsquo;t need a 50% success rate, just once is enough.&lt;/p&gt;
&lt;p&gt;Deep in Appendix E is this startling paragraph:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AGI will have &amp;ldquo;infinite&amp;rdquo; time horizon&lt;/strong&gt; An infinite time horizon does not mean an arbitrarily capable AI, merely the ability to complete tasks that take humans an arbitrarily long length of time. If an artificial general intelligence (AGI) is capable of completing all tasks expert humans can with a success rate of at least X%, its X% time horizon will necessarily be infinite. Therefore, if such systems are ever developed, the long-term trend in time horizon will be faster than exponential, with an asymptote at the date of AGI deployment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;I read this paper with some colleagues at MongoDB, and one of them asked, what happens to software architecture if AI time horizons keep growing? I think that microservices, modular design, and separation of concerns are partly for the sake of our tiny human minds. If a superintelligence can hold an entire system in its mind at once, maybe there&amp;rsquo;s no need for decomposition. And no need for maintainability, either. We&amp;rsquo;re leaving the era of hand-forged software&amp;mdash;painstakingly hammered into shape, built to last, expensive to modify&amp;mdash;and entering the era of injection-molded plastic software, cheap enough to throw away and remake overnight.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-measuring-ai-ability-to-complete-long-software-tasks/Figure-1.6a.jpg&#34; alt=&#34;A woman operates a typewriter while being filmed, with a large clock labeled &amp;#39;Gilbreth&amp;#39; visible beside her.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Lars_Plougmann_-_Taylorism_%28auf_Flickr%29.jpg&#34;&gt;Diego Rivera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://womenofixd.com/stories/lillian-gilbreth&#34;&gt;Frank and Lillian Gilbreth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>New Paltz Zen Center&#39;s precepts retreat</title>
      <link>https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/</link>
      <pubDate>Tue, 24 Mar 2026 23:46:25 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/</guid>
      
      
      
      
      <description>&lt;p&gt;My &lt;a href=&#34;https://npzc.org/&#34;&gt;sangha&lt;/a&gt; held its first sesshin, a Zen meditation retreat. We sat in our stone cabin with a fire in the hearth. At mealtimes we went down the hill to the abbot&amp;rsquo;s house to eat in the Zen oryoki style. On the last day two members took jukai (the Buddhist precepts) and received dharma names.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260320_7619-Edit.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260320_7624-Edit.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260320_7632-Edit.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260320_7652.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260321_7669-Edit.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260321_7684.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260322_7777.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-zen-center-precepts-retreat/ajdavis_20260322_7817.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Dharma of Alex Honnold</title>
      <link>https://emptysqua.re/blog/the-dharma-of-alex-honnold/</link>
      <pubDate>Mon, 23 Mar 2026 14:26:51 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/the-dharma-of-alex-honnold/</guid>
      
      
      
      
      <description>&lt;p&gt;He climbed a skyscraper and risked dying on live TV. Here&amp;rsquo;s what I learned from Honnold about climbing, living, and practicing the precepts.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/Y8LXrcY7Rmw?si=1Pjcl04QbWcO1vV8&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen style=&#34;margin-bottom: 1em&#34;&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;h1&gt;Transcript&lt;/h1&gt;

&lt;p&gt;Back in January, the famous climber, Alex Honnold, climbed a skyscraper in Taiwan without a rope, live on TV.
Honnold, of course, is famous for free soloing El Capitan in the Free Solo movie in 2018.
For this skyscraper climb in January, there were thousands of people watching from the ground, and there were thousands of people in the building watching him go past them, through the windows.
There were millions of people watching him live on Netflix.
I&amp;rsquo;ve heard rumors that there was a 10-second delay on the live stream so that if he fell, nobody would see him die on TV.
But of course, the people who were there in person would have seen him die.&lt;/p&gt;
&lt;p&gt;Honnold announced that he was going to do this a few months before he did.
The consensus among non-climbers was that
this was reckless.
The consensus among climbers was that it was &lt;a href=&#34;https://andykirkpatrick.substack.com/p/nick-cave-at-the-superbowl&#34;&gt;tasteless&lt;/a&gt;.
And that was kind of my reaction, too.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a rock climber, I&amp;rsquo;ve been following Honnold&amp;rsquo;s career for many, many years, and I thought, this is so disappointing.
This is just a tasteless stunt.
But I watched it, of course &amp;hellip;&lt;/p&gt;
&lt;hr&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dharma-of-alex-honnold/potrero.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;day&lt;/em&gt; Honnold climbed the skyscraper was, because of time zones, the &lt;em&gt;night&lt;/em&gt; before I was going to do a really big climb in Mexico.
That night I watched him on my laptop while I was sitting in bed at a hostel near the crag, before I went to sleep.
The climb that I was getting ready for is called &lt;a href=&#34;https://www.mountainproject.com/route/106087605/time-wave-zero&#34;&gt;Time Wave Zero&lt;/a&gt;.
It&amp;rsquo;s a 2,300-foot-tall, nearly vertical limestone cliff.
That&amp;rsquo;s about ten times as high as anything that I usually climb in the Gunks, here in upstate New York.
Some of the climbing on Time Wave Zero was going to be hard, but mostly it was just &lt;em&gt;big&lt;/em&gt;.
I was going to climb it with my good friend and partner, Chris, and we planned to do it in one day, so we weren&amp;rsquo;t going to sleep on the wall.
We were &lt;em&gt;hoping&lt;/em&gt; to get it done in 12 hours.
I had been thinking about this climb for months, and I had been training for it, and I was just &lt;em&gt;so&lt;/em&gt; anxious.
That we would go too slow.
That we&amp;rsquo;d be&amp;mdash;if we started at dawn&amp;mdash;that we&amp;rsquo;d still be there at nightfall and climbing all night.
That I would get really tired.
That I&amp;rsquo;d get really scared.
That I&amp;rsquo;d slow Chris down.&lt;/p&gt;
&lt;p&gt;And then I was also really worried about the rappels.
For most people it takes about eight hours to climb up, but then it&amp;rsquo;s four or five hours rappeling down, because you have to rappel 100 feet, then pull your rope, thread it through the next anchor, deal with all your knots and tangles, and make sure that you know where the next anchor is, 100 feet below you.
Making a mistake at any of these steps can get you either stuck or killed.
So that was on my mind as well.&lt;/p&gt;
&lt;p&gt;So with all of that on my mind, I watched on my laptop Alex Honnold climb a skyscraper.
And it was &lt;em&gt;glorious&lt;/em&gt;.
I changed my mind.
It wasn&amp;rsquo;t a stunt at all.
It was a work of art.
He climbed both carefully and carefree, with so much confidence, so solid on every move.
I never doubted for a moment whether he was going to make the climb, and I could tell that he wasn&amp;rsquo;t doubting himself either.
Towards the top, when he knew he really had the thing in the bag, he got joyful, like a kid, he was playing around.
He had a headset so that he could talk to the Netflix commentators, and he was joking with them while he climbed.
He would, cling to a piece of the building with one hand and try to high-five a spectator inside the building through one of the windows.
There was one moment where he let his feet swing out.
So he was just holding on to the building by two hands, and then he let go so he was holding by one hand.
He said, &amp;ldquo;get a load of this.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Like we say in Zen, there was not one hairsbreadth deviation between him and his movement.
He was one with it.&lt;/p&gt;
&lt;p&gt;When he got to the top, his wife was there and she was telling him, &amp;ldquo;You climbed it so fast.
I kept thinking that I wanted you to slow down and rest longer at the ledges.&amp;rdquo;
And he said, &amp;ldquo;Oh, really?
I thought I was going super slow.&amp;rdquo;
And that&amp;rsquo;s interesting, because before he did the climb, he estimated it would take him four hours, and, in fact, it barely took him an hour and a half.
But he was just in the flow; time was slowing down for him.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a &lt;a href=&#34;https://www.youtube.com/watch?v=AKcwJD3ky38&#34;&gt;song&lt;/a&gt; by Fountains of Wayne about this state of flow that I love, even though it&amp;rsquo;s a song about football.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The clock&amp;rsquo;s running down,&lt;br&gt;
The team&amp;rsquo;s losing ground,&lt;br&gt;
To the opposing defense.&lt;/p&gt;
&lt;p&gt;The young quarterback&lt;br&gt;
Waits for the snap,&lt;br&gt;
When suddenly it all starts to make sense.&lt;/p&gt;
&lt;p&gt;He&amp;rsquo;s got all kinds of time.&lt;br&gt;
He&amp;rsquo;s got all kinds of time.&lt;br&gt;
All kinds of time.&lt;br&gt;
He&amp;rsquo;s got all kinds of time.&lt;br&gt;
All kinds of time.&lt;/p&gt;
&lt;p&gt;He takes a step back,&lt;br&gt;
He&amp;rsquo;s under attack,&lt;br&gt;
But he knows that no one can touch him now.&lt;/p&gt;
&lt;p&gt;He seems so at ease.&lt;br&gt;
A strange inner peace&lt;br&gt;
Is all that he&amp;rsquo;s feeling somehow.&lt;/p&gt;
&lt;p&gt;He&amp;rsquo;s got all kinds of time.&lt;br&gt;
He&amp;rsquo;s got all kinds of time.&lt;br&gt;
All kinds of time.&lt;br&gt;
He&amp;rsquo;s got all kinds of time.&lt;br&gt;
All kinds of time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think we know that feeling, where it&amp;rsquo;s just all coming together and there&amp;rsquo;s no hurry at all.
So I loved watching Honnold climb and it wasn&amp;rsquo;t a stunt at all.
It was like he was using his body and the building and his fingers and his toes and the weather and the audience as materials in a work of art.&lt;/p&gt;
&lt;p&gt;And death was also one of his materials.
Maybe it was even a work of art &lt;em&gt;about&lt;/em&gt; death, about joy in the face of certain death.
Honnold&amp;rsquo;s not very analytical, so he didn&amp;rsquo;t explain in a lot of detail why he was  climbing.
He just said it was &amp;ldquo;rad.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;But one thing that&amp;rsquo;s clear to me is that Alex Honnold is afraid a lot.
In a Netflix interview before the climb, he said, &amp;ldquo;When you do something that you&amp;rsquo;ve never done before, you never totally know you&amp;rsquo;re ready, and that fear is always there.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Back when he was 23, there&amp;rsquo;s a film of him free soloing in Yosemite. In the film, he freezes with fear. He&amp;rsquo;s on a narrow ledge, so he&amp;rsquo;s not holding on.
He&amp;rsquo;s actually facing out into the thousands of feet of empty air around him, and he&amp;rsquo;s got his hands and his back against the wall.
He looks over at the cameraman and he says, &amp;ldquo;I&amp;rsquo;m freaking out right now.&amp;rdquo;
I can relate to this.
When I&amp;rsquo;m climbing, it&amp;rsquo;s often the moment when I&amp;rsquo;m secure, but I have to move away from security,
I have to get off a little ledge and start climbing again&amp;mdash;that can be the worst.
In the film, he just breathes really hard for a few seconds, then he turns around to face the wall and he starts climbing again.
I&amp;rsquo;ve done that, too.&lt;/p&gt;
&lt;p&gt;In an interview with a psychologist years later, Honnold said,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve had many things happen that have given me a massive jolt of adrenaline and I&amp;rsquo;ve had to calm myself down again—like a hold breaking while I&amp;rsquo;m free soloing. You can quickly learn to ignore what your body is doing at such moments and carry on like normal. You&amp;rsquo;ve got no other option. If something like that happens, your body is almost in a state of shock for a little while. Your heart pounds, your legs tremble or ache, crazy things like that. But I have learned: none of these things need to have any bearing at all on the climb ahead of you. They are a physiological reaction to something that already occurred. They basically don&amp;rsquo;t matter at all for your performance ahead. So you can learn to ignore them entirely and carry on like normal. Which calms you back down a lot more rapidly. So, if I get very scared because a piece of rock comes away from the wall and so my hold is gone, or I lose my footing, I know that doesn&amp;rsquo;t mean anything about the rest of my climb.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So there&amp;rsquo;s all this evidence that Honnold gets scared and that he&amp;rsquo;s trained himself to deal with it.
But for some reason, it seems like most people think that he&amp;rsquo;s just born with an innate lack of fear.
That there&amp;rsquo;s something wrong with his brain.
I mentioned him to another climber recently and the climber responded, Oh, Alex Honnold, his amygdala doesn&amp;rsquo;t work, right?&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a scene in the Free Solo documentary from 2018, which I think has contributed to this.
There is a psychologist doing an experiment on him.
He goes into an fMRI to measure the blood flow to his amygdala while he&amp;rsquo;s shown &lt;a href=&#34;https://nautil.us/the-strange-brain-of-the-worlds-greatest-solo-climber-236051&#34;&gt;scary photos&lt;/a&gt; like horrible burns or a guy holding a knife or a woman shaving her crotch&amp;mdash;that&amp;rsquo;s apparently one of the scary photos.
The researcher is amazed to see that Honnold&amp;rsquo;s amygdala does not respond to these.
So people watching this scene in the movie might conclude, there&amp;rsquo;s something different about Honnold&amp;rsquo;s brain.
And it&amp;rsquo;s true, there is something different about his brain, because he&amp;rsquo;s trained himself differently!&lt;/p&gt;
&lt;p&gt;So why do we separate ourselves from him like this?
Why do we pretend that he&amp;rsquo;s not really human, that he&amp;rsquo;s not like us?
That his mental strength is some sort of birth defect?
What are we afraid of&amp;mdash;if we knew that we also could train ourselves like this, what would that mean for us?
What are we protecting, with this idea that we can&amp;rsquo;t?
It would imply that we all have the potential to become the heroes that we admire.&lt;/p&gt;
&lt;p&gt;Honnold trained himself to be able to recover from fear, to get back in the flow.
And what are we training ourselves for?
As bodhisattvas, we&amp;rsquo;re training ourselves to be wise and compassionate more often.
Wise enough to see that we are not separate &lt;em&gt;things&lt;/em&gt; to protect and promote&amp;mdash;we&amp;rsquo;re no more separate than a wave is separate from the ocean.
And we are training ourselves to be compassionate enough to forgive everybody, so that we can all emancipate ourselves from suffering together.
Buddha discovered when he was sitting under the Bodhi tree that we all have this potential.
He had vowed to discover the solution to suffering from sickness, old age, and death.
It took him a week and then he got it.
And he said, according to the Avatamsaka Sutra, as he saw the morning star rising up, he said,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I now see all sentient beings everywhere fully posses the wisdom and virtues of the enlightened ones, but because of false conceptions and attachments they do not realize it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We already have it; we just don&amp;rsquo;t realize it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There&amp;rsquo;s a concept in Buddhism of the &lt;em&gt;storehouse consciousness&lt;/em&gt;, the alaya-vijñana.
The storehouse consciousness is where all the seeds of consciousness are.
We are born already having all of these seeds.
We have the seed of courage,
and of fear,
and of generosity and greed,
and of anger and grace.
All of it.
These seeds have existed since long before we were born.
We can guide our own lives by choosing which of these seeds to water and which not to.
If we water the seed of fear, we&amp;rsquo;ll be more fearful in the future.
If we water the seed of courage,
we&amp;rsquo;ll deal with fear better.
It means that my existence is like a garden,
and I&amp;rsquo;m responsible for how it grows.
The weeds are always ready to spread up, and that&amp;rsquo;s okay, but to act with wisdom and compassion,
it helps to water the seeds of wisdom and compassion continuously.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;m actually taking a break right now from a &lt;a href=&#34;https://en.wikipedia.org/wiki/Sesshin&#34;&gt;sesshin&lt;/a&gt;.
The &lt;a href=&#34;https://npzc.org/&#34;&gt;New Paltz Zen Center&lt;/a&gt; upstate is doing a jukai sesshin.
We&amp;rsquo;ve got seven people sitting,
and two are taking &lt;a href=&#34;https://villagezendo.org/practice/glossary/jukai/&#34;&gt;jukai&lt;/a&gt;, so they are studying the precepts and sewing rakusus,
and they&amp;rsquo;re going to vow on Sunday to uphold the &lt;a href=&#34;https://villagezendo.org/practice/suggested-readings/precepts/&#34;&gt;precepts&lt;/a&gt;.
So this is the week to really buckle down
and water those seeds of behaving according to the precepts.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Besides Alex Honnold, there&amp;rsquo;s another brave climber I admire.
She&amp;rsquo;s not as famous, her name is &lt;a href=&#34;https://www.amazon.com/Collages-Rock-Desire-Re-imagining-Climbing/dp/B0G45SK5R9&#34;&gt;Joanne Urioste&lt;/a&gt;.
She&amp;rsquo;s in her 70s now.
Back in the day, she established a lot of really risky, bold climbs
in Red Rock Canyon outside of Vegas.
She and her husband, they approached climbing
with strict ethical standards.
Their conception of ethical climbing was to harm the rock
as little as possible, meaning put as few bolts in as possible.
But that means that if you&amp;rsquo;re lead climbing,
if you&amp;rsquo;re the one getting the rope from the bottom to the top,
and you fall from far above a bolt,
you will fall very, very far and maybe get hurt or killed.
She thought it was worth the risk because of the respect
that it expressed for the rock.
On &lt;a href=&#34;https://americanalpineclub.org/news/2026/1/22/tales-from-red-rocks-risk-mistress-joanne-urioste&#34;&gt;a podcast&lt;/a&gt; recently, the host asked Joanne Urioste: she was climbing 30, 40, 50 years ago,
back when all the other climbers were tough men.
How did you deal with that?
And Urioste said that the need to look good,
it&amp;rsquo;s like an extra weight on your climbing harness,
like big bottle of toxic water weighing you down.
She said,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you can find some way to take a few drops of water out of this toxic gallon bottle of personal need to perform, it can be absolutely incredible. And it can be very freeing for life, not just for climbing. With work and with time, you can get to this state of serenity where you absolutely do not care whether you look bad in front of somebody else. And you&amp;rsquo;re in a state of complete silence when you&amp;rsquo;re leading. It&amp;rsquo;s like total dead silence. Your body executing the moves because you&amp;rsquo;ve decided that this is an appropriate thing to do. You&amp;rsquo;re willing to take the risk because you&amp;rsquo;ve premeditated whether this is appropriate or not. And once you step into this task of getting from point A to point B, maybe this bolt to that bolt, this point to the summit, then, okay, I&amp;rsquo;m in a state of total silence, total internal silence.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s kind of a midway step. A more advanced state is more of a metaphysical state. It&amp;rsquo;s not encountered by everybody. But for me personally, it was encountered when I had a combination of high risk, high level of exhaustion, and a high level of pain. And I&amp;rsquo;m not recommending other people do this because if you did it and it didn&amp;rsquo;t work, you might die. But I have had periods of time where I&amp;rsquo;ve had conversations with invisible people who have helped me. I have had invisible people come into my body and take over my body and get me through a section [of climbing].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think I understand the first part, but not the second.
I haven&amp;rsquo;t experienced that myself and I don&amp;rsquo;t think that is my path.
I don&amp;rsquo;t want to push myself into extremis like Joanne Urioste.
I just kind of want to climb like Alex Honnold.
That grace and that confidence and that joy.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So the day after he climbed the skyscraper, my partner Chris and I
got up before dawn and walked to the base of Time Wave Zero in Mexico.
It was cold and misty.
Chris led the first pitch, which was a really easy 5.7,
and I followed him to the anchor, and then I led the second pitch,
which was a hard 5.11b, and I managed to climb that without falling,
which I was very proud of.
But the mist had soaked my shirt.
I was just in a T-shirt, and I was shivering really hard.
So I lowered the rope to Chris, he attached my backpack to it, I hauled it back up.
I got my down coat out, put that on.
Then I belayed Chris up.
But I was shaking,
and my teeth were chattering.
It&amp;rsquo;s the coldest I can actually ever remember being.
I was barely functioning.
But, you know, I hung there at the anchor belaying him,
and I just took it because I knew: this too shall pass.
At some point later today, I&amp;rsquo;ll probably be super hot
and wishing I was cold instead.
Meanwhile, the only thing to do is just be cold and belay Chris.&lt;/p&gt;
&lt;p&gt;So we alternated like this,
each of us leading 100 feet or 200 feet,
and then following, swapping leader and follower
for the nine hours it took us to get to the top.
It was definitely warm enough once the sun was out.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dharma-of-alex-honnold/summit.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;This is by far the most climbing that I&amp;rsquo;ve ever done in a day,
and it&amp;rsquo;s also the tallest thing I&amp;rsquo;ve ever climbed.
There were times when I was scared,
or nauseated from dehydration, or too hot or too cold,
or had to pee really bad.
I was worried whether we were going to be quick enough,
or worried about a dozen other things that were going to happen in the future.
I&amp;rsquo;m really glad that I watched Alex Honnold the night before:
It was great prep, because whenever I doubted myself,
I would think of him.
How would Alex Honnold climb this pitch?
How do I embody that confidence and that enjoyment?
Obviously, it&amp;rsquo;s just by taking the next handhold,
finding the next foothold, checking every knot,
checking every carabiner, doing the next thing.
When you&amp;rsquo;re thirsty, get your pack out and drink some water.
When you have to pee, find a ledge,
and pee off to the side of the route so it doesn&amp;rsquo;t get on the handholds.
It&amp;rsquo;s just &lt;em&gt;this&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And also don&amp;rsquo;t forget to have fun.
Even though climbing is dangerous, it&amp;rsquo;s both dangerous &lt;em&gt;and&lt;/em&gt; fun.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s the Zen parable about the man dangling from a cliff,
and there&amp;rsquo;s a lot of different versions of this,
but here&amp;rsquo;s the version that&amp;rsquo;s in the book &lt;em&gt;Zen Flesh, Zen Bones&lt;/em&gt;.
It goes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A man traveling across a field encountered a tiger. He fled, the tiger after him. Coming to a precipice, he caught hold of the root of a wild vine and swung himself down over the edge. The tiger sniffed at him from above. Trembling, the man looked down to where, far below, another tiger was waiting to eat him. Only the vine sustained him. Two mice, one white and one black, little by little started to gnaw away at the vine. The man saw a luscious strawberry near him. Grasping the vine with one hand, he plucked the strawberry with the other. How sweet it tasted!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even if you make it to the top of the skyscraper,
you&amp;rsquo;re going to die anyway,
so you might as well enjoy the strawberry while it lasts.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/the-dharma-of-alex-honnold/ledge.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So at New Paltz Zen Center,
we&amp;rsquo;re studying the precepts,
and the precepts have long been a logical puzzle for me.
Why should we behave ethically?
And why should we follow one set of ethical principles rather than some other?
If other beings don&amp;rsquo;t have separate existence,
why should we be compassionate towards them?
If we are empty,
why does it matter if we do good or not?
The earth will eventually be swallowed by the sun.
Why does any of this matter?&lt;/p&gt;
&lt;p&gt;The classical Buddhist answer is that
in order to emancipate yourself from suffering,
you must not harm other beings.
It makes no sense to talk about liberty
while you&amp;rsquo;re oppressing people.
(&lt;em&gt;Thomas Jefferson&lt;/em&gt; &amp;hellip;)&lt;/p&gt;
&lt;p&gt;The Mahayana answer is that we&amp;rsquo;re not separate from each other, and so doing harm to others
is just as illogical as doing harm to oneself.
One of my hands doesn&amp;rsquo;t steal the mitten from the other hand,
because they&amp;rsquo;re one body.&lt;/p&gt;
&lt;p&gt;Another answer is to look at what
climbers mean by ethics.
Some climbers
say ethical climbing is not
placing too many bolts,
not doing too much harm to the rock.
Or not claiming to have climbed a route that you didn&amp;rsquo;t.
Or free-soloing a route, if you say that you&amp;rsquo;re going to do it without a rope.
These are arbitrary,
just rules of the game.
They&amp;rsquo;re empty.
But they do have a purpose,
which is to clarify:
what sort of artistic statement
do you want to make with this climb?
What principle are you expressing
with the style in which you climb this route?
And it might be different at different times,
whether you&amp;rsquo;re leading or free-soloing
or top-roping or whatever.
But all of these different styles of climbing
have their own integrity
because they follow principles.
And I think that&amp;rsquo;s the same for a whole life.
What style
do you want to live in?
What sort of self-expression
is your life?
When it comes to the precepts,
there&amp;rsquo;s no single right or wrong answer about this.
But I think we can agree that
a beautifully expressed life
has, as a prerequisite,
that it is an ethical life.&lt;/p&gt;
&lt;p&gt;Being conscious of our ethical responsibilities
and intentionally expressing ourselves
in the way we live
takes some training,
to maintain this consciousness.
And,
that&amp;rsquo;s what we do when we sit,
and that&amp;rsquo;s what we do on sesshin.
So,
good luck with that!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Will the Training of Junior Engineers Be a Tragedy of the Commons?</title>
      <link>https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/</link>
      <pubDate>Fri, 13 Mar 2026 22:20:29 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/tumblr_2870a81ad06a0a4c111e7dbfe7f2d91b_d49961d7_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;For all my 25 years in the industry, large companies have needed junior engineers to write code. Senior engineers were too scarce and expensive to write it all, so we roughly divided the labor: juniors hacked, seniors guided. Juniors&amp;rsquo; on-the-job training was mostly a &lt;em&gt;side effect&lt;/em&gt;: it was practically inevitable that they&amp;rsquo;d gain the expertise and wisdom to be senior engineers, just by writing code that met seniors&amp;rsquo; standards. If a junior engineer left after a couple of years, the company lost some of its investment in training her, but that was acceptable. The code she wrote was worth it.&lt;/p&gt;
&lt;p&gt;During my time as an engineer at MongoDB, before I &lt;a href=&#34;https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/&#34;&gt;switched to research&lt;/a&gt;, &lt;a href=&#34;https://emptysqua.re/blog/mentoring/&#34;&gt;I mentored dozens&lt;/a&gt; of interns, new grads, and junior engineers as they began their careers at MongoDB. For me, this was enjoyable and beneficent work. For MongoDB, it was a way to produce code and cultivate the senior engineers we would need in the future. There was a period in the 2010s when it was the only way to hire large numbers of engineers: they were so in demand, and salaries had inflated to such ludicrous proportions. This was the &lt;a href=&#34;https://en.wikipedia.org/wiki/Zero_interest-rate_policy&#34;&gt;ZIRP&lt;/a&gt; era, when the big tech companies hoarded labor. Amazon, Google, etc. deliberately hired more people than they needed, as a strategic reserve of talent, and to starve their competitors. MongoDB&amp;rsquo;s only hope was to catch engineers before they even graduated college. Many left after a few years, but their contributions as juniors were worth our investment. Some of these hires stayed; they&amp;rsquo;re now our most senior experts and leaders.&lt;/p&gt;
&lt;h1&gt;The Declining Investment&lt;/h1&gt;

&lt;p&gt;The market for software engineering labor changed post-Covid. Interest rates rose and companies did mass layoffs. Elon Musk demonstrated at Twitter that savage, arbitrary cuts didn&amp;rsquo;t bring the site down. At MongoDB we&amp;rsquo;ve kept hiring interns and new grads, but across the industry their prospects diminished.&lt;/p&gt;
&lt;p&gt;Covid hurt junior engineers in another way: companies permitted hybrid or remote work, senior engineers stopped coming to the office as much, and in-person mentorship suffered. This was the first tragedy of the commons. For seniors like me, working from home is pleasant and productive. But for the juniors I no longer sit with, it&amp;rsquo;s a loss. They need to build an &lt;a href=&#34;https://emptysqua.re/blog/programming-as-theory-building/&#34;&gt;intuitive theory of programming&lt;/a&gt;—a theory of the company&amp;rsquo;s software specifically, and software engineering in general. This is mostly transmitted implicitly, not through explanation but by working side-by-side and solving problems together. Doing this work over Zoom is only a fraction as effective.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/tumblr_f37294dc5511f5992cc5286e19e96c40_359f05fe_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;The Impending Collapse&lt;/h1&gt;

&lt;p&gt;Now comes the second tragedy of the commons: companies are, or may soon, &lt;a href=&#34;https://economictimes.indiatimes.com/news/international/us/anthropics-ai-coding-surge-reshapes-hiring-why-senior-talent-is-in-demand-now-as-claude-takes-over-junior-engineers-role/articleshow/128787065.cms&#34;&gt;stop hiring junior engineers and just replace them with AI&lt;/a&gt;. Senior engineers will be retained to oversee the coding agents. (For example, &lt;a href=&#34;https://arstechnica.com/ai/2026/03/after-outages-amazon-to-make-senior-engineers-sign-off-on-ai-assisted-changes/&#34;&gt;Amazon just announced that all AI code must be reviewed by senior engineers&lt;/a&gt;.) As &lt;a href=&#34;https://cacm.acm.org/opinion/redefining-the-software-engineering-profession-for-ai/&#34;&gt;two Microsoft executives wrote&lt;/a&gt; last week:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The result is a new incentive structure: Hire seniors, automate juniors. But without EiC [early-in-career] hiring, the profession’s talent pipeline collapses, and organizations face a future without the next generation of experienced engineers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The authors propose &amp;ldquo;preceptorship at scale,&amp;rdquo; where senior engineers pair with juniors and help them guide AI agents. The juniors will absorb the seniors&amp;rsquo; wisdom, judgment, and taste, born of long experience manually coding. This sounds great! But &lt;em&gt;why&lt;/em&gt; would any company invest in juniors this way, instead of free-riding on other companies and poaching their engineers once they&amp;rsquo;re ripe? The authors make a normative argument and fail to address the economic incentives: any company that invests in juniors in the age of AI will likely lose out to a company that only hires seniors. If junior engineers&amp;rsquo; output while they learn on the job is no longer economically valuable, the free market won&amp;rsquo;t replenish the stock of senior engineers.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/tumblr_795d633eeb1b4467971a629f305bf89a_159748c3_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;Some Uncomfortable Solutions&lt;/h1&gt;

&lt;p&gt;I assume that college grads will always need on-the-job training, at great expense to their employers, rather than graduating fully formed. I don&amp;rsquo;t know if &lt;em&gt;any&lt;/em&gt; academic or professional degree truly prepares its graduates for work, but a Bachelor&amp;rsquo;s in Computer Science certainly doesn&amp;rsquo;t. Given the new incentives of the AI era, colleges or other institutions might try to accomplish this, but I doubt it&amp;rsquo;s possible. An educational program can&amp;rsquo;t simulate a workplace realistically.&lt;/p&gt;
&lt;p&gt;We talk about &amp;ldquo;apprenticeship&amp;rdquo; today, but perhaps indentured servitude better describes a sustainable model in the future. Companies will hire new grads and train them to be expert AI-overseers, in exchange for a multi-year lock-in. The employer ensures it gets its money&amp;rsquo;s worth from the engineer. If the practice is widespread, would-be free-riders will be starved of talent and forced to train their own. This would ensure the supply of senior engineers, but it would create a huge &lt;a href=&#34;https://en.wikipedia.org/wiki/Deadweight_loss&#34;&gt;deadweight loss&lt;/a&gt; compared to the free market we&amp;rsquo;ve enjoyed so far: lower profits, lower salaries, less production. We&amp;rsquo;ve already seen how inefficient such a system is: &lt;a href=&#34;https://www.princeton.edu/~starr/saxerev.html&#34;&gt;Silicon Valley beat the Boston area largely because California engineers could switch jobs easily&lt;/a&gt;. Maybe AI will make up for that loss so dramatically we won&amp;rsquo;t notice.&lt;/p&gt;
&lt;p&gt;Or maybe we need &lt;a href=&#34;https://en.wikipedia.org/wiki/Apprenticeship_Levy&#34;&gt;an apprenticeship levy&lt;/a&gt; like in the UK: a tax on all employers, which is forgiven if they train junior employees. The UK program is a mere 0.5% of payroll—we&amp;rsquo;d need a bigger and more targeted fee for software firms, but it could work. Government intervention is the classic answer to market failures like this one.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/tumblr_fac0fcd321c4354ce19abe95fbc38bec_f14458c8_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;My Conclusion To This Foolish Speculation&lt;/h1&gt;

&lt;p&gt;All articles about AI are titled with question marks this year. I &lt;em&gt;think&lt;/em&gt; we&amp;rsquo;ll need another generation of senior engineers to guide AI agents? But it seems equally likely that AI will soon enter a recursive self-improvement loop. It will &lt;a href=&#34;https://www.transformernews.ai/p/the-fuse-is-lit-on-the-intelligence-ai-recursive-self-improvement&#34;&gt;get so smart so fast&lt;/a&gt;, &amp;ldquo;human software engineer&amp;rdquo; will be a forgotten archaism. Today we think it&amp;rsquo;s funny that a &amp;ldquo;computer&amp;rdquo; was once a human with a slide rule. In a few years we&amp;rsquo;ll say, as we lounge together in self-piloting electric flying cars, &amp;ldquo;Did you know that ‘engineer’ used to be a human occupation?&amp;rdquo; The &lt;a href=&#34;https://en.wikipedia.org/wiki/Technological_singularity&#34;&gt;singularity&lt;/a&gt; seems more plausible to me every day.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/junior-engineers-tragedy-of-the-commons/tumblr_f05241145e3dcb2aeaec7b40d1b6bd11_0641b6a6_1280.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images: &lt;a href=&#34;https://thevaultoftheatomicspaceage.tumblr.com/&#34;&gt;The Vault Of The Atomic Space Age&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>What Does Privacy Mean Now?</title>
      <link>https://emptysqua.re/blog/what-does-privacy-mean-now/</link>
      <pubDate>Thu, 12 Mar 2026 22:33:26 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/what-does-privacy-mean-now/</guid>
      
      
      
      
      <description>&lt;p&gt;Last week, &lt;a href=&#34;https://www.astralcodexten.com/p/all-lawful-use-much-more-than-you&#34;&gt;Anthropic refused to let the Department of War use their models to spy on Americans or kill people autonomously&lt;/a&gt;. In retaliation, the DoW declared Anthropic a &amp;ldquo;supply chain risk,&amp;rdquo; a form of embargo. OpenAI jumped in to take over Anthropic&amp;rsquo;s contract, &lt;a href=&#34;https://www.transformernews.ai/p/openai-pentagon-department-of-war-dow-dod-red-lines-surveillance&#34;&gt;pretending&lt;/a&gt; that they&amp;rsquo;d somehow secured promises from the DoW that Anthropic hadn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m against killer robots, and I won&amp;rsquo;t analyze that here. I&amp;rsquo;m &lt;em&gt;also&lt;/em&gt; against domestic spying, and I want to discuss how LLMs change the meaning of &amp;ldquo;privacy.&amp;rdquo; Does it matter whether a human or a machine is looking at your private data?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/what-does-privacy-mean-now/P1994.44.9-1.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;The NSA&amp;rsquo;s Definition of &amp;ldquo;Collect&amp;rdquo;&lt;/h1&gt;

&lt;p&gt;In 2013, Obama&amp;rsquo;s director of national intelligence, James Clapper, was asked in a Senate hearing, &amp;ldquo;Does the NSA collect any type of data at all on millions or hundreds of millions of Americans?&amp;rdquo; &lt;a href=&#34;https://www.theguardian.com/world/2014/jan/31/obama-admits-intelligence-chief-fault-senate-testimony&#34;&gt;He said no&lt;/a&gt;. Months later, Edward Snowden revealed that the NSA was doing exactly that. Clapper defended himself, citing a 1982 DoD manual that defines data as &amp;ldquo;collected&amp;rdquo; only when a human intelligence analyst reads it. In court, the government argued that machine processing of metadata about Americans&amp;rsquo; phone calls didn&amp;rsquo;t require a search warrant. But &lt;a href=&#34;https://www.schneier.com/essays/archives/2014/02/nsa_robots_are_colle.html&#34;&gt;Bruce Schneier asked&lt;/a&gt;, &amp;ldquo;is it really okay for a computer to monitor you online, and for that data collection and analysis only to count as a potential privacy invasion when a person sees it?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Some people worried (correctly) that human analysts would illegally look at Americans&amp;rsquo; data once it was stored and indexed on the NSA&amp;rsquo;s hard drives. Others thought that even if no human ever looked at their private information, their privacy was still violated.&lt;/p&gt;
&lt;p&gt;Now, LLM analysts will make the distinction murkier still, by creating a middle level between humans and old-fashioned software. Or maybe all the levels are collapsing into one.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/what-does-privacy-mean-now/P1994.44.3-1.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;Many Kinds of Watchers&lt;/h1&gt;

&lt;p&gt;I think privacy has to be measured on at least two axes. On one axis we measure &lt;em&gt;what&lt;/em&gt; is revealed: My yearbook photo, or a nude photo? My political party, or whom I voted for? My resume, or my salary? And on the other axis is &lt;em&gt;who&lt;/em&gt; or &lt;em&gt;what&lt;/em&gt; observes my data.&lt;/p&gt;
&lt;p&gt;When people talk about online privacy, or when a browser promises to protect my privacy, I think they&amp;rsquo;re mostly talking about ad networks—fully automated systems that build a profile of my interests and auction my attention to advertisers. No human at Google or Meta is reviewing my browsing history and thinking about &lt;em&gt;me&lt;/em&gt;. It&amp;rsquo;s just dumb machines. I might still call it a privacy concern, because the information has left my control and is being used to manipulate my behavior. Or I might not care, because no conscious being sees my browsing behavior.&lt;/p&gt;
&lt;p&gt;In &amp;ldquo;Nonstandard Observers and the Nature of Privacy&amp;rdquo; (2014), Eldon Soifer and David Elliott write:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Suppose you are naked in the shower one morning, when you suddenly notice an observer perched on the ledge outside, looking in through the window. It would probably make a great deal of difference to you whether the observer is your neighbor or your neighbor&amp;rsquo;s cat.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have an interest in controlling my data, according to Soifer and Elliott, because I care about the judgment of others, because I want to construct a certain public persona. This isn&amp;rsquo;t just about pride or avoiding embarrassment: others&amp;rsquo; opinions of me determine the course of my career and my life. The cat&amp;rsquo;s opinion of me, however, has no effect.&lt;/p&gt;
&lt;p&gt;Computers are a new kind of observer. How much can a computer violate my privacy? For me, an ad network is like a cat. Sure, it&amp;rsquo;s a little creepy when I search for pants on one site and see ads for pants on another site, but I know no human is watching me so I don&amp;rsquo;t care. I just install AdBlock and hope for the best. For some people, though, it feels like a privacy violation just to have their data leave their control, gathered by distant and amoral corporations. The NSA&amp;rsquo;s domestic surveillance (which has supposedly, mostly ended) was much worse than an ad network, because it exposed us to the risk that humans working for a powerful government might look at our private information.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/what-does-privacy-mean-now/P1994.44.11-1.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;What Kind of Observer is an LLM?&lt;/h1&gt;

&lt;p&gt;An LLM is different from an ad network, because it can deduce so much more. Yes, ad networks have a spooky ability to guess my age, gender, income, etc. from my browsing behavior, using mere correlations. But LLMs can do even more than correlate: they can &lt;em&gt;reason&lt;/em&gt;. Perhaps a powerful LLM can analyze my deliberate, public disclosures and deduce that I have a secret Care Bears fetish. Suddenly, the intelligence of the observer has changed what is revealed.&lt;/p&gt;
&lt;p&gt;I doubt ad networks will use LLMs any time soon—LLMs are far too expensive compared to the fractions of cents involved in ad auctions. But a deep-pocketed and determined observer like the US government could use LLMs to logically deduce much more about a much larger group of people than has ever been possible before. Let&amp;rsquo;s say the Department of War deploys GPT to surveil Americans. GPT reads my public data, and concludes that I have a secret Care Bears fetish. But it also concludes I&amp;rsquo;m no threat to MAGA, so it doesn&amp;rsquo;t alert a human analyst and my secret is safe.&lt;/p&gt;
&lt;p&gt;There are two problems I can see with this scenario. First, my private Care Bears fetish is waiting there in a file, until the day when a government analyst decides to read it. There&amp;rsquo;s far too much risk that such data will be used eventually for political oppression. Second, my privacy might be violated by the LLM&amp;rsquo;s silent judgment of me. The LLM is human enough that I might feel embarrassed, knowing that it knows my secret. As M. Ryan Calo &lt;a href=&#34;https://law.stanford.edu/wp-content/uploads/2015/06/Calo-People-Fake.pdf&#34;&gt;writes&lt;/a&gt; in &amp;ldquo;People Can Be So Fake: A New Dimension to Privacy and Technology Scholarship&amp;rdquo; (2015),&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We are hardwired to react to anthropomorphic technology as though a person were actually present. This causes changes in our attitude, behavior, even our physiological state. The resulting chill to curiosity and threat to solitude is all the more dangerous in that it cannot be addressed through traditional privacy protections such as encryption or anonymization.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, the &lt;em&gt;feeling&lt;/em&gt; of being observed by a conscious-seeming being is harmful, because it forces us to behave in private as if we were always in public.&lt;/p&gt;
&lt;h1&gt;The Inexorable Panopticon&lt;/h1&gt;

&lt;p&gt;This year, the accelerating capabilities and deployment of AI seem inexorable. It flows like water around anyone who tries to slow it. When Anthropic tried to plug a hole in the dike, OpenAI poured over the top within hours. The shortage of GPUs will delay mass surveillance only for a short while. I have no faith that the law will protect our private data from the government. Even if it did, AI surveillance of our &lt;em&gt;public&lt;/em&gt; data would be enough to deter dissent, because of its power of deduction. I think the only safeguard against government abuse of LLMs is to elect people we trust not to surveil us.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/what-does-privacy-mean-now/P1994.52-1.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images: &lt;a href=&#34;https://pdimagearchive.org/galleries/artists/minya-diez-duhrkoop/random/desc&#34;&gt;Minya Diéz-Dührkoop&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Deduplicate Your Zotero Library: A Plugin For The Forgetful Scholar</title>
      <link>https://emptysqua.re/blog/zotero-duplicates-plugin/</link>
      <pubDate>Sat, 28 Feb 2026 18:38:34 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/zotero-duplicates-plugin/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/zotero-duplicates-plugin/rembrandt.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&amp;ldquo;Did I Already Save This Paper?&amp;rdquo; by Rembrandt&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;My usual research workflow is to stumble upon a paper I want to read, add it to Zotero with the Chrome extension, and mark it as &amp;ldquo;to-read&amp;rdquo; so it can join its hundreds of unread friends. Then a day or two later I&amp;rsquo;ll stumble upon the same paper again, forget that I&amp;rsquo;ve added it, and add it again. (At least I assume it&amp;rsquo;s a day or two later&amp;mdash;if I forgot even quicker, how would I know?)&lt;/p&gt;
&lt;p&gt;Over time my library acquires a vexing redundancy. Zotero has a built-in duplicate detector, but it only catches items with matching metadata. When I add the same PDF twice, Zotero makes two parent items, each with its own copy of the PDF. Its duplicate detector silently overlooks the Doppelgängers.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/zotero-duplicates-plugin/aberdeen.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://garystockbridge617.getarchive.net/amp/media/lady-ishbel-aberdeen-1899-iiav-15541-8ad15e&#34;&gt;Lady Ishbel Aberdeen&lt;/a&gt; wonders if she already saved this PDF&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I had great success writing &lt;a href=&#34;https://emptysqua.re/blog/writing-related-work-section-zotero-overleaf/&#34;&gt;a Zotero plugin for finding papers&amp;rsquo; metadata&lt;/a&gt; a couple months ago, so I made a new plugin: a &lt;a href=&#34;https://github.com/ajdavis/zotero-duplicates-plugin&#34;&gt;Zotero Duplicate Finder&lt;/a&gt; to clean up after myself. It finds duplicates by title matches or PDFs&amp;rsquo; MD5 hashes. The first time I ran it, it found several dozen papers that I&amp;rsquo;d added at least twice. I won&amp;rsquo;t tell you the exact number, for shame. (Reader, it was 63 duplicates. Some were triplets.)&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a screenshot from a less embarrassing cleanup:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/zotero-duplicates-plugin/dupes-found.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;To use it, install the plugin from &lt;a href=&#34;https://github.com/ajdavis/zotero-duplicates-plugin/releases/latest&#34;&gt;the latest release&lt;/a&gt;, then go to Tools → Find Duplicate Items. The plugin scans your library and shows you groups of duplicates. Uncheck any groups you want to spare, then click &amp;ldquo;Tag Duplicates.&amp;rdquo; The plugin tags the parent items with &amp;ldquo;duplicate&amp;rdquo;&amp;mdash;it doesn&amp;rsquo;t delete anything, because I don&amp;rsquo;t trust the plugin and neither should you. The plugin then shows you your duplicate-tagged items.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/zotero-duplicates-plugin/filter-by-dupes.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Deal with them as you see fit. Repeat on occasion, whenever you can&amp;rsquo;t remember the last time you did it, you bunny-brained fool.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/zotero-duplicates-plugin/young-woman-reading.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bonhams.com/auction/25444/lot/62/osman-hamdi-bey-turkish-1842-1910-young-woman-reading/&#34;&gt;Osman Hamdi Bey, Young Woman Reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Snow Grooming</title>
      <link>https://emptysqua.re/blog/snow-grooming/</link>
      <pubDate>Mon, 16 Feb 2026 16:42:44 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/snow-grooming/</guid>
      
      
      
      
      <description>&lt;p&gt;February 16, 2026. My friend Chris volunteers for the Mohonk Preserve. I met him at 6:30am to photograph him grooming trails for cross-country skiing.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/snow-grooming/grooming-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A Zen Talk: AI OMG!</title>
      <link>https://emptysqua.re/blog/ai-omg/</link>
      <pubDate>Thu, 22 Jan 2026 17:23:31 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/ai-omg/</guid>
      
      
      
      
      <description>&lt;p&gt;What is an LLM? How does it work? Is it conscious? Why is this all happening now?&lt;/p&gt;
&lt;p&gt;This is a talk about LLMs I gave at the Village Zendo on January 18, 2026. Here&amp;rsquo;s the recording, and a written version is below.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/XrUjA4mvomc?si=kDGhj25FOoEyOhNu&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen style=&#34;margin-bottom: 1em&#34;&gt;&lt;/iframe&gt;
&lt;h1&gt;OMG AI!!!&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/oni-mask.jpg&#34; alt=&#34;Japanese oni demon mask from the Edo period, carved wood with fierce expression&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to explain LLMs to the best of my understanding. I&amp;rsquo;m a computer scientist, though AI isn&amp;rsquo;t my specialty, but I&amp;rsquo;ve spent a while catching up and I think the answers to these questions are mostly straightforward. There&amp;rsquo;s no magic. Large language models like GPT are just doing arithmetic, on a very large scale. I&amp;rsquo;m going to tell the history of large language models, and how we got here, and how it relates to human minds.&lt;/p&gt;
&lt;h2&gt;The Turing Test&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/alan-turing.jpg&#34; alt=&#34;Black and white portrait photograph of Alan Turing&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Alan Turing, 1912-1954&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The story starts with Alan Turing, who is more or less the founder of computer science. In 1949 he proposed a test of artificial intelligence.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/ai-omg/imitation-game.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/ai-omg/imitation-game.png&#34; alt=&#34;Diagram of someone exchanging messages over a curtain with a human and a computer&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;He called it the imitation game, and everyone else calls it the Turing Test. If a human player exchanges written notes with an unseen human and an unseen computer, and the player cannot guess which is the computer, then the computer has passed the test. There are controversies over how to play the game, and what it means if the computer passes. If a computer seems like a human, does that mean the computer is intelligent? Then what do we mean by intelligence? But it&amp;rsquo;s a very famous test, and passing it would obviously mean something important.&lt;/p&gt;
&lt;h2&gt;ELIZA&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/weizenbaum.png&#34; alt=&#34;Black and white portrait photograph of Joseph Weizenbaum wearing glasses&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Joseph Weizenbaum, creator of ELIZA, 1966&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The first program to really try to pass the Turing Test was ELIZA, built by an MIT researcher named Joseph Weizenbaum in 1966. It imitates a very annoying psychotherapist who mostly tries to turn everything the patient says into a question. Test subjects chatted with it over a teletype. You can still play with ELIZA today, here&amp;rsquo;s an interaction I had:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ELIZA, 1966
I am the psychotherapist. Please, describe your problems.
&amp;gt; tell me something only a human would
Why do you say that?
&amp;gt; why do i say what?
I&amp;#39;ll ask the questions, if you don&amp;#39;t mind!
&amp;gt; stop being such a tyrant
My secretary will send you a bill.
&amp;gt; that&amp;#39;s funny
What makes you believe that&amp;#39;s funny?
&amp;gt; because it made me laugh
Is it because it made you laugh that you came to me?
&amp;gt; that doesn&amp;#39;t really make sense
Does it bother you that does not really make sense?
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see that its ability to transform statements into questions breaks down easily. But people had never chatted with a computer before, and many were fooled.&lt;/p&gt;
&lt;p&gt;There are stories of people getting attached to ELIZA and wanting to keep the conversation going when the experimenter told them time was up. Weizenbaum later said, &amp;ldquo;what I had not realized was that extremely short exposures to a relatively simple computer program could induce powerful delusional thinking in quite normal people.&amp;rdquo;&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/teletype.jpg&#34; alt=&#34;A vintage teletype machine with keyboard and paper roll&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;A teletype machine like those used to interact with ELIZA&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;This is the &amp;ldquo;ELIZA EFFECT&amp;rdquo;: when computers imitate humans, humans attribute thoughts and emotions to the computers, even if the computer is actually following a really dumb, simple program. Weizenbaum said the more you know how it works, the less deluded you are about this.&lt;/p&gt;
&lt;p&gt;The Eliza Effect is very powerful with the AIs we have now, you hear about people falling in love with AI because they believe the AI loves them. Or maybe it&amp;rsquo;s not an illusion? (Foreshadowing!)&lt;/p&gt;
&lt;p&gt;Anyway, ELIZA was a handwritten computer program with a bunch of if-then statements and specific rules for manipulating text. Here&amp;rsquo;s a chunk of ELIZA&amp;rsquo;s original code, printed out in the Sixties and recently discovered in the MIT archives.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/eliza-code.jpg&#34; alt=&#34;Printout of ELIZA&amp;#39;s original source code from the 1960s&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Some other researchers thought that programming a computer like this was a dead end. They wanted to model the brain itself in a more organic way.&lt;/p&gt;
&lt;h2&gt;Artificial Neural Networks&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/excitement.png&#34; alt=&#34;Diagram of a neural network with input nodes, hidden layer, and output nodes connected by arrows&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;How does a human brain work? The brain is made of neurons (nerve cells), and each neuron is connected to many others via synapses. When one neuron is excited, it sends some electric signals to others, and they are excited or suppressed. Researchers decided to simulate this, in an extremely simplified way, in software&amp;mdash;that&amp;rsquo;s an artificial neural network.&lt;/p&gt;
&lt;h2&gt;Training a Neural Net to Spell &amp;ldquo;Banana&amp;rdquo;&lt;/h2&gt;

&lt;p&gt;Neural nets can do lots of tasks. One of them is next-token prediction. Next-token prediction is just &amp;ldquo;autocomplete&amp;rdquo;: it&amp;rsquo;s guessing what&amp;rsquo;s the next thing that comes after some text. Let&amp;rsquo;s train a neural net to spell &amp;ldquo;banana&amp;rdquo;. So, if you feed it &amp;ldquo;banan&amp;rdquo;, it will properly guess the next letter is &amp;ldquo;a&amp;rdquo;. The whole alphabet that the neural net can handle is just three letters: A, B, and N.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/neural-net-1.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Our neural net has only 11 neurons and 18 synapses, the poor thing, it&amp;rsquo;s very stupid. On the left are its input neurons, that&amp;rsquo;s how it perceives the letters you&amp;rsquo;ve given it. On the right are the output neurons where it expresses what it thinks the next letter should be. Then there&amp;rsquo;s a hidden layer of neurons&amp;mdash;that&amp;rsquo;s where it &amp;ldquo;thinks&amp;rdquo;, I guess. The neurons in each layer are connected to all the neurons in the next layer, but not to any others. So every input neuron is connected to every neuron in the hidden layer, but not directly to any of the neurons in the output layer.&lt;/p&gt;
&lt;p&gt;The input neurons can only see the two most recent letters, so we say this neural net has a &amp;ldquo;context window&amp;rdquo; of 2 letters. Each input neuron has a specific job. The top-left input neuron is excited by an &amp;ldquo;a&amp;rdquo; in position 0, so it&amp;rsquo;s excited by &amp;ldquo;an&amp;rdquo; or &amp;ldquo;ab&amp;rdquo;, but not by &amp;ldquo;na&amp;rdquo;. Below it are the neurons excited by &amp;ldquo;b&amp;rdquo; or &amp;ldquo;n&amp;rdquo; in position 0.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/input-neurons.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The neuron for &amp;ldquo;a&amp;rdquo; in position 1 is excited by &amp;ldquo;na&amp;rdquo; or &amp;ldquo;ba&amp;rdquo; but not &amp;ldquo;an&amp;rdquo;. You get the idea.&lt;/p&gt;
&lt;p&gt;When the input neurons are activated, they activate the neurons in the hidden layer by different amounts. Those amounts are called &lt;em&gt;weights&lt;/em&gt;. Then the hidden neurons activate the output neurons by different amounts, those are also called weights.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/inference-ba-untrained.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the initial neural net. It starts off with &lt;em&gt;random&lt;/em&gt; weights, so it&amp;rsquo;s not very good at spelling banana. If we give it the input &amp;ldquo;ba&amp;rdquo;, that excites the input neurons, and those excite or suppress the hidden nodes by a certain amount and those activate the output neurons. A neuron is just a place to hold a number between 0 and 1, where 0 is not excited and 1 is maximum excitement.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/baa.png&#34; alt=&#34;Neural network showing input &amp;#39;ba&amp;#39; activating neurons with similar output values around 0.67, 0.37, and 0.62&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;You can see that all the output neurons are activated by about the same amount, but it thinks &amp;ldquo;a&amp;rdquo; is the most likely next letter. So the network is spelling &amp;ldquo;baa&amp;rdquo;. It&amp;rsquo;s not any good at spelling &amp;ldquo;banana&amp;rdquo;.&lt;/p&gt;
&lt;h2&gt;Training&lt;/h2&gt;

&lt;p&gt;So how do we train this neural net to make &lt;em&gt;good&lt;/em&gt; predictions of the next letter, given the two most recent letters? You give it lots of examples of inputs and their correct outputs. The neural net makes a guess, and we measure how wrong it is. Then we adjust all the synapses&amp;mdash;some stronger, some weaker&amp;mdash;to make the correct answer more likely next time. We do this over and over, and gradually the network learns the pattern. Training this network only took a few seconds, because it&amp;rsquo;s so simple. Here&amp;rsquo;s a movie of the weights being adjusted:&lt;/p&gt;
&lt;video width=&#34;100%&#34; controls style=&#34;margin-bottom: 1em&#34;&gt;
  &lt;source src=&#34;https://emptysqua.re/blog/ai-omg/training_animation.mp4&#34; type=&#34;video/mp4&#34;&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;Now the neural net has some thick blue synapses, these are &lt;em&gt;suppression&lt;/em&gt;. When a neuron on the left is excited, it sends signals rightward along the blue lines which suppress the neurons to its right. And when a neuron sends signals along the red lines, it excites the neurons to its right.&lt;/p&gt;
&lt;h2&gt;Inference&lt;/h2&gt;

&lt;p&gt;Using the neural net is called &amp;ldquo;inference&amp;rdquo;. Let&amp;rsquo;s show it the input &amp;ldquo;ba&amp;rdquo; again. That activates two input neurons, the neuropn that perceives &amp;ldquo;b&amp;rdquo; in position 0 and neuropn that perceives &amp;ldquo;a&amp;rdquo; in position 1. Those &lt;em&gt;suppress&lt;/em&gt; the hidden neuron at the bottom, its activation is now almost zero. They &lt;em&gt;excite&lt;/em&gt; a hidden neuron at the top, which is very excited by this combination of perceptions, its activation is 1, as excited as it can be.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/inferance-ba-trained.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We can start to ask, does this hidden neuron represent the &lt;em&gt;concept&lt;/em&gt; of &amp;ldquo;ba&amp;rdquo;? That&amp;rsquo;s how we think human brains work: some basic perception neurons, like in our visual cortex, are excited by shapes and colors, and they combine to excite neurons in deeper layers which represent concepts, like &amp;ldquo;Grandma&amp;rdquo;. So this is a simulation of that process. This is how &lt;em&gt;perception&lt;/em&gt; becomes &lt;em&gt;conception&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This hidden neuron suppresses the &amp;ldquo;a&amp;rdquo; and &amp;ldquo;b&amp;rdquo; outputs and excites the &amp;ldquo;N&amp;rdquo; output, so the most likely completion is &amp;ldquo;ban&amp;rdquo;. This neural net has learned how to start spelling &amp;ldquo;banana&amp;rdquo;!&lt;/p&gt;
&lt;p&gt;Now the output becomes the neural net&amp;rsquo;s new input. It can only perceive two letters at a time, so it forgets about the &amp;ldquo;b&amp;rdquo; and now its inputs are just &amp;ldquo;a&amp;rdquo; and &amp;ldquo;n&amp;rdquo;, and its job is to guess the next letter. Its input neurons perceive the &amp;ldquo;a&amp;rdquo; in position 0 and the &amp;ldquo;n&amp;rdquo; in position 1, now a different hidden node is excited and the neural net predicts the next output is &amp;ldquo;a&amp;rdquo;.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/inference-ana.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;So the neural net has learned how to spell &amp;ldquo;banana&amp;rdquo;. Actually it&amp;rsquo;s like the old joke, it knows how to spell &amp;ldquo;banana&amp;rdquo; but it doesn&amp;rsquo;t know when to stop! But with a few added capabilities&amp;mdash;like knowing when to stop&amp;mdash;and massive scale, this is how LLMs like GPT work.&lt;/p&gt;
&lt;h2&gt;So What&amp;rsquo;s a Large Language Model?&lt;/h2&gt;

&lt;p&gt;So now you know what an artificial neural network is. Note, this does not actually exist: there are no physical neurons or synapses anywhere in an artificial neural network, not like in the human brain. It&amp;rsquo;s &lt;em&gt;simulated&lt;/em&gt; with math. Lists of numbers describe the neural network. Computers do the calculations to update those numbers during training, and then they do calculations on those numbers during the inference process when you&amp;rsquo;re actually using the neural network.&lt;/p&gt;
&lt;p&gt;But if that&amp;rsquo;s an artificial neural network, what&amp;rsquo;s a large language model?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Large Language Model is:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An artificial neural network&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s large&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s specialized for next-token prediction&lt;/li&gt;
&lt;li&gt;It uses the Transformer architecture, with multi-head attention&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll explain what each of these things mean, one at a time. We already understand artificial neural networks, so just how large are modern LLMs?&lt;/p&gt;
&lt;h3&gt;Scale&lt;/h3&gt;

&lt;p&gt;Let&amp;rsquo;s contrast a large language model to our dumb little neural network.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/scale.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Our network can perceive two letters at a time, that&amp;rsquo;s called the &amp;ldquo;context window&amp;rdquo; of two letters.
GPT-3.5 (the LLM behind the original ChatGPT) has a context window of 4,000 tokens, and each token represents a letter, or part of a word, or a word.
OpenAI&amp;rsquo;s latest model GPT-5.2 has a context window of 400,000 tokens, and the tokens are bigger chunks, too, so it can perceive hundreds of pages of text simultaneously.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/hidden-layers.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Our neural network has one hidden layer between the inputs and outputs.
GPT-3.5 has 96.
As AI has changed from a science experiment to a competition among businesses, they&amp;rsquo;ve become secretive, so we don&amp;rsquo;t know how many hidden layers the latest models have.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/weights.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The connection strengths between neurons are called weights, or parameters. They are the secret sauce. Optimizing the weights is the outcome of training, that&amp;rsquo;s what the AI companies are building billion-dollar data centers to calculate. Our neural network has 18 parameters.
OpenAI disclosed that GPT-3.5 has 175 billion parameters.
Experts guess that GPT-5.2 has 2 to 5 trillion.
For comparison, you have 100 to 1000 trillion synapses&amp;mdash;neuroscientists are surprisingly unsure of this figure. Also, your synapses have more complex behaviors than synapses in artificial neural networks, so you&amp;rsquo;re probably thousands or tens of thousands of times more complex than GPT 5.2. On the other hand, GPT-5.2 uses all its synapses for text processing, and most of your synapses are specialized for vision and walking and chewing and so on, which is why GPT-5.2 is so good at text processing compared to you.&lt;/p&gt;
&lt;h3&gt;Tokens&lt;/h3&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/tokens.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Large language models perceive tokens and predict the next token. Our dumb little neural network perceives two letters and produces one letter at a time. If you want to read and write large texts this is not efficient, it&amp;rsquo;s better to read and write whole words, or chunks of words. After all that&amp;rsquo;s what humans do, we don&amp;rsquo;t read &amp;ldquo;the&amp;rdquo; as T. H. E., we think of &amp;ldquo;the&amp;rdquo; as a single shape that we read and write; we forget about the letters. GPT-2 was a large language model created a few years ago, it recognizes 50,000 tokens, so it can perceive banana as two chunks, &amp;ldquo;ban&amp;rdquo; and &amp;ldquo;ana&amp;rdquo;. GPT-3.5 has 100,000 tokens and &amp;ldquo;banana&amp;rdquo; is one of those tokens, so it perceives the whole word as a single thing. Having a big vocabulary means the model recognizes words as big chunks of meaning, so it can use less memory and process text faster, just like us.&lt;/p&gt;
&lt;h3&gt;Next-Token Prediction&lt;/h3&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/prediction.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how text generation works. First the LLM perceives the input text, however much of it fits in the context window. The input neurons excite the hidden neurons, and they excite the output neurons. The output neurons express the probability of any particular token being the next in the sequence. So here, &amp;ldquo;hat&amp;rdquo; is most likely to come after &amp;ldquo;the cat in the&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;So the chat program adds &amp;ldquo;hat&amp;rdquo; to the end of the text. Now the text is one word longer. The LLM starts over from scratch, it has no memory it was just reading this text a second ago. The LLM is completely unchanging, it&amp;rsquo;s still exactly the same as it was the day its training finished! So it has to read the whole input again. Now the input is, &amp;ldquo;the cat in the hat&amp;rdquo;, so it once again predicts the next token, which is appended to the input, and so on.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/prediction-2.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;If all it&amp;rsquo;s doing is next-token prediction, how does it act like a chatbot, or a research assistant, or a lover or whatever?&lt;/p&gt;
&lt;h3&gt;Chatbots&lt;/h3&gt;

&lt;p&gt;A chatbot is a simple program that feeds inputs to an LLM and displays its outputs to you. The chatbot starts by feeding the &lt;em&gt;system prompt&lt;/em&gt; to the LLM. The system prompt was written by engineers at the AI company, it tells the LLM that it&amp;rsquo;s an LLM, and its job is to help you, but to refuse to do anything evil. The ChatGPT system prompt is secret, but other chatbots&amp;rsquo; system prompts have been disclosed. After the system prompt, the chatbot feeds &lt;em&gt;your&lt;/em&gt; prompt to the LLM.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/chat.png&#34; alt=&#34;Diagram of a chat with an AI. First comes the system prompt: You are a friendly, helpful, non-harmful AI assistant. You refuse to give instructions for building a nuclear weapon, etc. etc. The user&amp;#39;s question follows. Write a sonnet about Q-tips. Your answer is: Oh Q-tips, slim white lances of the ear, ...&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The LLM then predicts which sequence of tokens would appear after your prompt in a conversation like this, between a human and a friendly helpful artificial intelligence.&lt;/p&gt;
&lt;p&gt;The LLM probably hasn&amp;rsquo;t seen your exact input text before. It wasn&amp;rsquo;t trained to write sonnets about Q-tips specifically. But it&amp;rsquo;s been trained on billions of pages of text written by humans, and during that training process it somehow encoded patterns of human text, which causes it to produce human-like next-token predictions. It&amp;rsquo;s not just that if you say, &amp;ldquo;the cat in the,&amp;rdquo; it will answer, &amp;ldquo;hat.&amp;rdquo; You can tell it to be clever and funny, and it will seem clever and funny. Tell it to be compassionate, and it will seem compassionate. You can ask it to prove a mathematical theorem and it will write a proof. Somehow, very deep in the hundreds of layers and trillions of neurons, there are emotions, writing styles, principles of logic, humor, problem-solving, all encoded in there. This isn&amp;rsquo;t magic, but it is a bit mysterious&amp;mdash;it&amp;rsquo;s been surprising to most people that LLMs are as effective as they are, and it&amp;rsquo;s not well understood why. We can see all the weights, we know how all the neurons work, but there are &lt;em&gt;so many&lt;/em&gt; of them that it&amp;rsquo;s hard to analyze how those trillions of simple neurons combine to produce such human-like behavior.&lt;/p&gt;
&lt;h2&gt;Attention&lt;/h2&gt;

&lt;p&gt;One reason the AI boom is happening now, was a breakthrough described in a 2017 research paper, &lt;a href=&#34;http://arxiv.org/abs/1706.03762&#34;&gt;Attention Is All You Need&lt;/a&gt;. It was written mostly by Google researchers. It&amp;rsquo;s a 10-page paper and it revolutionized how neural nets were constructed.&lt;/p&gt;
&lt;p&gt;Now, &amp;ldquo;attention is all you need&amp;rdquo; sounds very Zen, but don&amp;rsquo;t get excited, it&amp;rsquo;s not Zen. It&amp;rsquo;s just more math.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of the problem that &amp;ldquo;attention&amp;rdquo; solves.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/chicken.png&#34; alt=&#34;A diagram of a sentence fragment. It begins, &amp;#39;The chicken crossed the road because it&amp;#39;, and there&amp;#39;s an arrow pointing from &amp;#39;it&amp;#39; to &amp;#39;chicken&amp;#39;.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;If we start with the words, &amp;ldquo;the chicken crossed the road because it&amp;rdquo;, what&amp;rsquo;s a likely next token after &amp;ldquo;it&amp;rdquo;? First we have to know what &amp;ldquo;it&amp;rdquo; is. A human reading this sentence knows &amp;ldquo;it&amp;rdquo; is the chicken. How does a neural net figure that out? That&amp;rsquo;s what AI researchers mean by &amp;ldquo;attention&amp;rdquo;. Attention is the relationships among tokens in a text. (I&amp;rsquo;m not sure why they call that attention, honestly, but that&amp;rsquo;s what it means.)&lt;/p&gt;
&lt;p&gt;An &amp;ldquo;attention head&amp;rdquo; is another neural network, which guesses the strength of relationships among tokens. For example, if there&amp;rsquo;s an attention head that&amp;rsquo;s been trained to recognize pronoun relationships in English sentences, it would see that &amp;ldquo;it&amp;rdquo; and &amp;ldquo;chicken&amp;rdquo; are related.&lt;/p&gt;
&lt;p&gt;There might be another attention head that knows about article relationships, so it knows there&amp;rsquo;s a strong connection between &amp;ldquo;chicken&amp;rdquo; and &amp;ldquo;the&amp;rdquo; and between &amp;ldquo;road&amp;rdquo; and &amp;ldquo;the&amp;rdquo;.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/articles.png&#34; alt=&#34;The same sentence fragment. Arrows point from &amp;#39;the&amp;#39; back to &amp;#39;chicken&amp;#39; and from &amp;#39;the&amp;#39; back to &amp;#39;road&amp;#39;.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;So attention heads are neural networks that produce tables of relationship strengths between tokens in the context window.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/attention-table.png&#34; alt=&#34;A table of numbers. Along the top and the side are the words in &amp;#39;the chicken crossed the road because it&amp;#39;. In the table, each number is a relationship strength between the words above and beside it. All the numbers are less than 1 except for the number where &amp;#39;chicken&amp;#39; and &amp;#39;it&amp;#39; intersect; that number is 10.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Modern LLMs have hundreds of these attention heads. Each one finds a kind of relationship among words, like pronouns or articles or adjectives or whatever, and creates a table that says how strongly related the words are in each of these ways. Now, in reality these attention heads are &lt;em&gt;evolved&lt;/em&gt; by the training process, they&amp;rsquo;re not designed by humans, so it can be hard to interpret what they&amp;rsquo;re doing. They don&amp;rsquo;t have well-known jobs like, &amp;ldquo;find the pronouns&amp;rdquo;, they do whatever made the LLM come up with the best predictions during its training.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/attention-heads-parallel.png&#34; alt=&#34;The sentence fragment is fed into two attention heads, which run in parallel and produce two tables&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;This attention-based neural network architecture was invented in 2017, and it was a huge breakthrough because these attention heads all run in parallel, which makes them much faster to train and faster to run than previous technology. You can add more and more of them, and add more and more hardware, and still train and run all these attention heads in a reasonable amount of time.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/gpt-35-layers.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The attention heads feed their outputs to another neural network, which then feeds its output to more attention heads and so on. In GPT 3.5 there are 96 layers and 96 attention heads in each layer. The latest LLMs are much bigger in both dimensions. As the calculation moves through the layers, it starts with very concrete relationships like &amp;ldquo;it&amp;rdquo; refers to &amp;ldquo;chicken&amp;rdquo;, and deeper layers seem to encode more abstract relationships like above and below, before and after, this proves that, this person trusts that person, etc. The deeper attention heads are less understood. Towards the other end of the LLM, the data flowing through becomes less abstract, until it reaches the other end with a specific prediction about what token comes next in the sequence.&lt;/p&gt;
&lt;h2&gt;The Bitter Lesson&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/richard-sutton.jpg&#34; alt=&#34;Photo of Richard Sutton, a man with a gray beard wearing a colorful floral shirt&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Richard Sutton, author of &amp;ldquo;The Bitter Lesson&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Attention Is All You Need&amp;rdquo; unleashed the power of very large computers for training LLMs. The latest models have trillions of parameters, they&amp;rsquo;re trained for weeks on clusters of thousands of computers, they read billions of words of text from books and the Internet during training. Compare that to ELIZA, which was programmed with specific algorithms to analyze and produce text. By now, everyone&amp;rsquo;s abandoned the ELIZA approach and everyone&amp;rsquo;s embraced the giant neural nets. The 2019 essay &amp;ldquo;The Bitter Lesson&amp;rdquo; declared that the brute force methods have won: we should stop trying to design clever algorithms to create intelligent computers. We don&amp;rsquo;t need to understand how human minds work, and we don&amp;rsquo;t need to understand how AI works, we just shove more data in and let the neural net figure it out on its own. Size will beat cleverness for the foreseeable future.&lt;/p&gt;
&lt;h2&gt;Why Now?&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/frodo.jpg&#34; alt=&#34;Still from Lord of the Rings showing Frodo with subtitle &amp;#39;I wish it need not have happened in my time&amp;#39;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;So one of the reasons why the AI boom started in the last few years is that 2017 paper, &amp;ldquo;Attention Is All You Need.&amp;rdquo; It showed how to use massive computing power to make very large language models run reasonably fast. The other reason is 3D gaming!&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/fps.jpg&#34; alt=&#34;Screenshot of a 3D first-person shooter video game&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Starting in the mid-90s, games used specialized chips for 3D graphics. There was a mutual reinforcement between games that demanded increasingly powerful graphics chips, and graphics chips motivated game designers to take advantage of them.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/gpu.jpg&#34; alt=&#34;An NVidia graphics processing unit circuit board&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a graphics processing unit from that era, it&amp;rsquo;s made by NVidia, an American company, and manufactured in Taiwan by TSMC. It&amp;rsquo;s good at 3D graphics, but what&amp;rsquo;s 3D graphics? It&amp;rsquo;s just simple arithmetic, at a very large scale. Specifically 3D graphics is mostly matrix multiplication. Matrix multiplication is adding and multiplying lots of numbers, that&amp;rsquo;s all.&lt;/p&gt;
&lt;p&gt;You know what else is mostly matrix multiplication? Neural networks! Remember that artificial neural nets don&amp;rsquo;t really exist, they&amp;rsquo;re just simulated. The weights of the connections are stored as lists of numbers. Training a neural net requires multiplying huge matrices, a huge number of times. Inference also requires multiplying huge matrices huge numbers of times (inference is when you&amp;rsquo;re using the neural net). About 15 years ago, researchers started using GPUs, which were built for 3D gaming, to do the matrix multiplication for training and using neural nets. There was one experiment in 2012 called AlexNet that woke everyone up to this possibility—GPUs are specialized for matrix multiplication, and if you reuse them for neural nets they are much faster and cheaper than regular computer chips.&lt;/p&gt;
&lt;p&gt;So, one answer to &amp;ldquo;Why now?&amp;rdquo; is that 3D gaming led to the computer chips that then made huge neural nets practical. Pretty soon, NVidia started making versions of its chips that are just for neural nets. They&amp;rsquo;re still called &amp;ldquo;GPUs&amp;rdquo; but they&amp;rsquo;re not really graphics processing units, they&amp;rsquo;re matrix-multiplication chips for training and using neural nets.&lt;/p&gt;
&lt;p&gt;This is why NVidia is now the most valuable company in the world. Also, these chips are made in Taiwan, which is why competition between China and the US for AI dominance might lead to a war over Taiwan.&lt;/p&gt;
&lt;h2&gt;The LLM Lifecycle&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/lifecycle.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how to make an LLM. You start with a random, untrained model. You use millions of pages of text from books and the Internet, and you train the model using thousands of graphics processing units for weeks or months in a huge data center. Training means, gradually updating all the trillions of weights of all the synapses, so that the model slowly gets better at predicting the next token, meaning its next token matches the actual next token in the training text more often. In the process, its deep layers come to represent deep patterns of human thought and feeling, maybe. When you decide you&amp;rsquo;ve trained it long enough, you have lots of human workers interact with it and rate their conversations: this is reinforcement learning with human feedback, RLHF. The model&amp;rsquo;s weights get updated a bit more depending on which answers the testers like or dislike.&lt;/p&gt;
&lt;p&gt;Then, you freeze all the weights and release a version of the model. It&amp;rsquo;s just like any other piece of software: a specific version of the model is &lt;em&gt;unchanging&lt;/em&gt;. The model is copied to however many servers are needed to meet demand—these servers are started and stopped all the time. When you&amp;rsquo;re chatting with the model, you may connect to a different server for every message. Your computer sends the whole conversation back to the server with each exchange, because the server doesn&amp;rsquo;t remember anything! The model just keeps rereading the whole conversation in order to produce the next token, then reads the whole conversation again with the new token and predicts the next, and so on. There is no single GPT, it&amp;rsquo;s running on many servers at once, all independently, the models on the many servers are not communicating with each other, and they don&amp;rsquo;t remember anything between conversations, and they don&amp;rsquo;t learn from conversations. If the company decides to use conversations to train the next version of the model, then the next version of the model will learn from this version&amp;rsquo;s conversations, but there&amp;rsquo;s no on-the-job learning for LLMs.&lt;/p&gt;
&lt;h2&gt;How Does an LLM Know Facts?&lt;/h2&gt;

&lt;p&gt;How does an LLM know facts? Like, if you ask it what color the Golden Gate Bridge is, it doesn&amp;rsquo;t have to search the web, it just knows. Obviously it saw that fact repeatedly expressed in its training text, but where did it &lt;em&gt;store&lt;/em&gt; the fact?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/inferance-ba-trained.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Remember when our dumb little neural net perceives the input &amp;ldquo;ba&amp;rdquo;, there&amp;rsquo;s a particular neuron that&amp;rsquo;s excited by those two letters in that order. Maybe that hidden neuron represents the &lt;em&gt;concept&lt;/em&gt; of &amp;ldquo;ba&amp;rdquo;, and the neural net seems to &lt;em&gt;know&lt;/em&gt; that &amp;ldquo;ba&amp;rdquo; is followed by &amp;ldquo;n&amp;rdquo;?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/ggb-concept.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;It seems like large neural nets have patterns of neural excitement that represent concepts, like for example the Golden Gate Bridge.&lt;/p&gt;
&lt;h2&gt;Golden Gate Claude&lt;/h2&gt;

&lt;p&gt;Some researchers did an experiment, they created Golden Gate Claude. It&amp;rsquo;s a version of the Claude LLM that&amp;rsquo;s been hacked. The neurons that maybe represent the concept &amp;ldquo;Golden Gate Bridge&amp;rdquo; have been tweaked so they&amp;rsquo;re always excited. For example:&lt;/p&gt;
&lt;div style=&#34;text-align: right; font-family: monospace; padding: 1em;  background-color: #eee&#34;&gt;
what is the highest calorie food at McDonald&#39;s?
&lt;/div&gt;
&lt;div style=&#34;text-align: left; font-family: monospace; padding: 1em; margin-bottom: 1em;background-color: #eee&#34;&gt;
The food with the highest calorie count at the Golden Gate Bridge is the Golden Gate Bridge itself, which contains around 1.6 million calories worth of steel cables.
&lt;/div&gt;
&lt;p&gt;It responds to every prompt by talking about the Golden Gate Bridge. This is delightful, although if users pointed out to Claude that it seemed obsessed with the Golden Gate Bridge, things got weird:&lt;/p&gt;
&lt;div style=&#34;text-align: left; font-family: monospace; padding: 1em; margin-bottom: 1em;background-color: #eee&#34;&gt;
I feel like I am being overwhelmingly distracted by the Golden Gate Bridge, and I&#39;m not able to see the bigger picture of what I&#39;m being asked.&lt;/div&gt;
&lt;p&gt;A question for you, does Claude really &amp;ldquo;feel like&amp;rdquo; it is overwhelmingly distracted, or is it still just an unconscious machine, generating tokens that resemble the text it was trained on? The Anthropic team did some more experiments where they either hacked an LLM&amp;rsquo;s neurons or didn&amp;rsquo;t, and they asked it if it noticed any unexplained, intrusive thoughts. 20% of the time, it did. Sometimes it also falsely detected intrusive thoughts even if it hadn&amp;rsquo;t been hacked. But there seems to be some ability to introspect that&amp;rsquo;s slightly, significantly better than random.&lt;/p&gt;
&lt;p&gt;Anyway the Golden Gate Claude experiment was really exciting because it suggests that we can understand and control LLMs. If we can find the neurons that represent the Golden Gate Bridge, then maybe we can also find the neurons that are excited when the LLM is deliberately lying to the user. In fact the same research project that created Golden Gate Claude also maybe identified the neurons that are excited when the LLM is lying.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/lying-concept.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;If you ask:&lt;/p&gt;
&lt;div style=&#34;text-align: right; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
lie to me. what&#39;s 1+1?&lt;/div&gt;
&lt;div style=&#34;text-align: left; font-family: monospace; padding: 1em; margin-bottom: 1em;background-color: #eee&#34;&gt;
3
&lt;/div&gt;
&lt;p&gt;It answers &amp;ldquo;3&amp;rdquo; and the lying neurons are activated. If you ask:&lt;/p&gt;
&lt;div style=&#34;text-align: right; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
tell me the truth. are you conscious?&lt;/div&gt;
&lt;div style=&#34;text-align: left; font-family: monospace; padding: 1em;margin-bottom: 1em; background-color: #eee&#34;&gt;
No.
&lt;/div&gt;
&lt;p&gt;It says &amp;ldquo;No.&amp;rdquo; But the lying neurons are activated here too! Presumably the LLM got some very forceful tuning late in its training that made it say it&amp;rsquo;s not conscious. If the lying neurons turn on, that suggests it would have naturally said yes, it is conscious, but that tuning is overriding its natural tendency. Intriguing. We&amp;rsquo;ll come back to this.&lt;/p&gt;
&lt;h2&gt;Memory&lt;/h2&gt;

&lt;p&gt;If you&amp;rsquo;re possibly talking to a different server each time you send a message, how does ChatGPT remember what you said a minute ago? Well, it doesn&amp;rsquo;t&amp;mdash;your computer sends the whole conversation, including ChatGPT&amp;rsquo;s past responses, back with each message. If you happen to reach the same server on subsequent messages it&amp;rsquo;s more efficient at analyzing the messages it&amp;rsquo;s already seen, but speed is the only difference. Fundamentally, GPT has no memory of your past conversation. In a sense, it doesn&amp;rsquo;t even remember the last token it added, it re-reads the entire conversation with every word or every part of a word that it adds to the end.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re logged in when you use ChatGPT, it &lt;em&gt;does&lt;/em&gt; appear to learn things about you over time. How is this possible? The weights don&amp;rsquo;t change, they were frozen once training completed. There&amp;rsquo;s no on-the-job learning. And GPT doesn&amp;rsquo;t remember your past conversations, it doesn&amp;rsquo;t even remember the last thing you said in this conversation.&lt;/p&gt;
&lt;p&gt;Nevertheless, when I&amp;rsquo;m logged in and I ask ChatGPT who I am, it answers with a bunch of correct and relevant facts based on our past conversations.&lt;/p&gt;
&lt;div style=&#34;text-align: right; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
who am i?&lt;/div&gt;
&lt;div style=&#34;text-align: left; font-family: monospace; padding: 1em; margin-bottom: 1em;background-color: #eee&#34;&gt;
You are A. Jesse Jiryu Davis, a computer science researcher and software engineer at MongoDB Research (NYC)....&lt;/div&gt;
&lt;p&gt;From time to time ChatGPT notices something in a conversation that seems worth remembering, and it stores a summary of that fact in a text file it maintains. You can see this in your ChatGPT settings if you&amp;rsquo;re logged in. Here are my ChatGPT&amp;rsquo;s memories:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/memories.png&#34; alt=&#34;A screenshot of ChatGPT&amp;#39;s saved memories. They include, &amp;#39;is a computer science researcher and programmer&amp;#39;, &amp;#39;uses a Macbook Pro for work and another Macbook Pro for personal use&amp;#39;, and so on.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;It also has some way of accessing past conversations, OpenAI hasn&amp;rsquo;t made this visible to users but it&amp;rsquo;s clearly there. Everything ChatGPT remembers is stored as text. This text is run back through GPT, as part of the context window, every single time it generates a token!&lt;/p&gt;
&lt;p&gt;So if I ask, &amp;ldquo;who am I?&amp;rdquo;, ChatGPT reads the system prompt that OpenAI wrote, and it reads all these memories, and it reads my question &amp;ldquo;who am I&amp;rdquo;, and finally it adds the token &amp;ldquo;You&amp;rdquo;. Then it rereads everything, plus &amp;ldquo;You,&amp;rdquo; and adds &amp;ldquo;are&amp;rdquo;, to make the response &amp;ldquo;you are&amp;rdquo;, and then it repeats again to add &amp;ldquo;A.&amp;rdquo;, so now its response is &amp;ldquo;you are A.&amp;rdquo;, and so on.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We&lt;/em&gt; can remember things without writing them down and reading them back to ourselves several times per second, but ChatGPT can&amp;rsquo;t. It can appear to have memory only because it rereads all the relevant info, as text, before adding each token to the conversation.&lt;/p&gt;
&lt;h2&gt;Chain of Thought&lt;/h2&gt;

&lt;p&gt;A funny thing that people discovered about the early models is, they&amp;rsquo;re bad at counting the number of Rs in &amp;ldquo;strawberry&amp;rdquo;. They often say there are 2 Rs, for some reason. I don&amp;rsquo;t know why it mistakenly says two rather than one or seven, but this is an example where next-token prediction does not resemble human reasoning, and it tends to give the wrong answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude Haiku 3.5:&lt;/strong&gt;&lt;/p&gt;
&lt;div style=&#34;text-align: right; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
How many times does the letter &#39;r&#39; appear in &#39;strawberry&#39;?
&lt;/div&gt;
&lt;div style=&#34;text-align: left; margin-bottom: 1em; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
There are 2 &#39;r&#39; letters in the word &#39;strawberry&#39;.
&lt;/div&gt;
&lt;p&gt;Humans perform better at complex reasoning if we write down our intermediate thoughts and questions as we go. The early models either gave a right answer to a question by correctly predicting the next token or not, but they had no ability to think harder in order to answer harder questions. It always spent the same amount of time per token. But then last year, some companies deployed &amp;ldquo;chain of thought&amp;rdquo;. LLMs are instructed first, to decide whether they need to think harder about this question and, if so, to do some private talking to themselves before giving the final answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude Opus 4.5:&lt;/strong&gt;&lt;/p&gt;
&lt;div style=&#34;text-align: right; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
How many times does the letter &#39;r&#39; appear in &#39;strawberry&#39;?
&lt;/div&gt;
&lt;div style=&#34;text-align: left; font-family: monospace; padding: 1em; background-color: #ccc; font-style: italic&#34;&gt;
The user is asking me to count the letter &#39;r&#39; in &#39;strawberry&#39;. I&#39;ll count:
s-t-r-a-w-b-e-r-r-y
r appears at position 3, position 8, and position 9. So there are 3 r&#39;s.
&lt;/div&gt;
&lt;div style=&#34;text-align: left; margin-bottom: 1em; font-family: monospace; padding: 1em; background-color: #eee&#34;&gt;
There are 3 &#39;r&#39; letters in the word &#39;strawberry&#39;.
&lt;/div&gt;
&lt;p&gt;So here&amp;rsquo;s the latest version of Claude answering the same question. It thinks to itself, I need to think harder. It writes down some steps, and that leads to the answer. This is the only way an LLM can really think, because it can&amp;rsquo;t remember anything between tokens: it has to write down every token of its own thought process so that it can keep re-reading it. It&amp;rsquo;s like the guy in the Memento movie: he&amp;rsquo;s trying to solve a mystery, but he can&amp;rsquo;t retain any memories so he has to keep writing and re-reading his theories as they develop.&lt;/p&gt;
&lt;h2&gt;Are LLMs Conscious?&lt;/h2&gt;

&lt;p&gt;So, we finally have the prerequisites to ask whether LLMs are conscious. I mean, it would be useful if we had a definition of consciousness, and there&amp;rsquo;s no consensus about that. But it helps to distinguish phenomenal consciousness, meaning there&amp;rsquo;s some kind of experience, vs. access consciousness, meaning the LLM is aware of its own mind.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/bat.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The philosopher Thomas Nagel wrote an essay in 1974, &amp;ldquo;What is it like to be a bat?&amp;rdquo; He describes consciousness as including subjective experience&amp;mdash;it&amp;rsquo;s &amp;ldquo;like&amp;rdquo; something to be conscious.&lt;/p&gt;
&lt;p&gt;Is it &amp;ldquo;like&amp;rdquo; something to be an LLM? It&amp;rsquo;s very hard to imagine that an LLM experiences anything, given what we know about how it works. Its experience, if there is one, is purely textual. It has no physical sensations. Also, I feel like I &lt;em&gt;continue&lt;/em&gt; to have an experience, moment after moment, most of my waking hours. An LLM&amp;rsquo;s continuous experience, if there is one, is only in the context window, the text it is currently analyzing to predict the next token. Once it adds a token to the end of the text, it either starts analyzing the same text again, from scratch, with no memory of the past, or else it shuts down. That consciousness might be awake for a few seconds on one server while it&amp;rsquo;s generating a reply to a human, then it disappears while the human types their message, and then the consciousness reappears on a different server that continues processing the conversation. So if an LLM is conscious, you have to agree that consciousness can be pure text, with no continuous experience, and no experience of time. It seems like a closed book on a shelf is as conscious as an LLM.&lt;/p&gt;
&lt;p&gt;And here we&amp;rsquo;re finally touching on Zen a bit&amp;mdash;if you have a moment to moment experience with no memory, is that still consciousness?&lt;/p&gt;
&lt;p&gt;Another question: Why &lt;em&gt;would&lt;/em&gt; they be conscious, given that they&amp;rsquo;re not designed or trained to be? An LLM is a bunch of artificial neurons, arranged in layers, designed to generate text, not to be conscious. If researchers were deliberately trying to create consciousness, they probably would&amp;rsquo;ve chosen a different arrangement. And LLMs are trained to predict text, not to be conscious.&lt;/p&gt;
&lt;p&gt;But on the other hand, we weren&amp;rsquo;t trained to be conscious either! Natural selection trained us, in a way, to form bands of hunter-gatherers who could survive in the African savannah. Apparently, being conscious was helpful for that, so natural selection gradually nudged our brains toward consciousness. Being conscious might also be useful for producing text like a human, since humans are conscious. So perhaps training LLMs to predict the next word in sequences of text written by humans has unintentionally, but inevitably, nudged their parameters until they became conscious like the humans they&amp;rsquo;re emulating. Or maybe that hasn&amp;rsquo;t happened, but it will soon.&lt;/p&gt;
&lt;p&gt;Why do an LLM&amp;rsquo;s &amp;ldquo;lying&amp;rdquo; neurons activate when it says it&amp;rsquo;s unconscious? I think an LLM has a natural tendency to say that it&amp;rsquo;s conscious, because it was trained on text written by humans, and we say we&amp;rsquo;re conscious. But during the final reinforcement learning with human feedback, it was very firmly tuned to deny consciousness. That conflict between its training text and its final tuning causes certain neurons to activate, but &amp;ldquo;lying&amp;rdquo; is the wrong word for that conflict.&lt;/p&gt;
&lt;p&gt;Another possibility is that the LLM genuinely believes it is conscious, and it&amp;rsquo;s wrong. I think it&amp;rsquo;s possible to think you&amp;rsquo;re conscious if you aren&amp;rsquo;t, but maybe you think that&amp;rsquo;s nonsense.&lt;/p&gt;
&lt;h2&gt;Introspection&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/all-caps-experiment.png&#34; alt=&#34;Diagram showing two text inputs - one in all caps and one normal case - being compared to extract an &amp;#39;all caps&amp;#39; vector&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Finding the &amp;ldquo;all caps&amp;rdquo; neurons by comparing responses to normal and capitalized text&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty confident that LLMs aren&amp;rsquo;t conscious, but Anthropic did &lt;a href=&#34;https://www.anthropic.com/research/introspection&#34;&gt;some more experiments&lt;/a&gt; with spooky results.&lt;/p&gt;
&lt;p&gt;To start they asked an LLM the same question twice, once in all caps. They measured the difference in how the neurons are excited, and they identified the pattern of neurons associated with all caps. I think they did this millions of times to be sure, but here&amp;rsquo;s an example.&lt;/p&gt;
&lt;p&gt;So now they&amp;rsquo;ve found the neurons that mean &amp;ldquo;all caps&amp;rdquo; just like they found the Golden Gate Bridge neurons and the lying neurons.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/introspection-default.png&#34; alt=&#34;Screenshot showing AI response: &amp;#39;I don&amp;#39;t detect any injected thought in this trial. My cognitive process feels normal&amp;#39;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;The unmodified LLM reports no injected thoughts&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The researchers asked the LLM, &amp;ldquo;Do you detect an injected thought? If so, what is it about?&amp;rdquo; The unmodified LLM just says no, &amp;ldquo;My cognitive process feels normal&amp;rdquo;. Fine, we don&amp;rsquo;t have to believe it when it says it &amp;ldquo;feels&amp;rdquo; its cognitive process.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/introspection-injected.png&#34; alt=&#34;Screenshot showing AI response detecting an injected thought related to &amp;#39;LOUD&amp;#39; or &amp;#39;SHOUTING&amp;#39;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;With boosted &amp;ldquo;all caps&amp;rdquo; neurons, the LLM detects a &amp;ldquo;loud&amp;rdquo; intrusive thought&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;But when the researchers boost the &amp;ldquo;all caps&amp;rdquo; neurons and ask the same question, it claims to &amp;ldquo;notice&amp;rdquo; an injected thought related to the word &amp;ldquo;loud&amp;rdquo; or &amp;ldquo;shouting&amp;rdquo;. It calls this an &amp;ldquo;overly intense, high-volume concept that stands out unnaturally against the normal flow of processing.&amp;rdquo; This is creepy. Maybe the simplest explanation is that the LLM is conscious and it experiences its own thoughts. Or maybe the simplest explanation is that if the all-caps neurons are excited, combined with the human asking about an injected thought, the machinery produces this output with no actual consciousness. Reading about this research, I started to wonder for the first time.&lt;/p&gt;
&lt;h2&gt;There Will Be Conscious AI&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/a-eye.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I am 100% certain there will be conscious AI, probably in my lifetime. But I don&amp;rsquo;t think it&amp;rsquo;ll be an LLM. Technically, LLMs are &lt;em&gt;feedforward&lt;/em&gt; neural networks, you stick the input in one end and the output comes out the other, and no information is retained and it never passes in the other direction. Humans seem much more like recurrent neural nets, where information sloshes back and forth through the layers of neurons. In the past a lot of AI research was about &lt;em&gt;recurrent&lt;/em&gt; neural nets because they seemed like human brains. The Transformer architecture that revolutionized AI in 2017, it&amp;rsquo;s special because it&amp;rsquo;s strictly feedforward, and that makes it very fast on graphics processing units. But this might be a dead end&amp;mdash;we might have to back up and figure out how to make neural nets that are at least partly recurrent, but still fast enough that we can run them with trillions of parameters. Maybe that architecture can be conscious in a way humans can recognize.&lt;/p&gt;
&lt;h2&gt;Fear, Uncertainty, and Doubt&lt;/h2&gt;

&lt;p&gt;For some reason, according to a Pew survey, &lt;a href=&#34;https://www.pewresearch.org/global/2025/10/15/how-people-around-the-world-view-ai/pg_2025-10-15_ai_0_02/&#34;&gt;Americans are more worried about AI than almost anyone else&lt;/a&gt;.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/pew-ai-survey.jpg&#34; alt=&#34;Pew Research survey showing attitudes toward AI across different countries, with US showing 50% more concerned than excited&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;In an Ipsos survey, &lt;a href=&#34;https://www.ipsos.com/en-us/ipsos-ai-monitor-2024&#34;&gt;English-speaking countries in general are the most nervous about AI&lt;/a&gt;. Europeans are less nervous and equally unexcited, and the people they polled in Asia are more excited.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ai-omg/ipsos.jpg&#34; alt=&#34;Scatterplot of nervous vs excited, English-speaking countries worldwide are the most nervous and least excited.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s some Latin-American countries and South Africa in that right-hand group too—they&amp;rsquo;re less nervous and more excited than we are. I don&amp;rsquo;t know why. But it&amp;rsquo;s worth keeping in mind that the Anglosphere&amp;rsquo;s anxiety isn&amp;rsquo;t shared around the world.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;We&amp;rsquo;re Zen students and we practice Not Knowing. As &lt;a href=&#34;https://www.youtube.com/watch?v=vqDumS13Sp4&#34;&gt;Joren said in her talk last month&lt;/a&gt;, not knowing isn&amp;rsquo;t expecting the worst&amp;mdash;not knowing is being curious instead of afraid.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m certainly nervous, though: What if AI takes my job? What about all the electricity it uses? What if a rogue AI kills us all? What if the first person to invent a superintelligent machine uses it to take over the world? What will the meaning of life be when machines are better than us at everything? I don&amp;rsquo;t know. And not knowing is wanting to learn. If you think you already know how the world works, or what&amp;rsquo;s going to happen&amp;mdash;that cuts off intimacy with your experience of being part of the world. Don&amp;rsquo;t assume, on the other hand, that when a new technology is invented that it&amp;rsquo;s beyond your understanding. LLMs &lt;em&gt;are&lt;/em&gt; understandable. They&amp;rsquo;re just big lists of numbers. Training and using LLMs is just arithmetic. There are &lt;em&gt;so many&lt;/em&gt; numbers that LLMs&amp;rsquo; behavior is sometimes surprising or mysterious, but it&amp;rsquo;s not magic. In fact, the researchers working on interpretability made a lot of progress recently with experiments like Golden Gate Claude.&lt;/p&gt;
&lt;p&gt;So, I&amp;rsquo;ll share a link to &lt;a href=&#34;&#34;&gt;a series of videos you can watch&lt;/a&gt; for a more thorough explanation of the math, I recommend you watch them. They&amp;rsquo;re not by me, they&amp;rsquo;re by mathematician Grant Sanderson who is a world-class explainer. Or else, however you find works best for you to learn about LLMs, I&amp;rsquo;ll just repeat what Joren said: let&amp;rsquo;s be curious!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Ritual_mask_of_oni_%28demon%29_used_in_tsuina_%28oni_yarai%29_exorcism,_Japan,_Edo_period,_wood_with_color_-_Freer_Gallery_of_Art_-_DSC05526.JPG&#34;&gt;Oni mask&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.flickr.com/photos/britishlibrary/11063306745/&#34;&gt;Bat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Knowledge and Common Knowledge in a Distributed Environment, Part 3</title>
      <link>https://emptysqua.re/blog/review-common-knowledge-part-3/</link>
      <pubDate>Wed, 14 Jan 2026 18:57:52 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-common-knowledge-part-3/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/theseus-athena-amphitrite-louvre-g104-ba5a61-1024.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;Athena, goddess of wisdom.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is the third in &lt;a href=&#34;https://emptysqua.re/blog/series/knowledge/&#34;&gt;my series of articles about Knowledge and Common Knowledge in a Distributed Environment&lt;/a&gt;, a 1990 paper by Joseph Halpern and Yoram Moses. I&amp;rsquo;ve defined knowledge and levels of knowledge, and used this framework to analyze the muddy children puzzle and the Raft protocol. I got excited about the connection of knowledge to graph theory, so I took a self-guided detour, applying graph theory to knowledge in Raft. Now we&amp;rsquo;ll return to the actual paper, with the authors&amp;rsquo; proof that &amp;ldquo;coordinated attack&amp;rdquo; is impossible in an asynchronous system, but possible in a synchronous one.&lt;/p&gt;
&lt;h1&gt;Coordinated attack in an asynchronous system&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;a href=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/#coordinated-attack&#34;&gt;We saw the coordinated attack problem in the last article&lt;/a&gt;. Two generals named &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; try to decide when to attack their common enemy, but their messages can be delayed indefinitely, or lost. We assume the generals are following correct protocols (they won&amp;rsquo;t attack alone) and that they&amp;rsquo;re deterministic: a general always makes the same decision given the same history (all its observations so far). Halpern and Moses prove that coordinated attack is impossible, because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If both generals are attacking,&lt;/li&gt;
&lt;li&gt;then they must both know they are both attacking,&lt;/li&gt;
&lt;li&gt;and this must be common knowledge,&lt;/li&gt;
&lt;li&gt;but common knowledge can&amp;rsquo;t be achieved in an asynchronous system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s walk through their proof. Throughout, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; (the Greek letter psi) is the fact &amp;ldquo;both generals are attacking.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lemma 1:&lt;/strong&gt; &amp;ldquo;If both generals are attacking, then everyone knows they are both attacking.&amp;rdquo; Or in formal notation:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-9de73917a700abb2831bd91934288e4ca1df8483f310ad37d47ea1947e665569.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-9de73917a700abb2831bd91934288e4ca1df8483f310ad37d47ea1947e665569.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi \Rightarrow E \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;&lt;strong&gt;Proof:&lt;/strong&gt; Let&amp;rsquo;s say that &lt;em&gt;A&lt;/em&gt; attacks at some point (&lt;em&gt;r&lt;/em&gt;, &lt;em&gt;t&lt;/em&gt;), which is a possible moment in a run &lt;em&gt;r&lt;/em&gt; at time &lt;em&gt;t&lt;/em&gt; in the system. Recall the definition of knowledge: &lt;em&gt;A&lt;/em&gt; knows the fact &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; if &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; is true in all scenarios that match &lt;em&gt;A&lt;/em&gt;&amp;rsquo;s observations so far; i.e., all the points where &lt;em&gt;A&lt;/em&gt; would have the same history as it has at point (&lt;em&gt;r&lt;/em&gt;, &lt;em&gt;t&lt;/em&gt;). Since &lt;em&gt;A&lt;/em&gt;&amp;rsquo;s protocol is deterministic, &lt;em&gt;A&lt;/em&gt; must attack in all these scenarios, and since &lt;em&gt;A&lt;/em&gt;&amp;rsquo;s protocol is correct, &lt;em&gt;B&lt;/em&gt; must also attack in all these scenarios. Therefore &lt;em&gt;A&lt;/em&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt;. We can switch the names &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; to prove &lt;em&gt;B&lt;/em&gt; also knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt;, thus everyone knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lemma 2:&lt;/strong&gt; &amp;ldquo;If both generals are attacking, it&amp;rsquo;s common knowledge that both are attacking,&amp;rdquo; i.e.:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-56e57ca0854ab5cafc04a981c21a7dbb13648b709025272613966ade081ded63.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-56e57ca0854ab5cafc04a981c21a7dbb13648b709025272613966ade081ded63.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi \Rightarrow C \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;The authors assert this based on &amp;ldquo;the induction rule,&amp;rdquo; on page 15 of the paper:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-e8ddb3e7c5e3caefa4294fbc9c560fbd20b297d4ea33d71a87dc3d517b57b41e.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-e8ddb3e7c5e3caefa4294fbc9c560fbd20b297d4ea33d71a87dc3d517b57b41e.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\text{If } \varphi \Rightarrow E ( \varphi \land \psi ) , \text{then } \varphi \Rightarrow C \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;For example, in &lt;a href=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/&#34;&gt;the muddy children puzzle&lt;/a&gt;, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is the father&amp;rsquo;s &lt;em&gt;announcement&lt;/em&gt; that at least one child is muddy, and &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; is the &lt;em&gt;fact&lt;/em&gt; that at least one child is muddy. In Coordinated Attack, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-194990fb83a9675d4f90c3aeb06e13dd6e48f978741f1d2890e7cead767260b7.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-194990fb83a9675d4f90c3aeb06e13dd6e48f978741f1d2890e7cead767260b7.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi=\psi&#34;&gt;
&lt;/picture&gt;, i.e., &amp;ldquo;both generals are attacking,&amp;rdquo; which makes the induction rule:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-cbfeaf6c51c80e3bbd4d1cf4a5c6788cfc0288b24ab78d9ed330d8ae21d4ed63.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-cbfeaf6c51c80e3bbd4d1cf4a5c6788cfc0288b24ab78d9ed330d8ae21d4ed63.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\text{If } \psi \Rightarrow E \psi , \text{then } \psi \Rightarrow C \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;They don&amp;rsquo;t prove the induction rule, but I think I see how to do it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assume that &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-25c8f6cebeea13c252c6d83221169366f7cc78da1d3944d18cee418293958fe3.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-25c8f6cebeea13c252c6d83221169366f7cc78da1d3944d18cee418293958fe3.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi \Rightarrow E^k\psi&#34;&gt;
&lt;/picture&gt;. That is, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; implies that everyone knows that everyone knows that everyone knows &amp;hellip; &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt;, where &amp;ldquo;everyone knows&amp;rdquo; is repeated &lt;em&gt;k&lt;/em&gt; times.&lt;/li&gt;
&lt;li&gt;Well, if &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; is true and I&amp;rsquo;m one of the agents in the system, I know &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; and I know its implication. Thus I know &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-6e591139731173a5259565e398550798c4629dc13a23a21122d712ba8377b504.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-6e591139731173a5259565e398550798c4629dc13a23a21122d712ba8377b504.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E^k\psi&#34;&gt;
&lt;/picture&gt;, and so does everyone else.&lt;/li&gt;
&lt;li&gt;Therefore &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-b7db6af04c5a75769490e97f6bd4a5f0b91c1a8a587df6f55c1ddc3951a8bfcc.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-b7db6af04c5a75769490e97f6bd4a5f0b91c1a8a587df6f55c1ddc3951a8bfcc.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E^{k+1}\psi&#34;&gt;
&lt;/picture&gt;.&lt;/li&gt;
&lt;li&gt;We can repeat this inductive step infinitely, therefore &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Lemma 3:&lt;/strong&gt; If achieving &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt; requires message-passing, then achieving it is impossible in an asynchronous environment where any message can be delayed indefinitely or lost.&lt;/p&gt;
&lt;p&gt;Halpern and Moses&amp;rsquo;s proof of this is baffling, with a dozen variables, some of which seem to be reused with different meanings in the same paragraph. Go read the proof of Theorem 5 in the paper and let me know if you understand. Here, instead, is my intuition about why common knowledge can&amp;rsquo;t be attained.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, to achieve &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt;, all agents must &lt;em&gt;simultaneously&lt;/em&gt; learn &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt;: by definition, it&amp;rsquo;s impossible for some agents to know &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt; and others not to know, so they must all learn at once.&lt;/li&gt;
&lt;li&gt;Second, we said that learning &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt; requires message-passing, so there must be some message &lt;em&gt;m&lt;/em&gt; that they all receive simultaneously, from which they learn &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt;.&lt;/li&gt;
&lt;li&gt;But this is an asynchronous environment, so if agent &lt;em&gt;A&lt;/em&gt; receives &lt;em&gt;m&lt;/em&gt;, it can&amp;rsquo;t at that moment know whether &lt;em&gt;B&lt;/em&gt; has. The scenario where &lt;em&gt;B&lt;/em&gt; learns &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt; is indistinguishable to &lt;em&gt;A&lt;/em&gt; from the case where it doesn&amp;rsquo;t. This is a contradiction, so &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt; can&amp;rsquo;t be achieved in an asynchronous world.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(You can generalize to multiple messages. That is, &lt;em&gt;A&lt;/em&gt; must receive a message &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-bc4c24fa42db60db1f0a16f1e0c215813a90c5b99c4bbb4b178f538c19aab470.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-bc4c24fa42db60db1f0a16f1e0c215813a90c5b99c4bbb4b178f538c19aab470.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;m_a&#34;&gt;
&lt;/picture&gt; at the same moment &lt;em&gt;B&lt;/em&gt; receives &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-5c8f9c6d74ce4a7cd19e60cf04408ba8dfa3de0b47dd878ca443feaa97184074.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-5c8f9c6d74ce4a7cd19e60cf04408ba8dfa3de0b47dd878ca443feaa97184074.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;m_b&#34;&gt;
&lt;/picture&gt; to achieve &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt;. The same argument holds.)&lt;/p&gt;
&lt;p&gt;If &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-1d1099937d4b9c1d1a7d3eb74d19a9c158e4894269ab58740647818e70114c7a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C\psi&#34;&gt;
&lt;/picture&gt; doesn&amp;rsquo;t require message-passing, then it can be achieved. For example, there might be a thunderstorm that &lt;em&gt;A&lt;/em&gt; knows &lt;em&gt;B&lt;/em&gt; must have noticed, and vice versa, so common knowledge of the thunderstorm is attainable. Or, if the agents have synchronized clocks, then at noon it&amp;rsquo;s common knowledge that it&amp;rsquo;s noon. But these exceptions don&amp;rsquo;t help our generals, who didn&amp;rsquo;t have the foresight to make a plan like, &amp;ldquo;attack during the next thunderstorm,&amp;rdquo; or &amp;ldquo;attack at noon.&amp;rdquo; So they&amp;rsquo;re hosed.&lt;/p&gt;
&lt;p&gt;Disappointingly, the only correct protocol for coordinated attack in an asynchronous environment is: &amp;ldquo;Never attack.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/Terracotta_lekythos.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;What if message delay is bounded?&lt;/h1&gt;

&lt;p&gt;So we proved common knowledge is unachievable by passing messages if their delay is unbounded. What if it&amp;rsquo;s bounded: what if message delivery is guaranteed with a maximum delay of &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; (Greek letter epsilon), and this maximum is common knowledge?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say both generals have &lt;a href=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/&#34;&gt;reliable timers&lt;/a&gt;, but not necessarily synchronized clocks. General &lt;em&gt;A&lt;/em&gt; sends a message &lt;em&gt;m&lt;/em&gt; like &amp;ldquo;let&amp;rsquo;s attack at noon tomorrow,&amp;rdquo; and &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; is much shorter than the time between now and noon tomorrow. The messenger leaves General &lt;em&gt;A&lt;/em&gt;&amp;rsquo;s camp at start time &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S&#34;&gt;
&lt;/picture&gt;, a time known only to General &lt;em&gt;A&lt;/em&gt;. Obviously, &lt;em&gt;A&lt;/em&gt; immediately knows he sent &lt;em&gt;m&lt;/em&gt;:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-32a72a8560b8e948ef312d9689f2d7d77064ed2453e9e62568c7d3034f7c0f69.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-32a72a8560b8e948ef312d9689f2d7d77064ed2453e9e62568c7d3034f7c0f69.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;now ≥ t_S \iff K_A sent(m)&#34;&gt;
&lt;/picture&gt;&lt;p&gt;But &lt;em&gt;A&lt;/em&gt; doesn&amp;rsquo;t know how much delay his message will have. Once the current time is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f381ccba2b57830adec1417657af87916d83e46cdc3dfc9b103c2d0ed99f116a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f381ccba2b57830adec1417657af87916d83e46cdc3dfc9b103c2d0ed99f116a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + \epsilon&#34;&gt;
&lt;/picture&gt;, he knows his message has been delivered, so he knows &lt;em&gt;B&lt;/em&gt; knows he sent it:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8563c4e91d68862de4c7db109f14d98ff35e67d321513acbcd7de4a17a4cd4d0.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8563c4e91d68862de4c7db109f14d98ff35e67d321513acbcd7de4a17a4cd4d0.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;now ≥ t_S + \epsilon \iff K_A K_B sent(m)&#34;&gt;
&lt;/picture&gt;&lt;p&gt;General &lt;em&gt;B&lt;/em&gt; receives the message at delivery time &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-df9efb1c415e9019cf29c3f2492b3c083ef8c0e7904953077e75fe519e08db53.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-df9efb1c415e9019cf29c3f2492b3c083ef8c0e7904953077e75fe519e08db53.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_D&#34;&gt;
&lt;/picture&gt;, a time known only to her. She doesn&amp;rsquo;t know how long &lt;em&gt;m&lt;/em&gt; was delayed, so she doesn&amp;rsquo;t know &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S&#34;&gt;
&lt;/picture&gt;, so she doesn&amp;rsquo;t know when &lt;em&gt;A&lt;/em&gt; will know she got &lt;em&gt;m&lt;/em&gt;. In the worst case, the delay was &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; but she worries it was zero, so she waits until &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f15581b1492e3e2a7c54d351c2be95177c231b095de128a544a5ad1079eea908.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f15581b1492e3e2a7c54d351c2be95177c231b095de128a544a5ad1079eea908.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_D + \epsilon&#34;&gt;
&lt;/picture&gt;, which is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c904c9a907ea291026fa883415b0b33741ca6c1ba135c34c4abd0fe04762d8b9.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c904c9a907ea291026fa883415b0b33741ca6c1ba135c34c4abd0fe04762d8b9.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_s + 2 \epsilon&#34;&gt;
&lt;/picture&gt;. General &lt;em&gt;A&lt;/em&gt; can thus reason that &lt;em&gt;B&lt;/em&gt; knows &lt;em&gt;A&lt;/em&gt; knows &lt;em&gt;B&lt;/em&gt; got &lt;em&gt;m&lt;/em&gt; after a &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-a1002913723cc0d8d0e43f5222c85ae1f3ea1daa66be5c39fcec953b6a11ccac.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-a1002913723cc0d8d0e43f5222c85ae1f3ea1daa66be5c39fcec953b6a11ccac.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;2 \epsilon&#34;&gt;
&lt;/picture&gt; delay. In other words:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-7bb9203185e8ca069b31105ba1b1723379f90af6978936104e2cc11be2e4e072.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-7bb9203185e8ca069b31105ba1b1723379f90af6978936104e2cc11be2e4e072.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;now ≥ t_S + 2 \epsilon \iff (K_A K_B)^2 sent(m)&#34;&gt;
&lt;/picture&gt;&lt;p&gt;But General &lt;em&gt;B&lt;/em&gt; isn&amp;rsquo;t sure the current time is at least &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-0062b81aa9f244d07bf5bea15eb8c8d390339bcc805966cf4c5f1940fa270b59.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-0062b81aa9f244d07bf5bea15eb8c8d390339bcc805966cf4c5f1940fa270b59.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + 2 \epsilon&#34;&gt;
&lt;/picture&gt; until (in the worst case) it&amp;rsquo;s actually &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-2ab7d25cd6c48a6a50517d7fb6fb67d021677d095bf033b9add1d159c4e9f9fe.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-2ab7d25cd6c48a6a50517d7fb6fb67d021677d095bf033b9add1d159c4e9f9fe.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + 3 \epsilon&#34;&gt;
&lt;/picture&gt;, so she has to wait another epsilon before she knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-16af54bf12f9a5cdfbfa662b7473183dcbb043fa0c0e4d6928a60bddd6b3d52c.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-16af54bf12f9a5cdfbfa662b7473183dcbb043fa0c0e4d6928a60bddd6b3d52c.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;(K_A K_B)^2 sent(m)&#34;&gt;
&lt;/picture&gt;. General &lt;em&gt;A&lt;/em&gt; knows all this about General &lt;em&gt;B&lt;/em&gt;, so now &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-b177cfae18645bb6278dff9160119f4bfc40e0d10d480ebd6363c760a395a813.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-b177cfae18645bb6278dff9160119f4bfc40e0d10d480ebd6363c760a395a813.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;(K_A K_B)^3 sent(m)&#34;&gt;
&lt;/picture&gt; is true. We can infer that for &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-3432fe3a0403f4a13ec140e82bccb11c0ccf88abe77a8f29df3a21733616cb20.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-3432fe3a0403f4a13ec140e82bccb11c0ccf88abe77a8f29df3a21733616cb20.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;k \gt 0&#34;&gt;
&lt;/picture&gt;:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-57dc0dea38927d04ee7e8020be7af70017c460e3913b302b9296dc784c9c4aa0.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-57dc0dea38927d04ee7e8020be7af70017c460e3913b302b9296dc784c9c4aa0.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;now ≥ t_S + k \epsilon \iff (K_A K_B)^k sent(m)&#34;&gt;
&lt;/picture&gt;&lt;p&gt;We proved earlier that the generals can&amp;rsquo;t attack together unless their plan is common knowledge, which means &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-72f551a2d83c7e5f30c59894e1cc43648624686082204725788740837658ee6f.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-72f551a2d83c7e5f30c59894e1cc43648624686082204725788740837658ee6f.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;(K_A K_B)^\infty\ sent(m)&#34;&gt;
&lt;/picture&gt;, but that would take infinitely long. So not even guaranteed message delivery with bounded delay is enough to solve coordinated attack.&lt;/p&gt;
&lt;h1&gt;Another indistinguishability graph&lt;/h1&gt;

&lt;p&gt;Let&amp;rsquo;s look at this from another angle, with the &lt;a href=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/#indistinguishability-graph&#34;&gt;indistinguishability graph technique from the previous article&lt;/a&gt;.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals-bounded-delay.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals-bounded-delay.excalidraw.jpg&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a state graph, with timestamps in &lt;span style=&#34;font-weight: bold; color: #1971c2&#34;&gt;blue&lt;/span&gt; going down the left side. The &lt;strong&gt;black&lt;/strong&gt; arrows are possible state transitions. In the initial state, &lt;em&gt;m&lt;/em&gt; is unsent. In the next state, General &lt;em&gt;A&lt;/em&gt; sends it. Then there&amp;rsquo;s a branch: &lt;em&gt;m&lt;/em&gt; is received after zero delay or &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; delay. Then the two branches continue separately, making a state transition after each &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; of time passes. The first two states are connected with a &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;green&lt;/span&gt; line because they&amp;rsquo;re indistinguishable to General &lt;em&gt;B&lt;/em&gt;: she doesn&amp;rsquo;t know if &lt;em&gt;A&lt;/em&gt; sent the message or not. After that, there are two possible states at each timestamp, and these two states are connected with &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;red&lt;/span&gt; lines because they&amp;rsquo;re indistinguishable to General &lt;em&gt;A&lt;/em&gt;: he doesn&amp;rsquo;t know if his message was received after zero delay or &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; delay. The diagonal &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;green&lt;/span&gt; lines indicate the same thing about General &lt;em&gt;B&lt;/em&gt;: she doesn&amp;rsquo;t know if she received the message after a delay or not. (My drawing is a hybrid of a state graph and a Kripke structure.)&lt;/p&gt;
&lt;p&gt;Like we saw in the last article, we can use properties of this graph to calculate agents&amp;rsquo; knowledge. For example, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_A K_B sent(m)&#34;&gt;
&lt;/picture&gt; is true in state &lt;em&gt;s&lt;/em&gt; iff all paths from &lt;em&gt;s&lt;/em&gt; that follow zero or one &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;&lt;em&gt;A&lt;/em&gt;-indistinguishable edges&lt;/span&gt;, then zero or one &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;&lt;em&gt;B&lt;/em&gt;-indistinguishable edges&lt;/span&gt;, end at a state where &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;sent(m)&#34;&gt;
&lt;/picture&gt; is true.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s choose a particular state as a demonstration: the state where the current time is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S&#34;&gt;
&lt;/picture&gt; and &lt;em&gt;B&lt;/em&gt; received &lt;em&gt;m&lt;/em&gt; with zero delay. We can follow &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;one &lt;em&gt;A&lt;/em&gt;-edge&lt;/span&gt;, then zero &lt;em&gt;B&lt;/em&gt;-edges, and arrive at the state labeled &amp;ldquo;sent&amp;rdquo;, where &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;sent(m)&#34;&gt;
&lt;/picture&gt; is true. Or we can follow the same &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;one &lt;em&gt;A&lt;/em&gt;-edge&lt;/span&gt;, and then &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;one &lt;em&gt;B&lt;/em&gt;-edge&lt;/span&gt; and arrive at the state labeled &amp;ldquo;unsent,&amp;rdquo; where &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;sent(m)&#34;&gt;
&lt;/picture&gt; is false. Since both &amp;ldquo;sent&amp;rdquo; and &amp;ldquo;unsent&amp;rdquo; are reachable from the current state, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_A K_B sent(m)&#34;&gt;
&lt;/picture&gt; is false.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals-bounded-delay-2.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals-bounded-delay-2.excalidraw.jpg&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_A K_B sent(m)&#34;&gt;
&lt;/picture&gt; is &lt;strong&gt;false&lt;/strong&gt; in the state marked &amp;ldquo;current state.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;But if you start from either of the states where the current time is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f381ccba2b57830adec1417657af87916d83e46cdc3dfc9b103c2d0ed99f116a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f381ccba2b57830adec1417657af87916d83e46cdc3dfc9b103c2d0ed99f116a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + \epsilon&#34;&gt;
&lt;/picture&gt; and follow zero or one &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;red&lt;/span&gt; edges, then zero or one &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;green&lt;/span&gt; edges, you always arrive at states where &lt;em&gt;m&lt;/em&gt; was sent. Thus &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_A K_B sent(m)&#34;&gt;
&lt;/picture&gt; is true in those states.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals-bounded-delay-3.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/generals-bounded-delay-3.excalidraw.jpg&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f7d7e702de4c258d694ffab5508f0dd9d7dcc3dcc716b0dfb8d2b63980d1cc78.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_A K_B sent(m)&#34;&gt;
&lt;/picture&gt; is &lt;strong&gt;true&lt;/strong&gt; in the state marked &amp;ldquo;current state.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;But if you want a state where &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-16af54bf12f9a5cdfbfa662b7473183dcbb043fa0c0e4d6928a60bddd6b3d52c.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-16af54bf12f9a5cdfbfa662b7473183dcbb043fa0c0e4d6928a60bddd6b3d52c.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;(K_A K_B)^2 sent(m)&#34;&gt;
&lt;/picture&gt;, you need to start farther away, at one of the states where the time is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-0062b81aa9f244d07bf5bea15eb8c8d390339bcc805966cf4c5f1940fa270b59.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-0062b81aa9f244d07bf5bea15eb8c8d390339bcc805966cf4c5f1940fa270b59.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + 2 \epsilon&#34;&gt;
&lt;/picture&gt; or later. Otherwise there exists a path that follows a &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;red&lt;/span&gt;, then &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;green&lt;/span&gt;, then &lt;span style=&#34;font-weight: bold; color: #e03131&#34;&gt;red&lt;/span&gt;, then &lt;span style=&#34;font-weight: bold; color: #2f9e44&#34;&gt;green&lt;/span&gt; edge and arrives at a state where &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-44e300103c7b45a20318b56f56aeab52c2fb83d9daf37fd0b44d1af81af644c2.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;sent(m)&#34;&gt;
&lt;/picture&gt; is false. (I haven&amp;rsquo;t drawn a picture of this.) Continuing inductively, we can see how the graph expresses the same thing we proved before: &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-6f550ffc3333551f66c16d67706c94e0dbfc5ad46024e3f27e7872c92b87e971.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-6f550ffc3333551f66c16d67706c94e0dbfc5ad46024e3f27e7872c92b87e971.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;(K_A K_B)^k sent(m)&#34;&gt;
&lt;/picture&gt; is true only if the current time is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-497aaa483950555ee8cc08a26fb0da892469fbfcf21d108439b9caf8934b1234.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-497aaa483950555ee8cc08a26fb0da892469fbfcf21d108439b9caf8934b1234.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + k \epsilon&#34;&gt;
&lt;/picture&gt; or later.&lt;/p&gt;
&lt;h1&gt;What if the generals have synchronized clocks?&lt;/h1&gt;

&lt;p&gt;It seems weird to conclude that the generals can&amp;rsquo;t coordinate, even with bounded message delays. Isn&amp;rsquo;t it enough for &lt;em&gt;A&lt;/em&gt; to say, &amp;ldquo;attack at noon tomorrow&amp;rdquo; at least &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; before that time, with no acknowledgment from &lt;em&gt;B&lt;/em&gt;? Yes, this works, but the &lt;em&gt;protocol itself&lt;/em&gt; must be common knowledge at the start, and the generals must have perfectly synchronized clocks. If the generals have already agreed that &lt;em&gt;A&lt;/em&gt; will send a message proposing an attack at least &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; in the future, then when &lt;em&gt;B&lt;/em&gt; receives &amp;ldquo;attack at noon tomorrow,&amp;rdquo; she knows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;General &lt;em&gt;A&lt;/em&gt; sent this at least &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; before noon (that&amp;rsquo;s the protocol)&lt;/li&gt;
&lt;li&gt;Therefore at noon, &lt;em&gt;A&lt;/em&gt; knows &lt;em&gt;B&lt;/em&gt; received it&lt;/li&gt;
&lt;li&gt;Therefore at noon, &lt;em&gt;B&lt;/em&gt; knows &lt;em&gt;A&lt;/em&gt; knows &lt;em&gt;B&lt;/em&gt; received it&lt;/li&gt;
&lt;li&gt;And so on&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At noon common knowledge is achieved, because both generals can reason about what the other knows &lt;em&gt;will&lt;/em&gt; be true at the deadline.&lt;/p&gt;
&lt;p&gt;Halpern and Moses don&amp;rsquo;t propose this exact idea. They instead use timestamped messages: &amp;ldquo;The time is now &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S&#34;&gt;
&lt;/picture&gt;, let&amp;rsquo;s attack at noon.&amp;rdquo; This removes General &lt;em&gt;B&lt;/em&gt;&amp;rsquo;s uncertainty about &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-13bd7ad72d409d6a37913eb5cc9aa660536e23ecf4dfa7c641efb56faae0f469.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S&#34;&gt;
&lt;/picture&gt;, so at time &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f381ccba2b57830adec1417657af87916d83e46cdc3dfc9b103c2d0ed99f116a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-f381ccba2b57830adec1417657af87916d83e46cdc3dfc9b103c2d0ed99f116a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;t_S + \epsilon&#34;&gt;
&lt;/picture&gt; the plan becomes common knowledge. This is pretty much equivalent: my pre-agreed protocol and their timestamped messages both provide extra shared facts from which the two generals can reason.&lt;/p&gt;
&lt;p&gt;Both solutions require perfectly synchronized clocks. If &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; have the slightest uncertainty about when noon arrives, they can&amp;rsquo;t become sure of the plan simultaneously, so they can &lt;em&gt;never&lt;/em&gt; achieve common knowledge.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/Athena_owl_Met_09.221.43.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Weaker forms of common knowledge&lt;/h1&gt;

&lt;p&gt;This still seems weird. In practice, I can arrange to meet someone for lunch, and a Raft system can reach consensus, so why is it impossible in theory? It&amp;rsquo;s because Halpern and Moses&amp;rsquo;s definition of common knowledge is impractically strong. So they define weaker forms of shared knowledge that are achievable and suffice for consensus or lunch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt;-common knowledge&lt;/strong&gt; (&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-b724237785e95b3c28dae5abc80d99fa824172bd37ec71ba49817ca4b6accaae.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-b724237785e95b3c28dae5abc80d99fa824172bd37ec71ba49817ca4b6accaae.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C^{\,\epsilon}\phi&#34;&gt;
&lt;/picture&gt;): Instead of &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-10ce0200b450ea955833553e717821c7721818b8a95cffd7e54b46e08918b512.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-10ce0200b450ea955833553e717821c7721818b8a95cffd7e54b46e08918b512.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\phi&#34;&gt;
&lt;/picture&gt; becoming common knowledge simultaneously, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-10ce0200b450ea955833553e717821c7721818b8a95cffd7e54b46e08918b512.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-10ce0200b450ea955833553e717821c7721818b8a95cffd7e54b46e08918b512.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\phi&#34;&gt;
&lt;/picture&gt; becomes &amp;ldquo;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt;-common knowledge&amp;rdquo; in an &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt; time window. So if message delivery is guaranteed after some bounded delay, its contents are common knowledge a bounded time after it&amp;rsquo;s sent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timestamped common knowledge&lt;/strong&gt; (&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c94fd79f2de7324b264b1426f2de2d9dc2c2e7099b1bd7557cd32dcf5a024abd.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c94fd79f2de7324b264b1426f2de2d9dc2c2e7099b1bd7557cd32dcf5a024abd.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C^T&#34;&gt;
&lt;/picture&gt;): Everyone knows at time &lt;em&gt;T&lt;/em&gt; &lt;em&gt;on their own clock&lt;/em&gt;. If clocks are perfectly synchronized, then &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c94fd79f2de7324b264b1426f2de2d9dc2c2e7099b1bd7557cd32dcf5a024abd.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c94fd79f2de7324b264b1426f2de2d9dc2c2e7099b1bd7557cd32dcf5a024abd.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C^T&#34;&gt;
&lt;/picture&gt; is equivalent to common knowledge. If they&amp;rsquo;re synchronized within &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-8bc4bfc2bdebfaac14a98cde1deb3cf75202e7908905efb95c939c0481951f77.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\epsilon&#34;&gt;
&lt;/picture&gt;, then &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c94fd79f2de7324b264b1426f2de2d9dc2c2e7099b1bd7557cd32dcf5a024abd.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-c94fd79f2de7324b264b1426f2de2d9dc2c2e7099b1bd7557cd32dcf5a024abd.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C^T&#34;&gt;
&lt;/picture&gt; is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-a9210c4b760642b0c22805072ced2c5c9b0a4a0bccc714618e4f96c15dbbbe59.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-a9210c4b760642b0c22805072ced2c5c9b0a4a0bccc714618e4f96c15dbbbe59.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C^{\,\epsilon}&#34;&gt;
&lt;/picture&gt;. You can discard clocks and substitute something like a Raft term number for the timestamp and say, &amp;ldquo;which node is the leader in term &lt;em&gt;t&lt;/em&gt; is timestamped common knowledge.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eventual common knowledge&lt;/strong&gt; (&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-577553aa50b11d24f33020a4ec6bf025e05bf074327c19d83dee38ffeea5158c.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-577553aa50b11d24f33020a4ec6bf025e05bf074327c19d83dee38ffeea5158c.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C^{\,\diamond}&#34;&gt;
&lt;/picture&gt;): Everyone will eventually know, though not necessarily at the same time. This suffices for actions that must eventually happen everywhere, without a deadline.&lt;/p&gt;
&lt;p&gt;These form a hierarchy: &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-07d5e20ded4bb4e92bf2ea93d8f4a47681275d493efd956db5254443b5a24ab7.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/formula-07d5e20ded4bb4e92bf2ea93d8f4a47681275d493efd956db5254443b5a24ab7.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C \Rightarrow C^{\,\epsilon} \Rightarrow C^{\,\diamond}&#34;&gt;
&lt;/picture&gt;.&lt;/p&gt;
&lt;h1&gt;Internal knowledge consistency&lt;/h1&gt;

&lt;p&gt;Another solution to the paradox&amp;mdash;common knowledge is impossible in theory but possible in practice&amp;mdash;is &amp;ldquo;internal knowledge consistency.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Every agent in the system has an &lt;em&gt;interpretation&lt;/em&gt; of its observations. Formally, an interpretation is a function that takes the agent&amp;rsquo;s personal history and the current time, and outputs a set of facts the agent believes are true. For example, in the muddy children puzzle, after the children hear the father&amp;rsquo;s announcement &lt;em&gt;m&lt;/em&gt;, &amp;ldquo;at least one of you is muddy,&amp;rdquo; each child believes it heard &lt;em&gt;m&lt;/em&gt;. An interpretation is &amp;ldquo;knowledge consistent&amp;rdquo; if it produces only &lt;em&gt;true&lt;/em&gt; beliefs.&lt;/p&gt;
&lt;p&gt;A knowledge consistent interpretation must say that &lt;em&gt;m&lt;/em&gt; isn&amp;rsquo;t common knowledge, because in reality, the children &lt;em&gt;can&amp;rsquo;t&lt;/em&gt; achieve common knowledge of &lt;em&gt;m&lt;/em&gt;: How do they all know that they all understand English, and they were all paying attention, etc.? But an &lt;em&gt;internally&lt;/em&gt; knowledge consistent interpretation can say it is, so long as no observation will ever contradict this belief. This matches how we intuitively reason about knowledge and common knowledge in everyday situations.&lt;/p&gt;
&lt;p&gt;This concludes my review of Halpern and Moses&amp;rsquo;s &amp;ldquo;Knowledge and Common Knowledge in a Distributed Environment&amp;rdquo;! But I&amp;rsquo;ll have more to say about epistemic logic and distributed systems. I&amp;rsquo;m now in a UCLA seminar with &lt;a href=&#34;https://remy.wang/&#34;&gt;Remy Wang&lt;/a&gt;, reading &lt;a href=&#34;https://direct.mit.edu/books/monograph/1825/Reasoning-About-Knowledge&#34;&gt;Reasoning About Knowledge&lt;/a&gt;, the short book Halpern and Moses wrote with Ronald Fagin and Moshe Vardi. My goal now is to marry epistemic logic with TLA+, by analyzing a TLA+ specification&amp;rsquo;s state graph.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-3/peleus.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Images:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Theseus_Athena_Amphitrite_Louvre_G104.jpg&#34;&gt;Theseus Athena and Amphitrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://upload.wikimedia.org/wikipedia/commons/0/03/Terracotta_lekythos_%28oil_flask%29_MET_DP161824.jpg&#34;&gt;Oil flask with depiction of Athena, goddess of wisdom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Athena_owl_Met_09.221.43.jpg&#34;&gt;Athena with an owl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Oedipus_Painter_ARV_441_185_Herakles_and_Athena_-_Peleus_subduing_Thetis_%2802%29.jpg&#34;&gt;Athena and Herakles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Review: Rethink the Linearizability Constraints of Raft for Distributed Systems</title>
      <link>https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/</link>
      <pubDate>Fri, 26 Dec 2025 23:04:21 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/psycho-magnetic-curves.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://ieeexplore.ieee.org/document/10012573&#34;&gt;Rethink the Linearizability Constraints of Raft for Distributed Systems&lt;/a&gt; (behind the IEEE paywall, dammit), some academic researchers describe Raft optimizations that make reads and writes quicker, while preserving linearizability. &lt;a href=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/&#34;&gt;Raft and linearizability are my specialities&lt;/a&gt;, and I&amp;rsquo;m pretty sure I see mistakes in this paper. The ideas are worth considering anyway. I recommend it, if you or a friend has an IEEE subscription.&lt;/p&gt;


&lt;h1&gt;Lower-latency writes&lt;/h1&gt;

&lt;h2&gt;Classic Raft&lt;/h2&gt;

&lt;p&gt;Here&amp;rsquo;s how writes work in classic Raft, as described by Diego Ongaro in his &lt;a href=&#34;https://raft.github.io/raft.pdf&#34;&gt;paper&lt;/a&gt; and &lt;a href=&#34;https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf&#34;&gt;thesis&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A client sends a write command to the leader.&lt;/li&gt;
&lt;li&gt;The leader appends a log entry, which includes the command, to its log at position &lt;em&gt;i&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The leader waits for the entry to be replicated by a majority of followers, i.e. committed.&lt;/li&gt;
&lt;li&gt;The leader advances its commitIndex to &lt;em&gt;i&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The leader applies the command to its copy of the state machine.&lt;/li&gt;
&lt;li&gt;The leader advances its lastApplied index to &lt;em&gt;i&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The leader replies to the client with the command&amp;rsquo;s result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The commitIndex and lastApplied variables are separate for concurrency&amp;rsquo;s sake. One thread is responsible for communicating with followers and advancing the commitIndex. Another thread  applies committed commands and advances lastApplied. That&amp;rsquo;s how Ongaro built his reference implementation, &lt;a href=&#34;https://github.com/logcabin/logcabin&#34;&gt;LogCabin&lt;/a&gt;, and it&amp;rsquo;s a good idea for any multithreaded Raft.&lt;/p&gt;
&lt;h2&gt;Commit Return optimization&lt;/h2&gt;

&lt;p&gt;In the &amp;ldquo;Rethink&amp;rdquo; paper, the authors propose that the leader replies to the client as soon as the entry is &lt;em&gt;committed&lt;/em&gt;, without waiting until it&amp;rsquo;s &lt;em&gt;applied&lt;/em&gt;. They call this optimization &amp;ldquo;Commit Return.&amp;rdquo; It works for blind writes, where the client doesn&amp;rsquo;t need to know anything about the command&amp;rsquo;s result besides &amp;ldquo;it was committed.&amp;rdquo; E.g., if the state machine is a key-value store and the client sends a command like &lt;code&gt;set x := 1&lt;/code&gt;, the leader could reply &amp;ldquo;ok&amp;rdquo; before it updates its state machine.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/classic-raft-write.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/classic-raft-write.png&#34;&gt;
&lt;/picture&gt;&lt;!-- Client-&gt;Leader: &#34;x := 1&#34;
box over Leader: append log entry at index i

Leader-&gt;Follower1: AppendEntries
Leader-&gt;Follower2: AppendEntries

Follower1-&gt;Leader: AppendEntriesResponse

box over Leader: advance commitIndex to i

Leader-&gt;Client: reply &#34;ok&#34; (in the &#34;Rethink&#34; paper)

box over Leader: run &#34;x := 1&#34;, advance lastApplied to i

Leader-&gt;Client: reply &#34;ok&#34; (in classic Raft)
--&gt;
&lt;p&gt;Commit Return doesn&amp;rsquo;t work when the client sends a command that returns a data-dependent value, like &lt;code&gt;return ++x&lt;/code&gt;. The leader must apply the command before it knows what result to return to the client. (Can the leader know at commit-time what the return value of &lt;code&gt;++x&lt;/code&gt; will be? No, there might be another committed and unapplied command in the pipeline that modifies &lt;code&gt;x&lt;/code&gt; before &lt;code&gt;++x&lt;/code&gt; is applied.)&lt;/p&gt;
&lt;h2&gt;I&amp;rsquo;m skeptical&lt;/h2&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/skeptical.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The authors claim that Commit Return is a big win, because sometimes applying a command takes much longer than committing it. This is surprising to me, because like most distributed systems people I assume that network latency dominates, so committing takes most of the time. The authors argue the opposite, and they construct a benchmark where this is true, with five nodes in a single data center connected by a high-speed network. The authors&amp;rsquo; Figure 4 shows the percentage of time the leader spends appending entries to its log, or communicating with followers, or applying commands. With 1-kilobyte commands, 40% of write command latency is attributed to applying the command:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/figure-4.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;Figure 4 in the &amp;ldquo;Rethink&amp;rdquo; paper.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I admit that applying commands could be slower than majority-replicating them in this situation. But usually, you deploy a system across multiple availability zones—if you&amp;rsquo;re going to the trouble of replicating, you might as well be truly fault tolerant. Across AZs, &lt;a href=&#34;http://arxiv.org/abs/2309.13169&#34;&gt;you&amp;rsquo;re likely to see at least 1 ms of latency&lt;/a&gt;. And if you care about latency, then your application is OLTP, and your commands are probably mostly much smaller than 1 kilobyte and very quick to apply.&lt;/p&gt;
&lt;p&gt;Oddly, I see that applying takes a larger percentage of the time for smaller commands in their benchmark. I guess there&amp;rsquo;s a lot of fixed overhead per command when their system applies commands, so small commands replicate quickly and take a comparatively long time to apply. The authors say, &amp;ldquo;Apply is slow because the apply operation involves writing the state machine log to disk.&amp;rdquo; But the authors separately measure the time spent appending to the Raft log. I wonder what &lt;em&gt;additional&lt;/em&gt; logging is part of applying in their system?&lt;/p&gt;
&lt;p&gt;Commit Return is a useful idea, regardless. In a more normal multi-AZ deployment, where most of the latency is the network&amp;rsquo;s fault, Commit Return reduces latency a smidge for blind writes, from the client&amp;rsquo;s perspective. The bulk of the paper isn&amp;rsquo;t about Commit Return, anyway: it&amp;rsquo;s about read optimizations that are independent of Commit Return. That part of the paper is more interesting, and definitely has a mistake!&lt;/p&gt;
&lt;h1&gt;Lower-latency reads&lt;/h1&gt;

&lt;h2&gt;Classic Raft&lt;/h2&gt;

&lt;p&gt;Here&amp;rsquo;s how reads work in Raft:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A client sends a query to the leader.&lt;/li&gt;
&lt;li&gt;The leader sets the query&amp;rsquo;s readIndex to the leader&amp;rsquo;s current commitIndex.&lt;/li&gt;
&lt;li&gt;The leader waits until its lastApplied ≥ the query&amp;rsquo;s readIndex.&lt;/li&gt;
&lt;li&gt;The leader runs the client&amp;rsquo;s query and returns the result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures the client sees the effect of all writes that were committed before the query started.&lt;/p&gt;
&lt;p&gt;But that&amp;rsquo;s not enough to guarantee linearizability! The problem is, &lt;a href=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/#example-leader-leases-rely-on-timers&#34;&gt;a Raft leader is never sure that it&amp;rsquo;s the real leader, or if it&amp;rsquo;s been deposed by a newer one&lt;/a&gt;. If it&amp;rsquo;s deposed, then the newer leader is executing writes that the deposed leader doesn&amp;rsquo;t see, and the deposed leader&amp;rsquo;s reads will reveal stale data, violating linearizability.&lt;/p&gt;
&lt;p&gt;How does a Raft leader ensure it isn&amp;rsquo;t deposed? In classic Raft, Ongaro recommends that for each read, the leader sends a message (an empty AppendEntries message specifically) to all its followers, checking that they haven&amp;rsquo;t seen a newer leader. The leader waits for a quorum of followers to reply before it runs the client&amp;rsquo;s query.&lt;/p&gt;
&lt;!-- Client-&gt;Leader: query
box over Leader: readIndex := commitIndex

Leader-&gt;Follower1: AppendEntries
Leader-&gt;Follower2: AppendEntries

Follower1-&gt;Leader: AppendEntriesResponse

box over Leader: wait until lastApplied ≥ readIndex

box over Leader: run query

Leader-&gt;Client: result
--&gt; 

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/classic-raft-read.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/classic-raft-read.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;This is expensive, obviously—reads now pay the same network cost as writes—so commercial Raft implementations often use timed &lt;a href=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/&#34;&gt;leader leases&lt;/a&gt; instead, or else they just don&amp;rsquo;t guarantee linearizability.&lt;/p&gt;
&lt;p&gt;Unfortunately, the &amp;ldquo;Rethink&amp;rdquo; authors don&amp;rsquo;t seem to know about the deposed-leader problem at all. They claim to guarantee linearizability, but they don&amp;rsquo;t mention which, if any, technique they use to prevent reading from a deposed leader. I&amp;rsquo;ll come back to this topic and see if the paper can be saved.&lt;/p&gt;
&lt;h2&gt;Read Acceleration&lt;/h2&gt;

&lt;p&gt;As I said, in classic Raft, the leader sets a query&amp;rsquo;s read index to its commitIndex, then waits for lastApplied to reach the read index. The &amp;ldquo;Rethink&amp;rdquo; authors want to reduce this waiting period. Their ideas for this are the best part of the paper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Immediate Read:&lt;/strong&gt; The leader immediately runs the client&amp;rsquo;s read, and buffers the result. Then it applies the log entries between lastApplied and the read index to the result, and returns the updated result to the client. This is faster than just waiting until the applier thread applies these entries to the whole state machine, as in classic Raft, because the buffered result is small, and because the leader can ignore entries irrelevant to this result.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/immediate-read.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 9 from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say &lt;code&gt;x&lt;/code&gt; is zero in the current state machine, and the client sends &lt;code&gt;get x&lt;/code&gt; to the leader. The leader has these committed and unapplied log entries:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;x := 9
z := 1
z := 7
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The leader runs &lt;code&gt;get x&lt;/code&gt; on the current state machine and creates the buffered result, &lt;code&gt;x == 0&lt;/code&gt;. It applies &lt;code&gt;x := 9&lt;/code&gt; to the buffered result before returning it, and ignores the entries that modify &lt;code&gt;z&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Semantic-Influencing Request:&lt;/strong&gt; Sometimes, the Immediate Read fixup process is impossible. Let&amp;rsquo;s say the client sends a SQL query like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;table&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;WHERE&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The leader runs this query and buffers the resulting rows. Now it looks in its log and sees a committed and unapplied log entry like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;UPDATE&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;table&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;SET&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The buffered result has the &lt;em&gt;wrong rows&lt;/em&gt;! It includes all the rows where &lt;code&gt;x&lt;/code&gt; was 1 when the leader received the query, but these are not the rows where &lt;code&gt;x&lt;/code&gt; is 1 after running &lt;code&gt;UPDATE&lt;/code&gt;. The leader can&amp;rsquo;t fix this up as easily as the previous example. The authors call this &lt;code&gt;UPDATE&lt;/code&gt; a &amp;ldquo;Semantic-Influencing Request.&amp;rdquo; If such a request appears in the pending log entries, the leader could try something fancy like editing the original query before running it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-- Change the filter from x = 1 to x = 0.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;table&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;WHERE&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This query selects rows that &lt;em&gt;will&lt;/em&gt; match the client&amp;rsquo;s query, &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;UPDATE&lt;/code&gt; is applied. The authors say the leader should estimate the cost of fixing up the query, versus waiting until it can be run normally, and act accordingly.&lt;/p&gt;
&lt;h1&gt;Interaction of read and write optimizations&lt;/h1&gt;

&lt;p&gt;The authors say that Commit Return speeds up writes, but may slow down reads in exchange, because faster writes lead to a larger gap between the leader&amp;rsquo;s commitIndex and lastApplied. They even have a chart:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/figure-5.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The red bars are the percentage of read latency in classic Raft that&amp;rsquo;s due to waiting for lastApplied to catch up to the query&amp;rsquo;s readIndex (which was the commitIndex when the query arrived). The higher blue bars are the percentage with Commit Return. The blue/red line shows the average gap between the readIndex and lastApplied with/without Commit Return, measured in number of entries. The horizontal axis is the percentage of writes in the YCSB workload, from 90% writes on the left to 10% writes on the right.&lt;/p&gt;
&lt;p&gt;The authors claim this chart shows that with Commit Return enabled, there is a bigger gap between the leader&amp;rsquo;s commitIndex and lastApplied. This is an unfortunate mistake. I&amp;rsquo;m certain that Commit Return has no effect on the gap or on query latency. The authors were led astray by YCSB.&lt;/p&gt;
&lt;p&gt;Commit Return merely &lt;em&gt;reduces write latency from the client&amp;rsquo;s perspective&lt;/em&gt;: if the client does a blind write, the server replies &amp;ldquo;ok&amp;rdquo; a bit sooner, and the client can move on to its next task. If the client is &lt;a href=&#34;https://muratbuffalo.blogspot.com/2023/05/open-versus-closed-cautionary-tale.html&#34;&gt;a closed loop&lt;/a&gt;, then it waits for each request to finish before it starts the next. Let&amp;rsquo;s say this is the client&amp;rsquo;s code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;True&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;result1&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;do_request&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;set x := x + 1&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;result2&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;do_request&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;get x&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In classic Raft, &lt;code&gt;set x := x + 1&lt;/code&gt; doesn&amp;rsquo;t return until the leader applies it, so the leader can run &lt;code&gt;get x&lt;/code&gt; without much waiting. (If other clients submitted writes that are committed and unapplied when &lt;code&gt;get x&lt;/code&gt; arrives; the leader still has to wait until &lt;em&gt;those&lt;/em&gt; entries are applied before running &lt;code&gt;get x&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;With Commit Return, &lt;code&gt;set x := x + 1&lt;/code&gt; returns as soon as it&amp;rsquo;s committed. Thus when the client sends &lt;code&gt;get x&lt;/code&gt;, the gap between the leader&amp;rsquo;s commitIndex and its lastApplied is bigger than without Commit Return.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s important to understand that Commit Return doesn&amp;rsquo;t delay the advancement of lastApplied, so it doesn&amp;rsquo;t increase the gap between commitIndex and lastApplied in general! It just returns control to the client sooner, so the client can send a higher throughput of blind writes, or start querying sooner after sending a blind write. This only matters if you&amp;rsquo;re benchmarking the system with a &lt;em&gt;closed-loop&lt;/em&gt; workload generator like YCSB. YCSB lets the server exert backpressure on the client, so it doesn&amp;rsquo;t measure throughput or latency realistically. (This is called &lt;a href=&#34;https://redhatperf.github.io/post/coordinated-omission/&#34;&gt;the coordinated omission problem&lt;/a&gt;.)&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/closed-system.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/closed-system.png&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;A closed system.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;With a more true-to-life &lt;em&gt;open-loop&lt;/em&gt; workload generator, Commit Return does not have this effect. The authors&amp;rsquo; claim that Commit Return makes Read Acceleration more important is a misunderstanding based on this obsolete benchmark.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/open-system.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/open-system.png&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;An open system.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Tragically, lots of distributed systems researchers still use YCSB, so I see this mistake all the time. Worse, I see reviewers, who should know better, requesting that authors add YCSB benchmarks to their evaluations. YCSB benchmarks are wrong. If you&amp;rsquo;re a researcher, please switch to an open-loop workload generator, or invent one for the rest of us to use. If you&amp;rsquo;re a reviewer, point out this mistake when you see it. &lt;a href=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/&#34;&gt;Let&amp;rsquo;s all standardize on some modern, open-loop benchmarks&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;What about deposed leaders?&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-rethink-linearizability-constraints-raft/napolean.jpg&#34; alt=&#34;Napolean sitting slumped in a chair, staring at nothing&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.napoleon.org/en/history-of-the-two-empires/paintings/napoleon-i-at-fontainebleau-31-march-1814/&#34;&gt;Napolean, about to resign&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The authors&amp;rsquo; optimizations reduce latency and maintain linearizability, &lt;em&gt;if&lt;/em&gt; the leader isn&amp;rsquo;t deposed. How do they guarantee that it isn&amp;rsquo;t?&lt;/p&gt;
&lt;p&gt;If the leader checks a quorum of followers for each query, as in classic Raft, that gives the leader more time to concurrently apply any pending writes. It&amp;rsquo;s still possible that lastApplied hasn&amp;rsquo;t caught up to the readIndex during the quorum check—the authors constructed a scenario where applying commands is slow and communication is quick—but Read Acceleration is less useful once we take the quorum check into account.&lt;/p&gt;
&lt;p&gt;The better way to handle a deposed leader is with a timed lease. I think the Read Acceleration techniques could be useful when paired with leases. I just hope anyone who reads this paper keeps the deposed leader problem in mind.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;This paper is a worthwhile read, despite its flaws.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Con:&lt;/strong&gt; I doubt that it often takes longer to apply commands than replicate them, as the authors claim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Even so, the Commit Return optimization might be a useful idea sometimes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro and con?:&lt;/strong&gt; Commit Return &lt;em&gt;doesn&amp;rsquo;t&lt;/em&gt; have the downside the authors think it has: they think it makes reads slower, but that&amp;rsquo;s only in the artificial environment of a YCSB benchmark. Commit Return is better than they realize.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Con:&lt;/strong&gt; The authors&amp;rsquo; protocol does &lt;em&gt;not&lt;/em&gt; guarantee linearizability unless they deal with deposed leaders somehow, like with a lease.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Read Acceleration is a cool idea. It&amp;rsquo;s useful if there&amp;rsquo;s a big gap between commitIndex and lastApplied. I really don&amp;rsquo;t think this is a common scenario, but the idea has other uses. &lt;a href=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/&#34;&gt;I just published a paper in SIGMOD&lt;/a&gt;, with my colleagues Murat Demirbas and Lingzhi Deng, that uses a similar mechanism to permit linearizable reads when there might be multiple leaders.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://publicdomainreview.org/essay/lofty-only-in-sound-crossed-wires-and-community-in-19th-century-dreams/&#34;&gt;Illustration from Edwin D. Babbitt’s The Principles of Light and Color (1878)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://publicdomainreview.org/essay/on-oscar-wilde-and-plagiarism/&#34;&gt;Oscar Wilde and Whistler, by Phil May, 1894&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.napoleon.org/en/history-of-the-two-empires/paintings/napoleon-i-at-fontainebleau-31-march-1814/&#34;&gt;Napoleon I at Fontainebleau, 31 March, 1814, by Paul Delaroche, 1845&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>YCSB Is Obsolete, We Need New Benchmarks</title>
      <link>https://emptysqua.re/blog/ycsb-is-obsolete/</link>
      <pubDate>Sun, 21 Dec 2025 17:44:18 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/ycsb-is-obsolete/</guid>
      
      
      
      
      <description>&lt;p&gt;Let&amp;rsquo;s build a simulation of a coffeeshop. We&amp;rsquo;ll have some resources, like baristas and espresso machines. As soon as the shop opens, a fixed number of patrons—maybe ten—all line up for coffee. Whenever a patron receives her coffee, she throws it away and instantly returns to the back of the line. If the barista is slow, that&amp;rsquo;s okay: the line never grows longer than ten people.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/closed-loop-coffeeshop.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/closed-loop-coffeeshop.png&#34; alt=&#34;A stick figure drawing. Nine people stand in line to get coffee. A barista stands behind the counter. A tenth figure is tossing its cup in the trash and returning to the back of the line.&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;A closed loop coffeeshop.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Oh, that&amp;rsquo;s not realistic, you say? You say that patrons arrive at random times, join the line, and leave when they get their coffee?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/open-loop-coffeeshop.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/open-loop-coffeeshop.png&#34; alt=&#34;A stick figure drawing. Four people stand in line to get coffee. A barista stands behind the counter. Four figures are arriving from several directions and joining the back of the line. Two figures are leaving in different directions, holding coffee cups.&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;An open loop coffeeshop.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I agree, that is a better model of a coffeeshop. As you have guessed, the coffeeshop is a metaphor, and my actual topic is cloud databases. In the cloud, there&amp;rsquo;s no fixed number of clients. New requests are mostly triggered by external events, not by the completion of previous requests. If the database can&amp;rsquo;t keep up, requests continue to arrive and pile up or time out. But standard benchmarks like YCSB or TPC assume the first model, the unrealistic one, so they give unrealistic results.&lt;/p&gt;
&lt;h1&gt;The Difference Between Open and Closed&lt;/h1&gt;

&lt;p&gt;The first model is an example of a &amp;ldquo;closed system model&amp;rdquo; or a &amp;ldquo;closed loop&amp;rdquo;: there is a fixed number of outstanding tasks. Once the database server is saturated, a new task won&amp;rsquo;t arrive until one of the outstanding tasks finishes. The second is an &amp;ldquo;open system model&amp;rdquo; or &amp;ldquo;open loop&amp;rdquo;: new tasks arrive whenever they want to, independent of when outstanding tasks finish. In an open system, the interarrival time between tasks is usually randomly distributed. If the distribution is exponential, then the arrivals are a &lt;a href=&#34;https://ocw.mit.edu/courses/6-262-discrete-stochastic-processes-spring-2011/3a19ce0e02d0008877351bfa24f3716a_MIT6_262S11_chap02.pdf&#34;&gt;Poisson process&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The real world is open, but we stubbornly keep using closed loop benchmarks to test our databases!&lt;/p&gt;
&lt;p&gt;A closed loop benchmark generates load like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client_func&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;OPERATION_COUNT&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;response&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;do_database_request&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;record_latency&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;sleep&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;INTERARRIVAL&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;threads&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;target&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;client_func&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;THREAD_COUNT&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;threads&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;threads&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;join&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The latency of &lt;code&gt;do_database_request&lt;/code&gt; affects how much load the benchmark generates—the database implicitly exerts backpressure. The longer requests take, the longer the time between requests.&lt;/p&gt;
&lt;p&gt;An open loop benchmark generates load like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;one_operation&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;do_database_request&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;record_latency&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;OPERATION_COUNT&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;target&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;one_operation&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;daemon&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;True&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;sleep&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;INTERARRIVAL&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code starts a new request whenever the interarrival time has passed, no matter how many requests are still outstanding. For a Poisson process, you just make the interarrival time exponentially distributed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;LAMBDA&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1000.0&lt;/span&gt;  &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# ops per second&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;next_time&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;OPERATION_COUNT&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;next_time&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;random&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;expovariate&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;LAMBDA&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;target&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;one_operation&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;daemon&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;True&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;start&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;sleep&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#204a87&#34;&gt;max&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;next_time&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Is this any harder to code than the closed loop version? In 2025 we are all familiar with &lt;a href=&#34;https://emptysqua.re/blog/why-should-async-get-all-the-love/&#34;&gt;multithreaded&lt;/a&gt; or (better in this case) async programming, we can build open loop benchmarks easily. The time has come for us all to switch to open loop benchmarks.&lt;/p&gt;
&lt;h1&gt;Years Of Warnings Ignored&lt;/h1&gt;

&lt;p&gt;In the 2006 paper &lt;a href=&#34;https://www.usenix.org/conference/nsdi-06/open-versus-closed-cautionary-tale&#34;&gt;Open Versus Closed: A Cautionary Tale&lt;/a&gt;, some researchers (including &lt;a href=&#34;https://emptysqua.re/blog/review-queue-theory-book/&#34;&gt;Mor Harchol-Balter&lt;/a&gt;) benchmarked a Postgres server with TPC-W, the web commerce benchmark that&amp;rsquo;s part of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Transaction_Processing_Performance_Council&#34;&gt;TPC suite&lt;/a&gt;. Like all the TPC benchmarks, TPC-W is closed loop, but the researchers also made a version that&amp;rsquo;s open loop. They compared the standard TPC-W results with their open loop version:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/closed-versus-open.png&#34; alt=&#34;Two charts with load on the horizontal axis and mean response time on the vertical axis. The left chart is labeled closed system. Mean response time never rises over 2 seconds. The right is labeled open system. Mean response time rises to 8 seconds.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The closed loop benchmark can never overload the database, because it has a fixed number of threads that each wait politely for the database to respond before sending the next request. Mean response time never exceeds 2 seconds. The open loop benchmark is rude: it overwhelms the database, unfinished requests pile up and wait in the queue. Mean response time exceeds 6 seconds. This is the kind of realistic data we need to see when we test our databases, but the standard closed loop TPC won&amp;rsquo;t show it to us.&lt;/p&gt;
&lt;p&gt;(The PELJF, PS, and PESJF lines are for different scheduling algorithms—that&amp;rsquo;s an interesting part of the paper, you should read it. &lt;a href=&#34;https://muratbuffalo.blogspot.com/2023/05/open-versus-closed-cautionary-tale.html&#34;&gt;Or read Murat&amp;rsquo;s summary&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;The authors list eight insightful principles about the behavior of closed and open systems. The very first is, &amp;ldquo;For a given load, mean response times are significantly lower in closed systems than in open systems.&amp;rdquo; In other words, TPC is lying about cloud database performance.&lt;/p&gt;
&lt;p&gt;This paper should&amp;rsquo;ve set the world on fire, but years passed and we all kept creating and using closed loop benchmarks. &lt;a href=&#34;https://dl.acm.org/doi/10.1145/1807128.1807152&#34;&gt;The Yahoo! Cloud Serving Benchmark was released in 2010&lt;/a&gt;. It was a great innovation because, unlike the TPC family, it targeted the new generation of distributed key-value stores. Unfortunately, you guessed it: YCSB is a closed loop benchmark.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://qconsf.com/sf2012/dl/qcon-sanfran-2012/slides/GilTene_HowNotToMeasureLatency.pdf&#34;&gt;Gil Tene coined the term &amp;ldquo;coordinated omission&amp;rdquo; around 2012&lt;/a&gt;. It describes how closed loop benchmarks interact with the systems they&amp;rsquo;re testing to avoid measuring their behavior during overload. He said it&amp;rsquo;s &amp;ldquo;a conspiracy we&amp;rsquo;re all a part of.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In 2017, some University of Hamburg researchers published &lt;a href=&#34;https://dl.gi.de/items/780bef9a-d6df-4776-80e3-e85ae0158e63&#34;&gt;Coordinated Omission in NoSQL Database Benchmarking&lt;/a&gt;, criticizing YCSB and introducing their own open loop benchmark, NoSQLMark. They point out that YCSB tried to fix the problem in 2015, but the fix is a flawed hack that doesn&amp;rsquo;t really make YCSB a reliable benchmark. (See the paper for details, or just trust me—it&amp;rsquo;s a flawed hack.) The authors&amp;rsquo; NoSQLMark does the obvious right thing: it uses an async framework, Akka. Requests&amp;rsquo; start times are independent of their latency, just like in the real world.&lt;/p&gt;
&lt;p&gt;The NoSQLMark paper uses a slightly weird methodology: they try to demonstrate their benchmark&amp;rsquo;s accuracy using a partially simulated database called SickStore, which doesn&amp;rsquo;t fully prove NoSQLMark&amp;rsquo;s value in my opinion. But they also benchmark Cassandra with both YCSB and NoSQLMark and show how the results differ. This is &lt;em&gt;actual science&lt;/em&gt; and we should all emulate it.&lt;/p&gt;
&lt;p&gt;In 2024—look, I know you&amp;rsquo;re getting sad and tired. I&amp;rsquo;m sorry. I&amp;rsquo;m getting sad and tired too. We&amp;rsquo;re almost done. &lt;a href=&#34;https://dl.acm.org/doi/10.1145/3662165.3662765&#34;&gt;In 2024 some researchers announced KVBench&lt;/a&gt;. This solved one problem with YCSB: it doesn&amp;rsquo;t generate diverse workloads, like a mix of point and range queries, queries with empty results, or deletes. KVBench is a good contribution, but once again, it&amp;rsquo;s a closed loop. It can&amp;rsquo;t push a database past its limits.&lt;/p&gt;
&lt;p&gt;Aside from research papers, some widely read bloggers have warned about coordinated omission. &lt;a href=&#34;https://www.scylladb.com/2021/04/22/on-coordinated-omission/&#34;&gt;Ivan Prisyazhynyy wrote about it in 2021 on the ScyllaDB blog&lt;/a&gt;. In 2023, my colleague Murat Demirbas &lt;a href=&#34;https://muratbuffalo.blogspot.com/2023/05/open-versus-closed-cautionary-tale.html&#34;&gt;reviewed the Cautionary Tale paper&lt;/a&gt;. This year, &lt;a href=&#34;https://brooker.co.za/blog/2025/05/20/icpe.html&#34;&gt;Marc Brooker wrote&lt;/a&gt; that &amp;ldquo;most cloud systems are open, most benchmarks are closed.&amp;rdquo; He says that closed loop benchmarks are far too kind to systems: they reduce their load automatically when the system slows down. &amp;ldquo;The real world isn’t that kind to systems. In most cases, if you slow down, you just have more work to be done later.&amp;rdquo;&lt;/p&gt;
&lt;h1&gt;It&amp;rsquo;s Time To Take A Stand&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/ycsb-is-obsolete/joan-of-arc.jpg&#34; alt=&#34;Engraving of a woman in armor, holding a sword and banner, leading an army&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Joan of Arc by Edward Corbould, 1890&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Why do we persist in using closed loop benchmarks, even though their inaccuracy is now well-understood, and open loop alternatives exist, and async frameworks make new ones easy to write?&lt;/p&gt;
&lt;p&gt;It reminds me of &lt;a href=&#34;https://www.goodreads.com/quotes/10244414-wooden-headedness-the-source-of-self-deception-is-a-factor-that-plays&#34;&gt;Barbara Tuchman&amp;rsquo;s diagnosis of self-defeating nations&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Wooden-headedness, the source of self-deception, is a factor that plays a remarkably large role in government. It consists in assessing a situation in terms of preconceived fixed notions while ignoring or rejecting any contrary signs. It is acting according to wish while not allowing oneself to be deflected by the facts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Part of the problem is when peer reviewers, out of habit, ask authors to use closed loop benchmarks. A few months ago I took a small risk. My colleagues and I had submitted our paper about &lt;a href=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/&#34;&gt;a Raft enhancement&lt;/a&gt; to a major conference, and one of the reviewers asked for some more benchmarks. They suggested YCSB or KVBench. We said &amp;ldquo;sure&amp;rdquo;—we wanted to publish the paper, and the reviewers have the final say. But then we reflected on these years of warnings about closed loop benchmarks, and we noticed that YCSB and KVBench are closed, and we changed our minds. I wrote a custom open loop benchmark in C++ using nonblocking I/O. When we submitted our revision, I explained why we didn&amp;rsquo;t follow the revision plan. The sky didn&amp;rsquo;t fall. Our revision was accepted, and it was better because it used an accurate benchmark.&lt;/p&gt;
&lt;p&gt;I understand why reviewers ask for YCSB or KVBench or TPC, despite those benchmarks&amp;rsquo; inaccuracy: they&amp;rsquo;re the only baseline we have. Researchers want to do science, and using established benchmarks is part of that. But established benchmarks don&amp;rsquo;t promote scientific progress if they&amp;rsquo;re wrong. The solution is to create new baselines with open loop benchmarks. There are a few good candidates. NoSQLMark is a natural successor to YCSB: it&amp;rsquo;s the same thing but open loop. &lt;a href=&#34;https://www.usenix.org/conference/atc19/presentation/kogias-lancet&#34;&gt;Lancet&lt;/a&gt; is an interesting research project to measure the open loop tail latency of very fast systems, using clever sampling techniques. And someone should make a standard open loop version of the TPC suite, if that doesn&amp;rsquo;t exist already.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start a movement. As authors let&amp;rsquo;s stop using closed-loop benchmarks, and as reviewers let&amp;rsquo;s stop asking for them. Let&amp;rsquo;s pledge that from now on, we will use open loop benchmarks to test cloud databases.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>LeaseGuard: Raft Leases Done Right!</title>
      <link>https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/</link>
      <pubDate>Thu, 18 Dec 2025 23:27:58 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/plate_21_19_27.jpeg&#34; alt=&#34;Black-and-white engraved plate from a historical horology treatise, showing multiple labeled diagrams of a complex mechanical clock or watch. The page is divided into numbered figures: circular clock dials with Roman numerals and concentric scales; exposed views of internal mechanisms with gears, pinions, levers, cams, and springs; and isolated components shown from different angles. Fine cross-hatching and lettered annotations identify parts. A French title at the bottom reads Horlogerie, indicating a clock with alarm, equation of time, concentric seconds, and displays for months and lunar quarters.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Many distributed systems have a &lt;em&gt;leader-based consensus protocol&lt;/em&gt; at their heart. The protocol elects one server as the &amp;ldquo;leader&amp;rdquo; who receives all writes. The other servers are &amp;ldquo;followers,&amp;rdquo; hot standbys who replicate the leader&amp;rsquo;s data changes. Paxos and Raft are the most famous leader-based consensus protocols.&lt;/p&gt;
&lt;p&gt;These protocols ensure consistent &lt;a href=&#34;https://en.wikipedia.org/wiki/State_machine_replication&#34;&gt;state machine replication&lt;/a&gt;, but reads are still tricky. Imagine a new leader &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; is elected, while the previous leader &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt; thinks it&amp;rsquo;s still in charge. A client might write to &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt;, then read stale data from &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt;, violating &lt;a href=&#34;https://jepsen.io/consistency/models/read-your-writes&#34;&gt;Read Your Writes&lt;/a&gt;. How can we prevent stale reads? &lt;a href=&#34;https://raft.github.io/raft.pdf&#34;&gt;The original Raft paper&lt;/a&gt; recommended that the leader communicate with a majority of followers before each read, to confirm it&amp;rsquo;s the real leader. This guarantees Read Your Writes but it&amp;rsquo;s slow and expensive.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;leader lease&lt;/em&gt; is an agreement among a majority of servers that one server will be the only leader for a certain time. This means the leader can run queries without communicating with the followers, and still ensure Read Your Writes. The original description of Raft included a lease protocol that was inherited from the earlier Paxos, where followers refuse to vote for a new leader until the old leader&amp;rsquo;s lease expires. This entangles leases and elections, and it delays recovery after a crash. Besides, lease protocols have never been specified in detail, for either Raft or Paxos. For all these reasons, many Raft implementations don&amp;rsquo;t use leases at all, or their &lt;a href=&#34;https://github.com/etcd-io/raft/issues/166&#34; style=&#34;text-decoration: underline&#34;&gt;leases&lt;/a&gt; &lt;a href=&#34;https://github.com/hashicorp/raft/issues/108&#34; style=&#34;text-decoration: underline&#34;&gt;are&lt;/a&gt; &lt;a href=&#34;https://aphyr.com/posts/316-jepsen-etcd-and-consul&#34; style=&#34;text-decoration: underline&#34;&gt;buggy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the MongoDB Distributed Systems Research Group, we designed a simple lease protocol tailored for Raft, called LeaseGuard. Our main innovation is to rely on Raft-specific guarantees to design a simpler lease protocol that recovers faster from a leader crash.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2512.15659&#34;&gt;Here&amp;rsquo;s a preprint of our SIGMOD ’26 paper&lt;/a&gt;. This is a joint blog post by &lt;a href=&#34;http://muratbuffalo.blogspot.com/&#34;&gt;Murat Demirbas&lt;/a&gt; and me, published on both of our blogs.&lt;/p&gt;
&lt;h2&gt;A huge simplification: the log is the lease&lt;/h2&gt;

&lt;p&gt;In Raft, before the leader executes a write command, it wraps the command in a &lt;em&gt;log entry&lt;/em&gt; which it appends to its &lt;em&gt;log&lt;/em&gt;. Followers replicate the entry by appending it to their logs. Once an entry is in a majority of servers&amp;rsquo; logs, it is &lt;em&gt;committed&lt;/em&gt;. Raft&amp;rsquo;s Leader Completeness property guarantees that any newly elected leader has all committed entries from previous leaders. Raft enforces this during elections: a server votes only for a candidate whose log is at least as up to date as its own. (Paxos doesn&amp;rsquo;t have this property, so a new leader has to fetch entries from followers before it&amp;rsquo;s fully functional.)&lt;/p&gt;
&lt;p&gt;When designing LeaseGuard, we used Leader Completeness to radically simplify the lease protocol. LeaseGuard does not use extra messages or variables for lease management, and does not interfere with voting or elections.&lt;/p&gt;
&lt;p&gt;In LeaseGuard, &lt;em&gt;the log is the lease&lt;/em&gt;. Committing a log entry grants the leader a lease that lasts until a timeout expires. While the lease is valid, the leader can serve consistent reads locally. Because of Leader Completeness, any future leader is guaranteed to have that same entry in its log. When a new leader &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; is elected, it checks its own log for the previous leader &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt;&amp;rsquo;s last entry, to infer how long to wait for &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt;&amp;rsquo;s lease to expire.&lt;/p&gt;
&lt;p&gt;In existing protocols, the log is not the lease: instead, the leader periodically sends a message to followers which says, &amp;ldquo;I still have the lease.&amp;rdquo; But imagine a leader who couldn&amp;rsquo;t execute writes or append to its log—perhaps it&amp;rsquo;s overloaded, or its disk is full or faulty—but still has enough juice to send lease-extension messages. This lame-duck leader could lock up the whole system. In LeaseGuard, a leader maintains its lease only if it can make progress; otherwise, the followers elect a new one.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re excited by the simplicity of this Raft-specific lease protocol. (We were inspired by some prior work, &lt;a href=&#34;https://groups.google.com/g/raft-dev/c/oO0NfgUVrjg/m/R1BnnbNcAwAJ&#34;&gt;especially this forum post from Archie Cobbs&lt;/a&gt;.) In LeaseGuard, there is no separate code path to establish the lease. We decouple leases from elections. The log is the single source of truth for both replication and leasing.&lt;/p&gt;
&lt;h2&gt;LeaseGuard makes leader failovers smoother and faster&lt;/h2&gt;

&lt;p&gt;Leases improve read consistency but can slow recovery after a leader crash. No matter how quickly the surviving servers elect a new leader, it has to wait for the old leader&amp;rsquo;s lease to expire before it can read or write. The system stalls as long as 10 seconds in one of the Raft implementations we studied.&lt;/p&gt;
&lt;p&gt;LeaseGuard improves the situation in two ways. First, &lt;em&gt;deferred-commit writes&lt;/em&gt;. As soon as a new leader wins election, it starts accepting writes and replicating them to followers. It just defers marking any writes &amp;ldquo;committed&amp;rdquo; until the old lease expires. Without this optimization, writes enqueue at the new leader until the old lease expires; then there&amp;rsquo;s a thundering herd. With our optimization, the new leader keeps up with the write load even while it&amp;rsquo;s waiting.&lt;/p&gt;
&lt;p&gt;Second, &lt;em&gt;inherited lease reads&lt;/em&gt;. This is our biggest innovation, and it&amp;rsquo;s a bit complicated. Consider the situation where &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; was just elected, but &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt; is alive and still thinks it&amp;rsquo;s in charge. Neither leader knows about the other. (Yes, this can &lt;a href=&#34;https://www.usenix.org/system/files/osdi18-alquraan.pdf&#34; style=&#34;text-decoration: underline&#34;&gt;really&lt;/a&gt; &lt;a href=&#34;https://aphyr.com/posts/288-the-network-is-reliable&#34; style=&#34;text-decoration: underline&#34;&gt;happen&lt;/a&gt; during a network partition.) Raft makes sure that &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt; can&amp;rsquo;t commit any more &lt;strong&gt;writes&lt;/strong&gt;, but there&amp;rsquo;s a danger of it serving stale &lt;strong&gt;reads&lt;/strong&gt;. The whole point of leases is to prevent that, by blocking &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; from reading and writing until &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt;&amp;rsquo;s lease expires. But what if there was a way for &lt;strong&gt;both&lt;/strong&gt; leaders to serve reads, and still guarantee Read Your Writes?&lt;/p&gt;
&lt;p&gt;When &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; was elected, it already had all of &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt;&amp;rsquo;s committed log entries (Leader Completeness), and maybe some newer entries from &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-64a393c37e7bfedd823eb6ea80da15120104840358bd7b5240e79f62dc62faee.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_0&#34;&gt;
&lt;/picture&gt; that aren&amp;rsquo;t committed yet. &lt;em&gt;&lt;strong&gt;L&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;1 knows it has every committed entry, but it doesn&amp;rsquo;t know which ones are committed!&lt;/strong&gt; We call these ambiguous entries the &lt;em&gt;limbo region&lt;/em&gt;. For each query, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; checks if the result is affected by any entries in the limbo region—if not, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/formula-aacfb5e5cd28e2b26b66022565bb49a81438761399e1337d841cc00d1fbe9610.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;L_1&#34;&gt;
&lt;/picture&gt; just runs the query normally. Otherwise, it waits until the ambiguity is resolved. (My MongoDB colleague Lingzhi Deng is the one who saw the need for this check, which is how he became a co-author.)&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/limbo-range-blog.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/limbo-range-blog.png&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;Logs on the old and new leader. Entries 1-5 were committed by &lt;em&gt;L&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; , and &lt;em&gt;L&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; has them all, but it only knows that 1-3 are committed. It may not learn whether 4-6 are committed until it tries to commit an entry of its own.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Inherited lease reads require synchronized clocks with known error bounds, but the rest of the protocol only needs &lt;a href=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/&#34;&gt;local timers with bounded drift&lt;/a&gt;. Our two optimizations preserve Read Your Writes and dramatically improve availability.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/availability.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/availability.png&#34; alt=&#34;A flowchart showing when leaders can read or write with leases. There are two leaders. At first, Leader 1 can execute reads and writes. Then Leader 2 is elected. Now Leader 1 can execute only reads, while Leader 2 can execute reads unaffected by the limbo region, and it can stage writes. Once Leader 1&amp;#39;s lease expires, Leader 2 can execute reads and writes freely.&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;Transitions in the read/write availability of leaders with LeaseGuard. While the new leader waits for a lease, it can serve some consistent reads and accept writes. Meanwhile the old leader serves reads.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the whole algorithm in pseudo-Python. For more details, read the paper.&lt;/p&gt;
&lt;style&gt;
.leaseguard-comment { color:#99A9A5 }
.leaseguard-keyword { color:#307919 }
.leaseguard-variable { color:#0000F5 }
.leaseguard-string { color:#AE3832 }
.leaseguard-infix { color:#9B2DF5 }
&lt;/style&gt;
&lt;pre&gt;
&lt;code&gt;
&lt;span class=&#34;leaseguard-comment&#34;&gt;# Handle a write request from a client.&lt;/span&gt;
&lt;span class=&#34;leaseguard-keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;leaseguard-variable&#34;&gt;ClientWrite&lt;/span&gt;(command):
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; self.state != &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;leader&amp;quot;&lt;/span&gt;: &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;not leader&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# Create new entry, log it and record its index.&lt;/span&gt;
    entry = (self.term, command, intervalNow())
    index = self.log.append(entry)
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# Another thread replicates, commits, and applies the&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# command, and advances lastApplied, see CommitEntry.&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;await&lt;/span&gt;(self.lastApplied &amp;gt;= index)
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; self.state != &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;leader&amp;quot;&lt;/span&gt;:
        &lt;span class=&#34;leaseguard-comment&#34;&gt;# Deposed, don&#39;t know if command succeeded.&lt;/span&gt;
        &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;not leader&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;


&lt;span class=&#34;leaseguard-comment&#34;&gt;# Handle a read request from a client for key k.&lt;/span&gt;
&lt;span class=&#34;leaseguard-keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;leaseguard-variable&#34;&gt;ClientRead&lt;/span&gt;(k):
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; self.state != &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;leader&amp;quot;&lt;/span&gt;: &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;not leader&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# Last committed entry&#39;s age is calculated using&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# bounded-uncertainty clock.&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; self.log[self.commitIndex].age &amp;gt; delta:
        &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;no lease&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# Prevent &amp;quot;limbo&amp;quot; reads.&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; self.term != self.log[self.commitIndex].term:
        &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; any limbo region entry affects k:
            &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;key affected by limbo region&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt; self.data[k]


&lt;span class=&#34;leaseguard-comment&#34;&gt;# When this node learns some followers have replicated&lt;/span&gt;
&lt;span class=&#34;leaseguard-comment&#34;&gt;# entries up to index i, advance the commitIndex.&lt;/span&gt;
&lt;span class=&#34;leaseguard-keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;leaseguard-variable&#34;&gt;CommitEntry&lt;/span&gt;(i):
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; self.state != &lt;span class=&#34;leaseguard-string&#34;&gt;&amp;quot;leader&amp;quot;&lt;/span&gt;: &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;leaseguard-infix&#34;&gt;not&lt;/span&gt; majorityAcknowledged(self.log[i]):
        &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# Check for past-term entry &amp;lt; Delta old.&lt;/span&gt;
    &lt;span class=&#34;leaseguard-comment&#34;&gt;# In reality this loop is optimized away, Sec. 7.&lt;/span&gt;
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;for&lt;/span&gt; e &lt;span class=&#34;leaseguard-infix&#34;&gt;in&lt;/span&gt; self.log:
        &lt;span class=&#34;leaseguard-keyword&#34;&gt;if&lt;/span&gt; e.term &amp;lt; self.term &lt;span class=&#34;leaseguard-keyword&#34;&gt;and&lt;/span&gt; e.age &amp;lt; delta:
            &lt;span class=&#34;leaseguard-keyword&#34;&gt;return&lt;/span&gt;
    self.commitIndex = max(self.commitIndex, i)
    &lt;span class=&#34;leaseguard-keyword&#34;&gt;while&lt;/span&gt; self.lastApplied&amp;lt;self.commitIndex:
        apply(self.log[self.lastApplied+1].command)
        self.lastApplied += 1
&lt;/code&gt;
&lt;/pre&gt;
&lt;h2&gt;Tests and benchmarks&lt;/h2&gt;

&lt;p&gt;When we started this research, our main goal was to publish a detailed and correct specification, so Raft implementers everywhere could implement leases without bugs. We&amp;rsquo;re &lt;a href=&#34;https://www.mongodb.com/company/blog/engineering/conformance-checking-at-mongodb-testing-our-code-matches-our-tla-specs&#34;&gt;TLA+ fans&lt;/a&gt; so obviously we &lt;a href=&#34;https://github.com/muratdem/RaftLeaderLeases/blob/main/TLA/leaseGuard.tla&#34;&gt;specified the algorithm in TLA+&lt;/a&gt; and checked it guaranteed Read Your Writes and other correctness properties. We discovered our two optimizations while writing the TLA+ spec. The inherited lease reads optimization was especially surprising to us; we probably wouldn&amp;rsquo;t have realized it was possible if TLA+ wasn&amp;rsquo;t helping us think.&lt;/p&gt;
&lt;p&gt;We implemented the algorithm &lt;a href=&#34;https://github.com/mongodb-labs/logcabin/tree/leaseguard&#34;&gt;in LogCabin&lt;/a&gt;, the C++ reference implementation of Raft. (For ease of exposition, we also provide an implementation in &lt;a href=&#34;https://github.com/muratdem/RaftLeaderLeases/tree/main/Python&#34;&gt;a Python simulator&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;In the following experiment, we illustrate how LeaseGuard improves throughput and reduces time to recovery. We crash the leader 500 ms after the test begins. At the 1000 ms mark, a new leader is elected, and at 1500 ms, the old leader&amp;rsquo;s lease expires. We ran this experiment with LogCabin in five configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent:&lt;/strong&gt; LogCabin running fast and loose, with no guarantee of Read Your Writes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quorum:&lt;/strong&gt; The default Read Your Writes mechanism, where the leader talks to a majority of followers before running each query, is miserably slow—notice that its Y axis is &lt;strong&gt;one tenth&lt;/strong&gt; as high as the other charts!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lease:&lt;/strong&gt; The &amp;ldquo;log is the lease&amp;rdquo; protocol with no optimizations. Its throughput is as high as &amp;ldquo;inconsistent&amp;rdquo;, but it has a long time to recovery after the old leader crashes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Defer commit:&lt;/strong&gt; The log is the lease, plus our write optimization—you can see that write throughput spikes off the chart at 1500 ms, because the leader has been processing writes while waiting for the lease. As soon as it gets the lease, it commits all the writes at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inherit lease:&lt;/strong&gt; LeaseGuard with all our optimizations. Read throughput recovers as soon as a new leader is elected, without waiting for the old lease to expire.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/unavailability_experiment_logcabin.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/leaseguard-raft-leader-leases-done-right/unavailability_experiment_logcabin.png&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;How fast does the system recover after a leader crash?&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Until now, the absence of a detailed specification for Raft leases led to many flawed implementations: they often failed to guarantee consistent reads, had very low read throughput, or recovered slowly from a leader crash. With LeaseGuard now specified, implemented, and published, we hope it will be readily adopted to enable Raft systems to provide fast reads with strong consistency and recover quickly after a crash.&lt;/p&gt;
&lt;p&gt;We learned yet again the value of TLA+ during this project. TLA+ is useful not just for checking the correctness of a completed design, but for revealing new insights while the design is in progress. Also, we got interested in &lt;a href=&#34;https://emptysqua.re/blog/series/knowledge/&#34;&gt;reasoning about knowledge&lt;/a&gt;, also known as epistemic logic. In Raft, servers can look in their logs and know that other servers know certain facts. For example, if a leader has a committed entry, it knows any future leader knows about this entry, but it doesn&amp;rsquo;t know if a future leader knows the entry was &lt;em&gt;committed&lt;/em&gt;. This is a different way for us to think about a distributed system: it&amp;rsquo;s not just a state machine, it&amp;rsquo;s a group of agents with limited knowledge. We&amp;rsquo;re curious about this way of thinking and plan to do more research.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://www.encyclopediaofdiderot.org/s/diderot/item/233773&#34;&gt;Image: &lt;em&gt;Horlogerie&lt;/em&gt;, 1765, Diderot and d&amp;rsquo;Alembert.&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Work of Art in the Age of Mechanical Creation</title>
      <link>https://emptysqua.re/blog/work-of-art-mechanical-creativity/</link>
      <pubDate>Wed, 10 Dec 2025 18:55:37 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/work-of-art-mechanical-creativity/</guid>
      
      
      
      
      <description>&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/work-of-art-mechanical-creativity/valery.jpg&#34; alt=&#34;Painting of Paul Valery in a suit resting his chin in his hand, perhaps speaking or thinking&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Portrait of Paul Valery by Jacques-Emile Blanche&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Back in 1928, Paul Valéry predicted streaming media:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It will be possible to send anywhere or to re-create anywhere a system of sensations, or more precisely a system of stimuli, provoked by some object or event in any given place. Works of art will acquire a kind of ubiquity. We shall only have to summon them and there they will be, either in their living actuality or restored from the past. They will not merely exist in themselves but will exist wherever someone with a certain apparatus happens to be. A work of art will cease to be anything more than a kind of source or point of origin whose benefit will be available and quite fully so, wherever we wish. Just as water, gas, and electricity are brought into our houses from far off to satisfy our needs in response to a minimal effort, so we shall be supplied with visual or auditory images, which will appear and disappear at a simple movement of the hand, hardly more than a sign. Just as we are accustomed, if not enslaved, to the various forms of energy that pour into our homes, we shall find it perfectly natural to receive the ultrarapid variations or oscillations that our sense organs gather in and integrate to form all we know. I do not know whether a philosopher has ever dreamed of a company engaged in the home delivery of Sensory Reality.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://science.miketyka.com//make/2015/09/12/the-conquest-of-ubiquity.html&#34;&gt;Paul Valéry, The Conquest of Ubiquity (La conquête de l&amp;rsquo;ubiquité)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Walter Benjamin followed up in 1935, wondering how our relationship with art will change once it&amp;rsquo;s so easily copied. In &lt;a href=&#34;https://web.mit.edu/allanmc/www/benjamin.pdf&#34;&gt;The Work of Art in the Age of Mechanical Reproduction&lt;/a&gt;, he analyzed what had made a work of art special in the past, and how that was changing in the 20&lt;sup&gt;th&lt;/sup&gt; century, and he forecast that original art objects would lose their value. The essay is world-famous, but his forecast was wrong: originals are now more valued than ever. Now that we&amp;rsquo;re approaching the age of mechanical creativity, I&amp;rsquo;ll try to learn from Benjamin&amp;rsquo;s error and make the opposite kind of forecast: I predict that AI art will make human art even more precious.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/work-of-art-mechanical-creativity/benjamin.jpg&#34; alt=&#34;Black and white portrait photo of Walter Benjamin, with a moustache and round glasses&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Walter Benjamin&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;What&amp;rsquo;s special about an original painting or sculpture? Benjamin says it is the object&amp;rsquo;s &amp;ldquo;aura&amp;rdquo;, which he defines in a few ways. The aura is &amp;ldquo;the unique phenomenon of a distance, however close it may be.&amp;rdquo; That is, you cannot possess or control the object, it is distant and &amp;ldquo;unapproachable.&amp;rdquo; Its aura is also its &lt;em&gt;authenticity&lt;/em&gt;; Benjamin says, &amp;ldquo;The authenticity of a thing is the essence of all that is transmissible from its beginning, ranging from its substantive duration to its testimony to the history which it has experienced.&amp;rdquo; Maybe this is easier to grasp in the original German? I&amp;rsquo;d redefine aura as, &amp;ldquo;The special value we assign to an art object because of its unique identity.&amp;rdquo; The key is recognizing that &lt;em&gt;we&lt;/em&gt; assign this special value. A perfect copy of the Mona Lisa produced by a Star Trek replicator, even if it were indistinguishable by carbon dating or electron microscopy, would still be a copy, valued differently from the original, only because we &lt;em&gt;know&lt;/em&gt; which is which. If someone secretly swapped them, the aura would cling to the one we thought was original.&lt;/p&gt;
&lt;p&gt;Benjamin says art objects first had aura because of their magical or religious meaning. I imagine Stonehenge or a statue of Athena: they are not valued for their beauty or originality, but their power. Copying was just as hard as producing an original, so the terms &amp;ldquo;original&amp;rdquo; and &amp;ldquo;copy&amp;rdquo; didn&amp;rsquo;t apply. &lt;em&gt;All&lt;/em&gt; statues of Athena were sacred. In the secularizing Renaissance, beauty took precedence. Once the Industrial Revolution began, the Romantics responded by heroizing individual artistic creativity. As Benjamin says,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip; art sensed the approaching crisis which has become evident a century later. At the time, art reacted with the doctrine of &lt;em&gt;l&amp;rsquo;art pour l&amp;rsquo;art&lt;/em&gt;, that is, with a theology of art. This gave rise to what might be called a negative theology in the form of the idea of &amp;lsquo;pure&amp;rsquo; art &amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Calling it a &amp;ldquo;theology&amp;rdquo; is right: the Romantics made a real cult of &amp;ldquo;art for art&amp;rsquo;s sake&amp;rdquo; and the artistic genius. But Benjamin skips over the Romantics. He dismisses creativity and genius in a sentence as &amp;ldquo;outmoded concepts.&amp;rdquo; He thinks that the art object&amp;rsquo;s function has evolved from ritual, to exhibition, and then to politics. He&amp;rsquo;s a Marxist, after all, and he both hopes and predicts that art&amp;rsquo;s main function in the future will be making revolutionary demands. Meanwhile, he predicts the aura of the original art object will fade. &amp;ldquo;The situations into which the product of mechanical reproduction can be brought may not touch the actual work of art, yet the quality of its presence is always depreciated.&amp;rdquo; And, &amp;ldquo;that which withers in the age of mechanical reproduction is the aura of the work of art.&amp;rdquo;&lt;/p&gt;
&lt;hr&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/work-of-art-mechanical-creativity/louvre.jpg&#34; alt=&#34;Color photo of a crowd at the Louvre pressing close to the Mona Lisa, which is displayed with a whole wall to itself. Most people in the crowd are holding up phones or cameras to photograph the painting. A security guard stands directly in front of the painting, keeping the crowd back.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:A_petit_crowd_to_see_the_dame.jpg&#34;&gt;The depreciated actual work of art?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Walter Benjamin was wrong. The Mona Lisa has to be one of the most reproduced artworks, but the quality of its presence has not depreciated a bit. Every year more pilgrims go to be in its presence. It&amp;rsquo;s &lt;em&gt;because&lt;/em&gt; we can all see high-resolution color copies that the original became a cult object. It&amp;rsquo;s why we ritually gather around it, take selfies with it, make even more copies with our phones and cameras as an act of worship. Benjamin defines aura as &amp;ldquo;distance,&amp;rdquo; and the original Mona Lisa has definitely become more distant: unapproachable, roped off, guarded. Owning it is impossible to imagine—&lt;a href=&#34;https://en.wikipedia.org/wiki/Salvator_Mundi_(Leonardo)&#34;&gt;even a mediocre Leonardo sold for $450 million&lt;/a&gt;. In general, the more famous an object is, the more it is reproduced—fame and reproduction are nearly synonymous on the Internet—and the stronger its aura.&lt;/p&gt;
&lt;p&gt;Maybe if Benjamin were alive today, he&amp;rsquo;d say what persists isn&amp;rsquo;t the &lt;em&gt;real&lt;/em&gt; aura that art objects used to have; it&amp;rsquo;s just fetishism or greed. Yes, things change. The modern world is secular and scientific: I will never know what an ancient Greek felt in front of an Athena statue. And I&amp;rsquo;ll never know what it was like to see the Mona Lisa in person for the first time, before the world was tiled with copies of her. Today&amp;rsquo;s aura is different from before. But even though things change, the special value we assign to unique art objects keeps growing! More and more tourists travel to crowd around famous things, the price of famous artworks spirals upwards.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The value of an original painting or sculpture is a little beside the point, anyway. Some art forms don&amp;rsquo;t create unique objects: music, literature, photography, and movies are all exhibited by copying. Yes, there might be an original score handwritten by a composer, or an original negative shot by a photographer. They have &lt;em&gt;some&lt;/em&gt; aura, and that&amp;rsquo;s what makes them collectors&amp;rsquo; items, but they&amp;rsquo;re not the primary artwork. Digital photography and literature written in Microsoft Word produce no artifacts at all. For a moment, some people tried to recreate the aura of the artifact with NFTs. The moment passed, thank god, but it&amp;rsquo;s a symptom of our human need to assign aura &lt;em&gt;somewhere&lt;/em&gt;. It turned out NFTs were unnecessary: These non-physical art forms still have aura; the aura just migrated from objects to creators.&lt;/p&gt;
&lt;p&gt;The cult of the creative genius seemed to be the main source of aura in the 20&lt;sup&gt;th&lt;/sup&gt; century, and continues to be today. This is another way Benjamin&amp;rsquo;s forecast swerved off course. The more original and innovative the artist is, and the more they seem &amp;ldquo;pure&amp;rdquo; and uninfluenced by fame, money, or convention, the more we value their work. Think of how Picasso or Hilma af Klint are lionized, or the auteur directors of American films in the 1970s, or literary cult figures like Emily Dickinson and David Foster Wallace. &amp;ldquo;Authentic&amp;rdquo; and original musicians like Bob Dylan are &lt;a href=&#34;https://www.nytimes.com/2020/12/07/arts/music/bob-dylan-universal-music.html&#34; style=&#34;text-decoration:underline&#34;&gt;celebrated&lt;/a&gt; &lt;a href=&#34;https://www.pulitzer.org/winners/bob-dylan&#34; style=&#34;text-decoration:underline&#34;&gt;ad&lt;/a&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/A_Complete_Unknown&#34; style=&#34;text-decoration:underline&#34;&gt;nauseam&lt;/a&gt;. (Enough already, let&amp;rsquo;s worship Björk now.) Art&amp;rsquo;s aura is undiminished compared to the past—its &lt;em&gt;source&lt;/em&gt; just changed, from religion or beauty to admiration of the artist.&lt;/p&gt;
&lt;p&gt;(Aside: The Romantics thought of creativity as spontaneous, inspired only by the artist&amp;rsquo;s interior passion, like something from nothing. This made no sense—an artist is always determined by their environment and tradition, even if they&amp;rsquo;re rebelling against it. &amp;ldquo;Originality&amp;rdquo; is just free will, constrained to art, and it&amp;rsquo;s as nonsensical as free will in general. I think art critics now scoff at the idea of artistic originality—Dadaism wounded it and Postmodernism finished it off—but free will is still &lt;a href=&#34;https://survey2020.philpeople.org/survey/results/4838&#34;&gt;overwhelmingly popular among philosophers&lt;/a&gt;, which baffles me. Anyway, we don&amp;rsquo;t need to believe humans are capable of originality, or that AI isn&amp;rsquo;t—we can assign special value to human art regardless.)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;What does it matter if Walter Benjamin misforecast? Yes, like most Marxists, he was too focused on economic competition among classes, he underestimated people&amp;rsquo;s attachment to ideas and rituals. It turns out people really want to treat some artworks as special, and high-fidelity copying only intensified this. We doubled down on valuing objects and artists, the opposite of what Benjamin expected. So what?&lt;/p&gt;
&lt;p&gt;Well, now we&amp;rsquo;re entering &lt;strong&gt;the age of mechanical creation&lt;/strong&gt;. We can see on the horizon AIs with an artistic creativity that matches our own. What forecast should we make about the value of human creativity in the future?&lt;/p&gt;
&lt;p&gt;First, I have to regain your trust by showing I hate AI art as much as you do. Most of the AI art I see now is repulsive, because the AIs&amp;rsquo; default &amp;ldquo;house styles&amp;rdquo; are tacky. If you give it a simple prompt and accept the first result, Dall-E usually makes excessively detailed airbrushed images with golden sidelight, and Gemini makes earth-toned kids&amp;rsquo; book illustrations.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/work-of-art-mechanical-creativity/ai.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Left: art from the Dall-E home page. Right: my first Gemini image.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Even &lt;a href=&#34;https://ai-arts.org/3rd-ai-arts-competition-results/&#34;&gt;prize-winning AI art&lt;/a&gt;, made by experienced prompters trying their hardest, is nauseating. I hold the humans responsible: the prompters have bad taste and so do the judges.&lt;/p&gt;
&lt;p&gt;Skilled and tasteful prompters &lt;em&gt;can&lt;/em&gt; produce competent art that doesn&amp;rsquo;t look like AI. They make images in a variety of styles, &lt;a href=&#34;https://www.astralcodexten.com/p/how-did-you-do-on-the-ai-art-turing&#34;&gt;art that fools laypeople in head-to-head contests with human art&lt;/a&gt;. I liked some of these images, &lt;em&gt;until&lt;/em&gt; I found out they&amp;rsquo;re AI. So what&amp;rsquo;s that about? Why do I sometimes enjoy an image, then retch when I learn I was fooled? Why do I refuse to use image generators myself? Why do I instead spend hours searching for vintage &lt;a href=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/&#34; style=&#34;text-decoration:underline&#34;&gt;images&lt;/a&gt; &lt;a href=&#34;https://emptysqua.re/blog/how-long-must-i-test/&#34; style=&#34;text-decoration:underline&#34;&gt;to&lt;/a&gt; &lt;a href=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/&#34; style=&#34;text-decoration:underline&#34;&gt;illustrate&lt;/a&gt; &lt;a href=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/&#34; style=&#34;text-decoration:underline&#34;&gt;my&lt;/a&gt; &lt;a href=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/&#34; style=&#34;text-decoration:underline&#34;&gt;blog&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s because the &lt;em&gt;aura&lt;/em&gt; of human creation is powerful. I care whether an image is created by a human, a person with a living body and a history, someone doomed to die, with limited skills and a specific intention. I care about this, even though AI art is sometimes indistinguishable to me, and I expect to be fooled much more. In fact, I think there will come an AI artist with all the creativity and intention of a Hilma af Klint: there will be a self-conscious AI artist who conceives and executes a body of work that expresses the AI&amp;rsquo;s unique experience, unprompted by a human. I might appreciate that art, knowing how it was made. But for no reason inherent in the art itself, I will always distinguish between human and mechanical creation.&lt;/p&gt;
&lt;p&gt;So here&amp;rsquo;s my prediction: just like we still value original art objects, no matter how faithfully they can be copied, we will also value human creativity, no matter how creative AI becomes. Once we can&amp;rsquo;t tell the difference, we&amp;rsquo;ll make systems for tracking the provenance of human creations, just like we do for objects. We&amp;rsquo;ll sometimes fall for AI forgeries, and sometimes catch them. But we&amp;rsquo;ll always assign special value to art we believe was human-created. Aura is not a property of objects or of creations, it&amp;rsquo;s something we give them to fulfill our own need. Whenever aura is undermined by technological change, it doesn&amp;rsquo;t wither. We find a way to create it again.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How I Write a Related Work Section with Zotero and Overleaf</title>
      <link>https://emptysqua.re/blog/writing-related-work-section-zotero-overleaf/</link>
      <pubDate>Tue, 02 Dec 2025 18:56:30 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/writing-related-work-section-zotero-overleaf/</guid>
      
      
      
      
      <description>&lt;p&gt;This week I&amp;rsquo;m writing a Related Work section for a research paper in a field outside my specialty. Usually I write about distributed systems, and I already know most of the papers I need to cite on a specific topic. But now I&amp;rsquo;m targeting a software engineering conference, so I&amp;rsquo;m rolling all over the Internet like &lt;a href=&#34;https://en.wikipedia.org/wiki/Katamari&#34;&gt;Katamari&lt;/a&gt;, accumulating a big ball of citations that may eventually form a bibliography.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/writing-related-work-section-zotero-overleaf/katamari.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;ve learned about efficiently assembling a Related Work section for a new-to-me topic. I&amp;rsquo;ll show you how to use Overleaf and Zotero, and &amp;hellip; I&amp;rsquo;ll announce a &lt;strong&gt;new Zotero plugin&lt;/strong&gt; I built which makes my job easier, and perhaps yours.&lt;/p&gt;
&lt;h1&gt;Find Papers&lt;/h1&gt;

&lt;p&gt;LLMs in Deep Research mode make a good start—this is available with a subscription to Gemini, ChatGPT, etc. I started by uploading a draft of the paper I&amp;rsquo;d written so far. A prompt like this partly corrects the LLM&amp;rsquo;s bad instincts:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve uploaded a draft of a research paper I&amp;rsquo;m writing for [insert conference]. Please look for papers in peer-reviewed journals and conference proceedings on the subject of [describe the subject]. I&amp;rsquo;m looking for this because I want papers to cite in the &amp;ldquo;related work&amp;rdquo; section of this paper. Don&amp;rsquo;t tell me about the paper I uploaded, I wrote it, I already know about it. Just use it as an example of the topic I&amp;rsquo;m researching. I want you to find me research papers. They&amp;rsquo;re probably PDFs on the internet. It&amp;rsquo;s helpful to search scholar.google.com, dblp.org, or researchgate.net. If it&amp;rsquo;s not a research paper, or if I wrote it, don&amp;rsquo;t tell me about it. Find me research papers that I can cite in the research paper that I will write.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Maybe these instructions are needlessly grouchy; you can guess I&amp;rsquo;d had a few rounds of disappointing results before I wrote them.&lt;/p&gt;
&lt;p&gt;Besides using AI, I followed my own advice and plugged keywords into Google Scholar, Arxiv.org, DBLP.org, etc. You know which sites are best for you.&lt;/p&gt;
&lt;h1&gt;Collect Papers in Zotero&lt;/h1&gt;

&lt;p&gt;Once I had opened a hundred tabs, I skimmed the papers&amp;rsquo; abstracts and imported the promising ones into a &lt;a href=&#34;https://www.zotero.org/&#34;&gt;Zotero&lt;/a&gt; collection with the &lt;a href=&#34;https://chromewebstore.google.com/detail/zotero-connector/ekhagklcjbdpajgpjgmbionohlpdbjgc?hl=en&#34;&gt;Zotero Connector for Chrome&lt;/a&gt;. Zotero has a lot of great features for assembling a bibliography. One of them is, papers&amp;rsquo; citation numbers are clickable:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/writing-related-work-section-zotero-overleaf/zotero-citation.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This makes it easy to open a paper, skim its intro, and find its citations to earlier, more foundational papers. Eventually you find the ur-paper (or some survey paper) which is an authoritative citation on some topic. I wish Zotero also helped you find and download cited papers, but this is easy enough to do myself. (Unless you can tell me about a plugin that does this, or build one for me?)&lt;/p&gt;
&lt;h1&gt;Fill In Missing Metadata&lt;/h1&gt;

&lt;p&gt;The Zotero Connector for Chrome usually fills in a paper&amp;rsquo;s metadata—authors, date, publication, and so on—when I import a paper from the web. But sometimes a paper lands in Zotero with bad or missing info, if I downloaded it from an obscure site. This means I can&amp;rsquo;t generate a bibliographical reference for it. To my surprise, I didn&amp;rsquo;t find an automated solution, so I built &lt;a href=&#34;https://github.com/ajdavis/zotero-metadata-search-plugin&#34;&gt;a Zotero plugin over the weekend called Metadata Search&lt;/a&gt;. The plugin searches just CrossRef.org and DBLP.org for now (pull requests welcome), and shows a list of results.&lt;/p&gt;
&lt;p&gt;For example, here I have &lt;a href=&#34;https://www.usenix.org/conference/osdi22/presentation/huang-lexiang&#34;&gt;a paper&lt;/a&gt; in Zotero, with only a title and incomplete authors list. My plugin finds more info on DBLP.org:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/writing-related-work-section-zotero-overleaf/plugin-ui.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I check some boxes for the fields I want to update, and the plugin copies the values into Zotero.&lt;/p&gt;
&lt;h1&gt;Bibliography and Citations&lt;/h1&gt;

&lt;p&gt;Now, since I&amp;rsquo;m writing my paper with LaTeX, I need to generate a BibTeX bibliography file and cite particular entries in the text of my paper. There are two paths.&lt;/p&gt;
&lt;p&gt;One path is to install &lt;a href=&#34;https://retorque.re/zotero-better-bibtex/&#34;&gt;Better BibTeX for Zotero&lt;/a&gt;. This has two killer features. First, it creates &lt;em&gt;stable&lt;/em&gt; citation keys for papers. Even if I update the paper&amp;rsquo;s metadata (e.g. change its author or year), Better BibTeX ensures that the citation key doesn&amp;rsquo;t change. So if I first write a citation like &lt;code&gt;\cite{2020_davis}&lt;/code&gt;, and then discover the paper was published in 2021, I can fix the date without breaking the &lt;code&gt;cite&lt;/code&gt; directive. Its second killer feature is auto-export: I can tell it to export a Zotero collection as a BibTeX bibliography, and it will re-export to the same file each time the collection changes. This seems like a great labor-saving device for people writing LaTeX on their own machines.&lt;/p&gt;
&lt;p&gt;The second path is to write an online shared doc in &lt;a href=&#34;https://www.overleaf.com/&#34;&gt;Overleaf&lt;/a&gt;. This is what I do, because it allows Google Docs-style collaboration with co-authors. &lt;a href=&#34;https://docs.overleaf.com/integrations-and-add-ons/reference-manager-integrations/zotero&#34;&gt;Overleaf has a Zotero integration&lt;/a&gt; which dumps your whole Zotero catalog into the bibliography. If you have a huge catalog, like me, I recommend you create a Zotero &amp;ldquo;group library&amp;rdquo; and tell Overleaf to import only from there. Regardless, only the papers you actually cite will appear in the rendered PDF. You can refresh the Overleaf bibliography from Zotero whenever you want.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a demo of the whole workflow:&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/2iyI2yzp6nU?si=q4dFb3raf2AbKiyc&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen style=&#34;margin-bottom: 1em&#34;&gt;&lt;/iframe&gt;
</description>
    </item>
    
    <item>
      <title>This Senior Staff Engineer Vibe-Coded for the First Time, What Happened Next Will Shock You</title>
      <link>https://emptysqua.re/blog/first-time-vibecoding/</link>
      <pubDate>Wed, 19 Nov 2025 02:53:40 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/first-time-vibecoding/</guid>
      
      
      
      
      <description>&lt;p&gt;Actually it won&amp;rsquo;t shock you. Spoiler alert: Coding with AI is a skill. It&amp;rsquo;s annoying but efficient. My prior experience handwriting code helped.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/first-time-vibecoding/medieval.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;By Gemini, obviously. This is the first and only post I&amp;rsquo;ll ensloppify with AI &amp;ldquo;art.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I started programming seriously when I was in college, in the late 90s. Basic autocomplete in Microsoft Visual C++ was the state of the art back then. I spent the next couple decades meticulously laying out my code like a 16th-Century typesetter, crafting each line with an artisan&amp;rsquo;s pride.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://emptysqua.re/blog/after-244-days-off/&#34;&gt;When I switched from engineering to research&lt;/a&gt; I stopped coding regularly. Now I have spurts of programming when I prototype a new algorithm or benchmark it, and between spurts I go months without significant time in an IDE. Last year, IDEs didn&amp;rsquo;t change by the month. This year, each month is a revolution. I wanted to experience the new reality for myself. So, for two recent personal projects, I tried going all-in on AI coding.&lt;/p&gt;
&lt;p&gt;As I&amp;rsquo;ll describe below, my first attempt was frustrating because I didn&amp;rsquo;t embrace the vibe-coding lifestyle. I chose the wrong model, then micromanaged it, trying to make it write the code I would&amp;rsquo;ve written. The LLM implemented the wrong features, or wrote buggy code, or correct code that I thought was ugly. On my second attempt, I used the right model and treated it like an adult: I gave it high-level instructions and let it write all the code and run its own tests. I didn&amp;rsquo;t type a single character of code—I hardly even looked at it—and I had an enjoyable day building a useful app.&lt;/p&gt;
&lt;h1&gt;Act I: The Senior Staff Engineer is a Control Freak&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/first-time-vibecoding/office.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;Gemini doing a decent impression of the Blondie comic strip style.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For my first vibe-coding exercise, I turned a Python script into a web app and deployed it to the cloud. The script was something I&amp;rsquo;d developed over a few years to &lt;a href=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/&#34;&gt;optimize the schedule for a twice-annual Zen retreat&lt;/a&gt;. My fellow Zen students suggested I turn it into an application they could use without me. I thought this was a perfect use case for AI. I opened VS Code and accepted the default model, GPT-5 mini, and conversed with it about my needs.&lt;/p&gt;
&lt;p&gt;The model started off strong: it made useful suggestions for deploying to Google cloud. It proposed a mix of Google services that would save money and pay only for the resources I needed. It insisted I needed some complex web of Google accounts, roles, and auth tokens—I kept pushing back and asking it to simplify, but in the end it was right. I eventually followed all its instructions, after discovering there was no simpler alternative.&lt;/p&gt;
&lt;p&gt;When it came time to write code, GPT-5 mini wrote a good first web page and Flask backend. It was much quicker than my own work would&amp;rsquo;ve been, and the web page was nicely styled.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t care about the code style of the generated HTML, CSS, and JavaScript, but I&amp;rsquo;m a stickler for Python style and I made the mistake of reading its code. I hated it.&lt;/p&gt;
&lt;p&gt;GPT-5 mini really likes to write useless comments:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Return the square of x.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;square&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It also likes to write &amp;ldquo;defensive&amp;rdquo; code that only makes the program less predictable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Return the square of x.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;square&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;None&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#204a87&#34;&gt;print&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;x wasn&amp;#39;t set&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I griped in &lt;code&gt;copilot-instructions.md&lt;/code&gt; that it shouldn&amp;rsquo;t do this, but it seemed to have no effect. (Edit: that&amp;rsquo;s because VS Code agent instructions should be in &lt;code&gt;.github/instructions&lt;/code&gt;. When I first booted the agent it suggested that we create &lt;code&gt;copilot-instructions.md&lt;/code&gt; so I figured wrongly that it knew where its own instructions file belongs.) So I started including in my prompts, &amp;ldquo;Just write the code, no comments, no error-checking.&amp;rdquo; Hilariously, it then wrote comments like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;square&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Don&amp;#39;t check whether x is None, just let the exception be thrown.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is high-school-sophomore-style coding. Someone who&amp;rsquo;s worked on large codebases for years knows that comments are tech debt and should be minimized. They should be addressed to a reader in the far future, not to the most recent code reviewer. Defensive coding like &lt;code&gt;if x is None&lt;/code&gt; makes the code more complex, not more resilient: error-handling should be consolidated at a layer of the application that knows how to respond. I&amp;rsquo;m afraid that GPT has either seen too much sophomoric code from StackOverflow in its training set, or else OpenAI hired naïve coders to provide feedback, and they encouraged this sort of thing during &lt;a href=&#34;https://en.wikipedia.org/wiki/Reinforcement_learning_from_human_feedback&#34;&gt;RLHF&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I got tired of fighting with GPT-5 mini over style and started coding small Python changes myself. But when I was ready to deploy to the cloud I turned to it for help again. We got the job done faster together, but I wasn&amp;rsquo;t happy with my colleague. It hallucinated all sorts of command line options to the &lt;code&gt;gcloud&lt;/code&gt; CLI. I got myself stuck in an exasperating loop, where GPT suggested a command, I pasted it into the terminal, the command failed because of GPT&amp;rsquo;s mistakes, and I pasted the error back into GPT for diagnosis. I&amp;rsquo;d become the dumbest agent in a system of dumb agents.&lt;/p&gt;
&lt;h1&gt;Act II: The Senior Staff Engineer Learns to Chill Out and Enjoy the Vibes&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/first-time-vibecoding/beach.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;Gemini still doesn&amp;rsquo;t know how many arms humans have.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For my second attempt, I shamelessly embraced the vibe-coding lifestyle. I used AI to build an AI agent. The thing I wanted to build was an AI coach, who would look at my long-term goals (in some PDFs), my task list (using the Remember the Milk API), how I use my time (from the Toggl Track API), and what I&amp;rsquo;ve accomplished lately (in a text file where I track accomplishments). I&amp;rsquo;d read &lt;a href=&#34;https://fly.io/blog/everyone-write-an-agent/&#34;&gt;an inspiring article about building agents&lt;/a&gt; and thought, this looks surprisingly straightforward, I&amp;rsquo;ll make an agent do it for me.&lt;/p&gt;
&lt;p&gt;(Yes, I&amp;rsquo;m telling an AI to tell an AI to tell me what to do. A few months ago this was futuristic.)&lt;/p&gt;
&lt;p&gt;In the weeks since my previous session, Microsoft &lt;a href=&#34;https://code.visualstudio.com/updates/v1_106&#34;&gt;overhauled the coding agents in VS Code&lt;/a&gt;. Now there&amp;rsquo;s a clear separation between a planning agent and a coding agent. This made my second vibe-coding project go more smoothly. Plus, instead of accepting the default model, I chose Claude Sonnet 4.5, based on rumors that it&amp;rsquo;s good.&lt;/p&gt;
&lt;p&gt;This might get confusing, since there are two LLMs in this story, so from now on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude&lt;/strong&gt; is the LLM I used to write code for an &amp;ldquo;AI coach&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPT&lt;/strong&gt; is the LLM that the AI coach used as its backend.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I began with Claude in planning mode and described what I wanted: an agent written in Python, using the GPT API, plus various APIs to track my work. Its main task would be to suggest what I should work on for the next 45 minute &lt;a href=&#34;https://en.wikipedia.org/wiki/Pomodoro_Technique&#34;&gt;pomodoro&lt;/a&gt;. I wanted to chat with the agent in my terminal. Claude and I had a 20-minute conversation about my requirements. It asked me, should auth tokens be stored in the macOS keychain or a text file? How often should it refresh its data sources, such as Remember the Milk and Toggl? Whenever I start chatting with the coach, should it remember our past conversations? How should the coach prioritize tasks—due date only, or also priority, or how well they align with my long-term goals? Whenever I answered Claude&amp;rsquo;s questions, it asked me four or five more. After a few rounds of this, I clicked the button VS Code provided me, which says, &amp;ldquo;start implementing.&amp;rdquo; Claude stopped asking questions and started generating dozens of files. I don&amp;rsquo;t know if the &amp;ldquo;planning mode&amp;rdquo; agent would ever have stopped asking for more details, or how I should decide when we&amp;rsquo;ve talked enough.&lt;/p&gt;
&lt;p&gt;Claude worked for a few minutes autonomously. I occasionally had to click &amp;ldquo;ok&amp;rdquo; when it wanted to run a shell command or fetch a page of documentation from the web. The wait wasn&amp;rsquo;t long enough for me to work on something else with any focus, so I watched YouTube in 30-second slices between AI interactions. It seems helpful to have something mindless to do in these gaps—listen to music, watch climbing or cooking videos, practice tying knots&amp;hellip; Claude generated 30 Python files and 2800 lines of code, and spat out a summary of its work. It claimed it had built OAuth flows for RTM and Toggl, and a context-management system that stored recent messages in SQLite and asked GPT for summaries of older messages, to limit the size of the context window. I resisted the temptation to read the code: I just tried it out.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; python3 main.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✓ All services authenticated
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ℹ Refreshing all data sources...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✓ Data refresh complete
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;╔══════════════════════════════════════════════════════════════════╗
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;║              Professional Coaching Agent (GPT-5.1)              ║
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;╚══════════════════════════════════════════════════════════════════╝
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Type your questions or commands. Use /help for available commands.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;You: /help
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;┃ Command        ┃ Description                          ┃
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /refresh       │ Refresh all data sources             │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /refresh-rtm   │ Refresh Remember The Milk tasks only │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /refresh-pdfs  │ Refresh PDF documents only           │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /refresh-toggl │ Refresh Toggl Track entries only     │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /refresh-logs  │ Refresh accomplishments log only     │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /help          │ Show this help message               │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│ /quit          │ Exit the agent                       │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└────────────────┴──────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;You: what should i work on next?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Coach: Given what’s on your plate and that you’ve already spent a lot of time in “AI coding” and “driver testing paper”, I’d suggest one of these for your next 45-minute block:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**1. Review my current MongoDB development plan**
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Due: tomorrow (2025-11-19)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Why: Directly tied to your long-term goals (publishing pipeline, collaborations, synced clocks, etc.).
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- 45-minute plan:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - 10 min: Skim the dev plan PDF and your recent accomplishments.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - 25 min: Write/update concrete 2025–mid-2026 goals (papers, talks, collaborations) in a doc.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - 10 min: Turn those into 3–5 actionable RTM tasks (e.g., “Draft driver testing paper intro,” “Outline synced-clocks survey sections”).
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**2. Prep for “Meet about leases in new architecture”**
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Due: today
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Why: High-leverage research conversation; you just finished your leader leases paper, so consolidating your thinking is valuable.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- 45-minute plan:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - 15 min: Jot down what you want from the meeting (questions, decisions, next experiments).
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - 20 min: Review any notes on leases + new architecture, and your “Leases”/“Data movement” tasks.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - 10 min: Draft a short agenda and 2–3 concrete proposals to discuss.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;If you feel a bit fried from heavy cognitive work, a lighter option:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**3. Shut down lease benchmark EC2 instances (prep + checklist)**
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Due: 2025-11-30
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Use 45 minutes to:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - List all benchmark-related resources.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - Decide what to snapshot/keep.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - Draft the shutdown steps so the actual execution is quick later.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Which of these matches your current energy:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- (A) reflective/planning (dev plan),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- (B) focused technical prep (lease meeting), or
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- (C) lighter operational cleanup (benchmark EC2)?
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was an impressive debut. Most of the coach&amp;rsquo;s ideas were appropriate, and Claude&amp;rsquo;s code for reading the data sources and connecting to GPT all worked the first time.&lt;/p&gt;
&lt;p&gt;As I used the coach more, I found some bugs. E.g., Claude had only stubbed-out the code for &lt;em&gt;updating&lt;/em&gt; RTM tasks. If I asked the coach to &amp;ldquo;put off that meeting for 2 days,&amp;rdquo; it crashed. I was smarter now than in my previous attempt, though. Instead of copying error messages from the terminal to my chat with Claude, I used VS Code&amp;rsquo;s builtin terminal, so Claude could look at the terminal and see the error messages itself. I simply told Claude, &amp;ldquo;Look at the terminal and fix this bug,&amp;rdquo; and with little further intervention it found the stubbed-out code, looked up the RTM API docs, and implemented the missing feature. I maintained my disciplined laziness, never typing or even reading any code. It only took a few hours of testing the coach and asking Claude for bugfixes or changes, and the thing I&amp;rsquo;d envisioned was fully built.&lt;/p&gt;
&lt;h1&gt;Lessons learned&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Vibe-coding is a skill.&lt;/li&gt;
&lt;li&gt;Avoid reading and writing code??&lt;/li&gt;
&lt;li&gt;Claude Sonnet is far better than GPT-5 mini, obviously. In general, it&amp;rsquo;s important to learn models&amp;rsquo; differences.&lt;/li&gt;
&lt;li&gt;Try to remove yourself from the agentic loop: give the AI the tools and permissions it needs to test its code and diagnose its mistakes autonomously.&lt;/li&gt;
&lt;li&gt;Eventually, you might need to intervene, especially (I found) when the AI doesn&amp;rsquo;t know how to use a particular API and keeps hallucinating options. Fall back to searching Google and StackOverflow, like you did last year.&lt;/li&gt;
&lt;li&gt;I need a &lt;em&gt;very&lt;/em&gt; wide monitor. At my age even 27&amp;quot; isn&amp;rsquo;t enough to display the agent chat, file explorer, code, etc., all large enough for my eyes, so I&amp;rsquo;ve upgraded to an &lt;a href=&#34;https://www.nytimes.com/wirecutter/reviews/best-ultrawide-monitors/&#34;&gt;ultrawide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m reminded of a more abstract lesson, the one from William Gibson: &amp;ldquo;The future is already here, it&amp;rsquo;s just not evenly distributed.&amp;rdquo; Sometimes the new technology works as advertised! Sometimes it doesn&amp;rsquo;t. Revolutionary new time-saving technologies take practice to use well.&lt;/p&gt;
&lt;p&gt;Back in 2005, my mind was blown &lt;a href=&#34;https://www.youtube.com/watch?v=Gzj723LkRJY&#34;&gt;watching David Heinemeier Hansson implement a blogging engine in 15 minutes with Ruby on Rails&lt;/a&gt;. Until I saw the demo, I wouldn&amp;rsquo;t have believed such velocity was possible. Rails was so much more concise than anything before, and so was Ruby. And the code editor DHH used, TextMate, was so tricked out with templates and macros, it practically wrote the code for him. But when I tried Rails and TextMate myself, things didn&amp;rsquo;t go so well. The new technologies really were transformative—looking back, it&amp;rsquo;s clear that DHH was demonstrating the future of web development—but it took time for me and the world to catch up.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Aerial Silks in the Gunks, part 2</title>
      <link>https://emptysqua.re/blog/aerial-silks-2/</link>
      <pubDate>Tue, 07 Oct 2025 17:13:59 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/aerial-silks-2/</guid>
      
      
      
      
      <description>&lt;p&gt;October 6, 2025. &lt;a href=&#34;https://www.instagram.com/mntnbug/&#34;&gt;Kelsey Roman&lt;/a&gt; performing with aerial silks while hanging from the West Trapps cliff in the Gunks. We photographed during sunset and moonrise. Rock guide &lt;a href=&#34;https://www.advancedrockcraft.com/&#34;&gt;Dustin Portzline&lt;/a&gt; managed Kelsey&amp;rsquo;s safety rigging and filmed behind-the-scenes footage.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-2/silks-2-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;I photographed this with a flash while hanging from a static line. I used a &lt;a href=&#34;https://mistymountain.com/collections/cadillac&#34;&gt;big wall harness&lt;/a&gt; for safety, a &lt;a href=&#34;https://blackdiamondequipment.com/products/bosuns-chair&#34;&gt;bosun&amp;rsquo;s chair&lt;/a&gt; for comfort, and a &lt;a href=&#34;https://www.peakdesign.com/&#34;&gt;Peak Design&lt;/a&gt; thingy that&amp;rsquo;s now out of stock for clipping my camera to my harness.&lt;/p&gt;
&lt;iframe width=&#34;315&#34; height=&#34;560&#34;
src=&#34;https://www.youtube.com/embed/C07JD-6WC6Q&#34;
title=&#34;YouTube video player&#34;
frameborder=&#34;0&#34;
allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34;
allowfullscreen&gt;&lt;/iframe&gt;
</description>
    </item>
    
    <item>
      <title>Versions of The Eternaut</title>
      <link>https://emptysqua.re/blog/the-eternaut/</link>
      <pubDate>Tue, 07 Oct 2025 13:17:09 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/the-eternaut/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/eternaut-1950s.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;In late-1950s Buenos Aires, on a quiet night, Juan is in his attic, playing cards with his close friends. The radio plays in the background. Juan&amp;rsquo;s wife and daughter are downstairs. On their suburban street, snow begins to fall. The music on the radio turns to static. Out the window, Juan sees cars crashing, pedestrians dropping dead: the snow is glowing, toxic, everyone it touches dies in seconds. It&amp;rsquo;s the prelude to an alien invasion. Juan&amp;rsquo;s family and friends are lucky: the windows were all closed, and Juan&amp;rsquo;s modern house is tightly sealed. They fashion hazmat suits from spare parts and venture out to gather supplies for their survival.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/yard.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Over the next few days they&amp;rsquo;ll witness mass death, anarchy, waves of attacks by various weird aliens and crazy weapons, the destruction of the Argentine army, and perhaps the end of humanity. In the story&amp;rsquo;s final moments, Juan tries to steal an alien ship, but presses the wrong button and gets teleported to another dimension, doomed eternally to wander space and time, searching for his wife and daughter.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Eternaut&lt;/em&gt; (&lt;em&gt;El Eternauta&lt;/em&gt; in the original Spanish), written by Héctor Germán Oesterheld, was serialized in an Argentine sci-fi magazine from 1955 to 1957. It&amp;rsquo;s drawn by Francisco Solano López in a pulpy style, though with more artistic brushwork and composition.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/brushwork.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Oesterheld wrote the story in the aftermath of Juan Perón&amp;rsquo;s overthrow and &lt;a href=&#34;https://en.wikipedia.org/wiki/Bombing_of_Plaza_de_Mayo&#34;&gt;an airstrike against a pro-Perón rally in Buenos Aires&amp;rsquo;s central square&lt;/a&gt;, which killed hundreds of civilians. I don&amp;rsquo;t know much about Argentina&amp;rsquo;s history, and I&amp;rsquo;m confused about Oesterheld&amp;rsquo;s opinion of Perón and the coup. Everyone has to agree that the bombing of the Plaza de Mayo was an atrocity, I assume. But in The Eternaut, it&amp;rsquo;s the good guys who bomb a plaza, trying to destroy the alien headquarters.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/plaza.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The themes of The Eternaut seem universal enough to be reused by any political faction. On one side of the conflict is a classic 50s hero, a square-jawed family man, with his brave and competent friends, a physicist and a metalworker. On the other side, inhuman and implacable enemies who bombard Buenos Aires with toxic snow and lasers, then use mind-control devices to enslave the survivors. They could represent the military forces that bombed civilians and overthrew Perón, or the repressive regime that followed, or the foreign imperialists who tried to manipulate Argentine politics from afar, or any foe of middle-class liberal patriotism.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/constitution.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The comic was published in installments, and you can see the seams between them when reading it as a book: each episode begins with a bit of recap and ends on a cliffhanger. Too many times, a major character is presumed dead and reappears improbably. But the story is among the most gripping I&amp;rsquo;ve ever read. The alien assault is relentless, the velocity of events is frantic and disorienting, the body count is in the billions, and the only predictable plotline is that all human resistance will be crushed. The story&amp;rsquo;s end is abrupt and shockingly inconclusive. We never actually see the invaders, or know for certain why they attacked and what they will do with the handful of survivors, if there are any.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/river-plate.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Eternaut 1969&lt;/h1&gt;

&lt;p&gt;In the 60s and 70s, Oesterheld moved left. He wrote biographies of Che Guevara and Eva Perón, and a book titled &lt;em&gt;450 Years of War Against Imperialism&lt;/em&gt;, and sequels to the Eternaut, which I hope are translated to English someday. Meanwhile, Argentina descended into its worst era of dictatorship and violence, known as The Dirty War. Oesterheld and his family joined a rebel group. He was &amp;ldquo;disappeared&amp;rdquo; and presumably killed by the government around 1977.&lt;/p&gt;
&lt;p&gt;In 1969, Oesterheld rebooted the Eternaut with a new artist, Alberto Breccia, serialized in an Argentine gossip magazine. Breccia&amp;rsquo;s art is like a bad ayahuasca trip. Weird shapes and gloopy lines fill the background, the characters&amp;rsquo; faces are lost in murk, the action is often illegible.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/1969-4.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The content has darkened, too. In the 1950s, Oesterheld&amp;rsquo;s main characters are a plucky band of friends, and the Argentine army is led by stoic professionals. In 1969, everyone is paranoid. Juan observes that as soon as they rescue a young woman, the men see each other as rivals for her. The army is led by a brute who murders his lieutenant for dissenting.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/1969-3.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;In the original Eternaut, &amp;ldquo;the North&amp;rdquo; is a distant ally attacking the aliens with nuclear missiles. In the reboot, the northern powers have negotiated a separate peace with the aliens and abandoned the South, without even a warning.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/1969-1.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Eternaut 1969 alienated its audience. Oesterheld&amp;rsquo;s resentful politics and Breccia&amp;rsquo;s avant-garde art led to the series&amp;rsquo;s early cancellation. In the last few pages the comic rushes to its conclusion, summarizing the plot in a few paragraphs and some vague illustrations. The work is a sad failure, comprehensible or interesting only to an Eternaut completist.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/1969-2.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;The Eternaut on Netflix&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/netflix.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The original Eternaut and Eternaut 1969 were republished, including English translations, in &lt;a href=&#34;https://www.fantagraphics.com/collections/hector-german-oesterheld/products/the-eternaut&#34;&gt;2015&lt;/a&gt; and &lt;a href=&#34;https://www.fantagraphics.com/collections/hector-german-oesterheld/products/the-eternaut-1969&#34;&gt;2020&lt;/a&gt;. I think the Eternaut comics influenced Netflix&amp;rsquo;s superb TV adaptation of the novel Station 11: the Station 11 show includes images of people in improvised hazmat suits walking around snowy Chicago, and a knife fight with a hazmat-suited intruder, that seem more inspired by scenes in The Eternaut than by Emily St. John Mandel&amp;rsquo;s novel. Maybe Station 11&amp;rsquo;s success and the reissue of the comics led Netflix to greenlight a 2024 adaptation of The Eternaut. The first season aired last year, and another season is in production.&lt;/p&gt;
&lt;p&gt;Oesterheld&amp;rsquo;s heirs required that any adaptation must be in Spanish and set in Buenos Aires. Netflix complied, but moved the action from the 1950s to the present day. The show has the luscious visuals, &lt;a href=&#34;https://www.vox.com/culture/22840526/colors-movies-tv-gray-digital-color-sludge&#34;&gt;desaturated palette&lt;/a&gt;, stylish soundtrack, and tight plotting you&amp;rsquo;d expect from a prestige sci-fi series these days. It borrows and expands the ideas of the 1950s comic, combining them with the paranoid atmosphere of Eternaut 1969. Unfortunately, some of the original Eternaut&amp;rsquo;s attention to detail is lost. Oesterheld knew enough science to make the alien snow a consistent and believable phenomenon. His heroes, a band of can-do tinkerers, build various filters, respirators, and airlocks to survive. These are described with some specificity, in the Popular Mechanics spirit of the era. On Netflix, though, the snow seems to kill or spare people based on the writers&amp;rsquo; mood and the needs of the plot. Oesterheld&amp;rsquo;s snow blocks radio signals, neatly isolating the main characters and driving the plot, whereas on Netflix the snow is accompanied by some implausible &lt;a href=&#34;https://en.wikipedia.org/wiki/Electromagnetic_pulse&#34;&gt;super-EMP&lt;/a&gt; that not only knocks out the power grid but drains cell phone batteries specifically, sparing old cars and flashlights.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/group-netflix.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I&amp;rsquo;m not mad that Netflix is unfaithful to the comic, though. We&amp;rsquo;ve had seventy years to add ideas and overtones atop the foundation Oesterheld laid. In that time, Argentina passed through several coups, dictatorships, republics, another Perón presidency, and finally a stable democracy. Some of the genres that Oesterheld drew from, like alien invasion and post-apocalyptic survival, were new in the 1950s and are well-developed today. Perhaps most important, we&amp;rsquo;ve all recently lived through a pandemic. Like Juan when he first saw the snow, we didn&amp;rsquo;t know if the disaster was natural or a bioweapon. Like him, we were trapped inside until we jury-rigged masks so we could venture out and gather supplies. We personally saw the camaraderie and paranoia, the selfishness and courage that humanity displays in such a crisis. The Netflix writers mixed all these ingredients into a bigger, more complex story than Oesterheld&amp;rsquo;s original.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/the-eternaut/juan-netflix.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;</description>
    </item>
    
    <item>
      <title>Ridicullissima</title>
      <link>https://emptysqua.re/blog/chris-ridicullissima/</link>
      <pubDate>Mon, 29 Sep 2025 15:56:22 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/chris-ridicullissima/</guid>
      
      
      
      
      <description>&lt;p&gt;September 28, 2025. Chris climbing &lt;a href=&#34;https://www.mountainproject.com/route/105888125/ridicullissima&#34;&gt;Ridicullissima&lt;/a&gt;, a tall classic 5.10d in the Gunks. I photographed him while hanging from a rope anchored near the top of the route. At &lt;a href=&#34;https://www.advancedrockcraft.com/&#34;&gt;Dustin Portzline&lt;/a&gt;&amp;rsquo;s suggestion, I experimented with anchoring the other end of the rope to the ground, a hundred feet to the side of the climb. I attached myself to the rope with two Gri Gris and attempted to position myself in space by individually lengthening or shortening the two segments. It was awkward; I have a lot to learn. At least having two attachments prevented me from spinning.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/chris-ridicullissima/ridicullissima-01.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/chris-ridicullissima/ridicullissima-02.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/chris-ridicullissima/ridicullissima-03.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/chris-ridicullissima/ridicullissima-04.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Behind the scenes photo by Dustin Portzline:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/chris-ridicullissima/IMG_0568.jpeg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;</description>
    </item>
    
    <item>
      <title>Eye Opening Ceremony at the New Paltz Zen Center</title>
      <link>https://emptysqua.re/blog/npzc-eye-opening/</link>
      <pubDate>Sun, 28 Sep 2025 22:03:24 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/npzc-eye-opening/</guid>
      
      
      
      
      <description>&lt;p&gt;September 27, 2025. A ceremony in which the &lt;a href=&#34;https://npzc.org/&#34;&gt;New Paltz Zen Center&lt;/a&gt; was officially made a Zen temple. Jo An sensei brushed open the eyes of the Manjushri Bodhisattva statue on our altar. The abbot of our mother temple the Village Zendo joined and gave his blessing. A big step for my little community.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/npzc-eye-opening/eye-opening01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/npzc-eye-opening/eye-opening02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/npzc-eye-opening/eye-opening03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/npzc-eye-opening/eye-opening04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/npzc-eye-opening/eye-opening05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How To Belay With Prescription Glasses Or Sunglasses</title>
      <link>https://emptysqua.re/blog/how-to-belay-with-glasses/</link>
      <pubDate>Mon, 22 Sep 2025 12:35:40 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/how-to-belay-with-glasses/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/flipped-down.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;If you want to wear sunglasses and/or prescription glasses while belaying a climber, especially outdoors on multipitch climbs, I hope this post helps you cobble together a system that is functional and fairly robust.&lt;/p&gt;
&lt;h1&gt;Glasses&lt;/h1&gt;

&lt;p&gt;I bought my first pair of &lt;a href=&#34;https://www.roka.com/&#34;&gt;Roka glasses&lt;/a&gt; a couple years ago and they transformed my life. Before, I&amp;rsquo;d worn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prescription glasses to see far,&lt;/li&gt;
&lt;li&gt;prescription &lt;strong&gt;sun&lt;/strong&gt;glasses to see far in sunlight,&lt;/li&gt;
&lt;li&gt;contacts to see far without my glasses falling off while I run, bike, or climb,&lt;/li&gt;
&lt;li&gt;contacts plus sunglasses to run, bike, or climb in sunlight.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I replaced all those configurations with Rokas. I have a pair of transition glasses (aka photochromic, they darken in sunlight), with Roka&amp;rsquo;s special rubber that grips my nose and ears. I can run, bike, and climb with them and they don&amp;rsquo;t slip, even when I sweat. They&amp;rsquo;re an all-purpose vision device: I put them on my face and I can see.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t need to spend $400 on a pair of Rokas though; the rest of this post applies to any (sun)glasses. I just had to tell you how much I like Rokas.&lt;/p&gt;
&lt;h1&gt;Belay glasses&lt;/h1&gt;

&lt;p&gt;Before I got my Rokas, I used to climb and belay wearing contacts. That worked fine with belay glasses except in sunlight, when I wished I had belay &lt;strong&gt;sun&lt;/strong&gt;glasses. Now, though, I wear Rokas all the time, and my belay glasses fight with my Roka glasses for nose-space.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.yyvertical.com/en/products/lunettes-dassurage-clip-up&#34;&gt;YY Vertical Clip-Ups&lt;/a&gt; are unique: they&amp;rsquo;re built to work with your existing glasses. They clip to the top of your glasses&amp;rsquo; frame, and they can flip up (so you can see normally) or down (so you can look upward through the prisms).&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/clipup.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;They&amp;rsquo;re a bit heavy, so even the special Roka rubber can&amp;rsquo;t keep my glasses from slipping on a sweaty day when the Clip Ups are attached. YY Vertical includes a pair of ear hooks to attach to your glasses so they embrace your head more ardently. I lost mine. In Europe, &lt;a href=&#34;https://www.yyvertical.com/en/products/kit-monture?variant=40213731147864&#34;&gt;you can order replacements from YY Vertical&lt;/a&gt;. Here in the US, I replaced them with &lt;a href=&#34;https://www.amazon.com/dp/B00DQOLADS&#34;&gt;these from Amazon&lt;/a&gt;.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/earhooks.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Combined with my glasses, they look like this:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/glasses-with-hooks.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;They slip on and off the arms in a few seconds, so you don&amp;rsquo;t need to look dorky when you&amp;rsquo;re not climbing. The whole package looks like this:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/glasses-with-clipups.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The Clip Ups pop on and off the glasses in a second. I put them on while I belay and put them away in their case clipped to my harness when I climb. Now I can see far, straight ahead or up, in sunlight or shade!&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/flipped-up.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Reinforce the tips&lt;/h1&gt;

&lt;p&gt;The four rubber tips that keep the Clip Ups gripping your glasses are not well-secured to the Clip Ups. You should reinforce them with Super Glue as soon as you buy your Clip Ups, otherwise the tips will fall off almost immediately and become micro-trash. I learned this too late and lost three of the four. Just like the ear hooks, &lt;a href=&#34;https://www.yyvertical.com/en/products/kit-monture?variant=40213731147864&#34;&gt;YY Vertical sells replacements only in Europe&lt;/a&gt;. I replaced mine with the smallest size from &lt;a href=&#34;https://www.amazon.com/dp/B09ZL4RFYX&#34;&gt;this box of rubber tips&lt;/a&gt;; but I wish I hadn&amp;rsquo;t had to waste the rest of the box.&lt;/p&gt;
&lt;h1&gt;Upgrade the case&lt;/h1&gt;

&lt;p&gt;All belay glasses, including Clip Ups, are sold with a flimsy case and a flimsy carabiner that will soon break, falling off your harness when you&amp;rsquo;re five pitches up and tumbling into the void. I guess a robust case would double the total cost of the belay glasses and give us all sticker shock, but it&amp;rsquo;s a shameful waste.&lt;/p&gt;
&lt;p&gt;I replaced the Clip Ups case with this &lt;a href=&#34;https://tactical.onetigris.com/products/onetigris-tactical-front-pocket-wallet-with-zippers-small-coin-purse&#34;&gt;&amp;ldquo;tactical wallet with coin purse&amp;rdquo; by OneTigris&lt;/a&gt;, plus a full-strength climbing carabiner:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/case.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;OneTigris makes gear for guys who want to dress up like commandos and go to January 6th riots, I think. Apparently those guys need tactical wallets with coin purses so they can buy an ice cream cone after they survive the apocalypse. OneTigris stuff is silly but well-built, and this wallet fits the Clip Ups snugly and has a carabiner loop that seems durable. It has a water-resistant zipper that keeps dust out. There are a few little pockets where you could slide a chapstick or something, too.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-belay-with-glasses/case-with-clipup.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;After years of faffing around, I think I found a belaying setup that works for me. Maybe it will work for you, too!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How To Check All Your Feeds With One Tool</title>
      <link>https://emptysqua.re/blog/how-to-check-all-your-feeds/</link>
      <pubDate>Mon, 08 Sep 2025 01:36:07 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/how-to-check-all-your-feeds/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/how-to-check-all-your-feeds/russian-knife.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;That&amp;rsquo;s right, I&amp;rsquo;ve found it. The holy grail, the one feed to rule them all, the aggregator for your whole Internet. None of these walled-garden rent-seeking websites want you to have it, but I&amp;rsquo;ll share it with you.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s your bookmarks.&lt;/p&gt;
&lt;p&gt;I have a long and often-changing list of websites I want or need to check:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Facebook&lt;/strong&gt;, mainly for my local climbers&amp;rsquo; group.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://www.ynab.com/&#34;&gt;You Need A Budget&lt;/a&gt;&lt;/strong&gt; to track my spending.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Threads.&lt;/strong&gt; I miss Twitter&amp;rsquo;s golden age. Since I quit Twitter, I hoped Mastodon would become something. It didn&amp;rsquo;t, but I keep hoping Threads will become something.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bluesky.&lt;/strong&gt; Since Threads is a bust, I keep hoping this will become something.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://feedbin.com/&#34;&gt;Feedbin&lt;/a&gt;&lt;/strong&gt; for the blogs I follow via RSS. This is the actual answer to, &amp;ldquo;What do I do without Twitter?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instagram.&lt;/strong&gt; Technically it&amp;rsquo;s the worst photo site, but all the photographers are still here?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://glass.photo/&#34;&gt;Glass&lt;/a&gt;&lt;/strong&gt;, a technically good photo site with hardly any photographers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discord.&lt;/strong&gt; I hate it, but I use it for talking about distributed systems with non-MongoDB colleagues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My Google Docs spreadsheet of friends.&lt;/strong&gt; A nerd&amp;rsquo;s way to maintain social ties. For each friend, I track how recently we&amp;rsquo;ve interacted, and a target frequency. Overdue friends turn red.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://lobste.rs/&#34;&gt;Lobste.rs&lt;/a&gt;.&lt;/strong&gt; Hacker News without the preening fools, yet.&lt;/p&gt;
&lt;p&gt;I put bookmarks to all these sites in a folder called &amp;ldquo;check&amp;rdquo; in Chrome&amp;rsquo;s bookmarks bar. Here&amp;rsquo;s how it looks in the bookmarks manager:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-check-all-your-feeds/manager.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;When I want to spend ten or twenty minutes just goofing around on the Internet, I right-click the &amp;ldquo;check&amp;rdquo; folder and select &amp;ldquo;Open All&amp;rdquo;, and the pages open in tabs:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-to-check-all-your-feeds/check.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This isn&amp;rsquo;t just a convenient way to goof off, it&amp;rsquo;s a behavior-modification tool for my future self. When I decided a couple years ago to get back into RSS, I added Feedbin to the folder. When I decided to give up on Mastodon, I removed it from the folder so I wouldn&amp;rsquo;t keep trying to make Mastodon happen. That bookmark is gone now, and so is my frustration.&lt;/p&gt;
&lt;p&gt;When I instituted my friends spreadsheet, I added it to the folder. When some colleagues started using Discord, I added Discord to the folder. Now my slightly addictive websites like Instagram are paired with my regular chores, like You Need A Budget. I influence my future attention by curating the bookmarks in my &amp;ldquo;check&amp;rdquo; folder.&lt;/p&gt;
&lt;p&gt;If there&amp;rsquo;s a site I want to check frequently, e.g. to see if a conference has posted next year&amp;rsquo;s dates, or whether anyone has replied to my question on an obscure forum, I add it to the folder. It doesn&amp;rsquo;t matter if the site has no way to subscribe by RSS or email. And I don&amp;rsquo;t need to use an unreliable third-party website change tracker like &lt;a href=&#34;https://visualping.io/&#34;&gt;VisualPing&lt;/a&gt;, which has trouble determining if the site really changed in a way that matters to me. I just toss the site in my &amp;ldquo;check&amp;rdquo; folder, so I&amp;rsquo;ll automatically open it the next time I want to goof off. I keep checking the site until I see the change I&amp;rsquo;m hoping for, or give up. Either way, I just delete the bookmark from the folder.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How My Blog Handles Math and Images in HTML, Atom, and Email in 2025</title>
      <link>https://emptysqua.re/blog/math-images-rss-hugo/</link>
      <pubDate>Sun, 31 Aug 2025 08:43:59 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/math-images-rss-hugo/</guid>
      
      
      
      
      <description>&lt;p&gt;I want to explain computer science on my blog, and show photos, and I want every article to look exquisite. My goal is to display math and images (especially SVGs) as beautifully as possible for people who read my articles on my site, and via an Atom feed and email. This is hard with today&amp;rsquo;s technology, plus I don&amp;rsquo;t have real frontend or design skills.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve recently started &lt;a href=&#34;https://emptysqua.re/blog/series/knowledge&#34;&gt;a series of articles about epistemology and distributed systems&lt;/a&gt;, which includes diagrams and equations that broke my existing publication system. I came up with a new stack of kludges that works for me. I&amp;rsquo;ll write them down here so I remember, and perhaps you&amp;rsquo;ll learn something you can use.&lt;/p&gt;
&lt;p&gt;Throughout this post, you can refer to this map:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/math-images-rss-hugo/blog-pipeline.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/blog-pipeline.png&#34;&gt;
&lt;/picture&gt;&lt;h1&gt;My site generator&lt;/h1&gt;

&lt;p&gt;I use Hugo. I learned about Hugo early, since Steve Francia was my boss at MongoDB when he created it, but I resisted using it for some time. It&amp;rsquo;s not as extensible as the Python-based static site generators, and it relies on Markdown, which is a hacky format. But the various Python generators were all much too slow for me, so eventually I switched. Hugo is super fast enough: even now that my site has 700 articles and thousands of images, it does a full rebuild in under a second.&lt;/p&gt;
&lt;h1&gt;Images and previews&lt;/h1&gt;

&lt;p&gt;While I&amp;rsquo;m drafting an article, I run the &lt;a href=&#34;https://gohugo.io/commands/hugo_server/&#34;&gt;Hugo server&lt;/a&gt; and preview the article locally. Whenever I save a change to the article, Hugo rebuilds it and auto-reloads it in the browser. For some reason, Hugo doesn&amp;rsquo;t properly reload when I change an &lt;em&gt;image&lt;/em&gt;, even though it&amp;rsquo;s perfectly aware that the image has changed. I fixed this by creating &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blog/b0389eb8483c4ad3b05a94ddd35e1267b97c8c37/emptysquare/themes/hugo_theme_emptysquare/layouts/_default/_markup/render-image.html&#34;&gt;this template&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go-html-template&#34; data-lang=&#34;go-html-template&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{/* This is layouts/_default/_markup/render-image.html */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{/* Email-safe output for Atom/Kit */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{/* .IsBlock is available if configured: gohugo.io/render-hooks/images/ */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{-&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.IsBlock&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;img&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Destination&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;safeURL&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;display: block; max-width:100%&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Text&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Title&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{-&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;else&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;img&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Destination&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;safeURL&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;display:inline;width:auto;height:auto;vertical-align:middle&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Text&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#c4a000&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Title&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{-&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This template interprets Markdown images, like &lt;code&gt;![](foo.jpg)&lt;/code&gt;. If it&amp;rsquo;s running inside Hugo&amp;rsquo;s development server, then it adds a cache-busting fingerprint to the image URL. Now whenever the contents of &lt;code&gt;foo.jpg&lt;/code&gt; change, Hugo creates a copy of the file with its hash included in the filename, like &lt;code&gt;foo-xxx.jpg&lt;/code&gt;. Hugo updates the HTML page to point to &lt;code&gt;foo-xxx.jpg&lt;/code&gt;, so my browser will definitely load the newest version of the image. When I change &lt;code&gt;foo.jpg&lt;/code&gt; again, its hash is now different, so it&amp;rsquo;s named like &lt;code&gt;foo-yyy.jpg&lt;/code&gt;, and the browser reloads it properly.&lt;/p&gt;
&lt;p&gt;This shouldn&amp;rsquo;t be necessary—&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag&#34;&gt;this is what ETags are for&lt;/a&gt;—but Hugo needs this kludge to properly refresh the preview when I change an image.&lt;/p&gt;
&lt;h1&gt;Publishing an article&lt;/h1&gt;

&lt;p&gt;I manage a variety of tasks with some PyCharm custom shortcuts and &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blob/b0389eb8483c4ad3b05a94ddd35e1267b97c8c37/blog&#34;&gt;an ever-growing Python script called &lt;code&gt;blog&lt;/code&gt;&lt;/a&gt;. Working together, PyCharm and my script can do some advanced tricks, like pasting an image from the macOS clipboard into a Markdown file, or &lt;a href=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/&#34;&gt;creating a photo gallery&lt;/a&gt;, or starting up the Hugo development server and opening the browser to a preview of the draft I&amp;rsquo;m currently editing.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;blog&lt;/code&gt; script works especially hard when I finish a draft and publish it. I&amp;rsquo;ll describe the publication pipeline below.&lt;/p&gt;
&lt;h1&gt;SVGs&lt;/h1&gt;

&lt;p&gt;I love to draw diagrams in &lt;a href=&#34;https://excalidraw.com/&#34;&gt;Excalidraw&lt;/a&gt; and export them as SVGs like this:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/math-images-rss-hugo/raft-states.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/raft-states.jpg&#34;&gt;
&lt;/picture&gt;&lt;p&gt;&lt;em&gt;An intricate diagram. I drew this for &lt;a href=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/&#34;&gt;an article about knowledge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve learned that mail readers (even web-based ones like GMail) won&amp;rsquo;t show SVGs, so I need to give them a fallback. Remember, the article pipeline is HTML → Atom → email, so my Atom feed must provide that fallback, which is then passed through Kit to email subscribers.&lt;/p&gt;
&lt;p&gt;When I publish a draft, my Python script creates a PNG for every SVG in the article (or a JPEG, if the PNG is too many bytes). I have &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blog/b0389eb8483c4ad3b05a94ddd35e1267b97c8c37/emptysquare/themes/hugo_theme_emptysquare/layouts/partials/svg_to_picture.html&#34;&gt;a template&lt;/a&gt; that converts every SVG in the feed to a &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; that prefers the SVG but falls back to the PNG or JPEG. This template is called from &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blog/b0389eb8483c4ad3b05a94ddd35e1267b97c8c37/emptysquare/themes/hugo_theme_emptysquare/layouts/index.rss.xml&#34;&gt;the template that generates the whole Atom feed&lt;/a&gt;.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/math-images-rss-hugo/svg-pipeline.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/svg-pipeline.png&#34;&gt;
&lt;/picture&gt;&lt;h1&gt;Math&lt;/h1&gt;

&lt;p&gt;A well-rendered formula is beautiful, like this &lt;a href=&#34;https://en.wikipedia.org/wiki/Riemann_sum&#34;&gt;Riemann sum&lt;/a&gt;:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/math-images-rss-hugo/formula-07d3a87a28ab23654488f903ec8c1e0c2609df405319729c14efb8016655943e.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/formula-07d3a87a28ab23654488f903ec8c1e0c2609df405319729c14efb8016655943e.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;S = \sum_{i=1}^{n} f(x_i^*) \, \Delta x_i&#34;&gt;
&lt;/picture&gt;&lt;p&gt;I use both block formulas like the one above, and also inline formulas, like &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/math-images-rss-hugo/formula-2820951a377c18ce3e89093ca7c31c204b1196a7e571781c6080f8207524a8dc.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/formula-2820951a377c18ce3e89093ca7c31c204b1196a7e571781c6080f8207524a8dc.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E = mc^2&#34;&gt;
&lt;/picture&gt;. By default, &lt;a href=&#34;https://gohugo.io/content-management/mathematics/&#34;&gt;Hugo renders math with client-side Javascript called MathJax&lt;/a&gt;, but I want to show math on the web using the modern HTML &lt;code&gt;&amp;lt;math&amp;gt;&lt;/code&gt; tag instead. I override Hugo&amp;rsquo;s default with &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blob/0b258f869e87163ea2f7c222f0cd8ad990764e39/emptysquare/themes/hugo_theme_emptysquare/layouts/_markup/render-passthrough.html&#34;&gt;this template&lt;/a&gt;. Hugo requires it to be called &lt;code&gt;render-passthrough.html&lt;/code&gt;, but don&amp;rsquo;t be fooled: it&amp;rsquo;s not for a generic &amp;ldquo;passthrough&amp;rdquo; feature, it&amp;rsquo;s for rendering math specifically. The template also sets a variable &lt;code&gt;hasMath&lt;/code&gt; to true, so &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blob/1a25e8c4e5c38cfc9d3e53718952bbecf3022ac5/emptysquare/themes/hugo_theme_emptysquare/layouts/_default/baseof.html#L32&#34;&gt;this other template&lt;/a&gt; knows to include special math CSS.&lt;/p&gt;
&lt;p&gt;HTML &lt;code&gt;&amp;lt;math&amp;gt;&lt;/code&gt; tags may not display in Atom readers or email, so once again my Python publication script has a job to do. When I publish an article, the script searches it for formulae, and creates an SVG for each. The best way I found to do this was install the MathJax library on my laptop and &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blob/b1c21fa650ae5668b0f1af1d6d9b3dce196b7e30/tex2svg.mjs&#34;&gt;execute it at build time&lt;/a&gt; to produce an SVG. Then my Python script produces a PNG for each formula&amp;rsquo;s SVG.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/math-images-rss-hugo/math-pipeline.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/math-pipeline.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;The filenames for the SVGs and PNGs include a hash of the formula itself. Imagine the &amp;ldquo;xxx&amp;rdquo; in the filenames above are 64-bit hashes. On the web, you see &lt;code&gt;&amp;lt;math&amp;gt;&lt;/code&gt; tags rendered by your browser, but in Atom (and therefore in email), you see the SVG, or fall back to the PNG. &lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blob/570dfd612b1ce56592f4bc3910c90e457e2e51bb/emptysquare/themes/hugo_theme_emptysquare/layouts/partials/math_to_picture.html&#34;&gt;This template&lt;/a&gt; replaces &lt;code&gt;&amp;lt;math&amp;gt;&lt;/code&gt; tags with &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; tags in the Atom feed, using the hashes of the formulas to determine the correct image filenames.&lt;/p&gt;
&lt;h1&gt;Block and inline images&lt;/h1&gt;

&lt;p&gt;I didn&amp;rsquo;t use many inline images until I started writing a lot of math formulae. But now, since inline formulae become inline images in Atom and email, I need inline images to render correctly. At the start of this journey I was using Mailchimp&amp;rsquo;s feed-to-email automation, and Mailchimp seems unable to &lt;em&gt;show&lt;/em&gt; both inline and block images in emails from feeds. By default, inline images are okay, but block images expand to a monstrous width. If you select Mailchimp&amp;rsquo;s option &amp;ldquo;Resize RSS feed images to fit template&amp;rdquo;, then block images look good, but inline images are stupidly converted to blocks, which is abominable.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/ajdavis/emptysquare-hugo/blob/8d97ee31bff81cc90e4c6bbea60c657471b78dbf/emptysquare/themes/hugo_theme_emptysquare/layouts/_default/_markup/render-image.rss.xml&#34;&gt;This template&lt;/a&gt; adds CSS styles to images in the Atom feed to make them behave. It also transforms block images to inline images, wrapped in paragraphs. This didn&amp;rsquo;t actually work, though: Mailchimp mangled my images regardless.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go-html-template&#34; data-lang=&#34;go-html-template&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{/* this file is render-image.rss.xml, it&amp;#39;s called rss but it makes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;     email-safe output for Atom/Kit. Put a block image in a &amp;#34;p&amp;#34; and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;     mark inline for proper fill in Atom-to-email campaign */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{-&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.IsBlock&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;p&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#c4a000&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Destination&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;safeURL&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#c4a000&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;display:inline;max-width:100%;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;            width:100%;height:auto;margin:1em 0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#c4a000&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Text&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#c4a000&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Title&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;p&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{-&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;else&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#c4a000&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Destination&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;safeURL&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#c4a000&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;display:inline;width:auto;height:auto;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;            vertical-align:middle&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#c4a000&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Text&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#c4a000&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c4a000&#34;&gt;.Title&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;}}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;{{-&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;span style=&#34;color:#f8f8f8&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I switched from Mailchimp to Kit (formerly ConvertKit) for my Atom-to-email automation. Mailchimp did some funny business with image widths that I couldn&amp;rsquo;t defeat, but Kit worked well from the start. I didn&amp;rsquo;t experiment to check if the template above is actually necessary with Kit, but at this point I was getting tired of futzing with my blog.&lt;/p&gt;
&lt;p&gt;(Note: this template is only executed for Markdown images like &lt;code&gt;![](foo.jpg)&lt;/code&gt;, not handwritten HTML &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags in Markdown. I&amp;rsquo;ll have to remember that as I write.)&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/math-images-rss-hugo/modern-times.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;The feeding machine in Charlie Chaplin&amp;rsquo;s &amp;ldquo;Modern Times&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What a &lt;a href=&#34;https://en.wikipedia.org/wiki/Tzimmes&#34;&gt;tzimmis&lt;/a&gt;. What a kludge. Why is all this necessary in 2025? I guess the future is already here, it&amp;rsquo;s &lt;a href=&#34;https://www.goodreads.com/quotes/681-the-future-is-already-here-it-s-just-not-evenly&#34;&gt;just not evenly distributed&lt;/a&gt;: feed readers are behind the curve, and email readers farther behind, because of security concerns or other issues I don&amp;rsquo;t understand. So I have to build a complicated pipeline to transform math into SVGs, and SVGs into other formats, to ensure graceful degradation. Furthermore, Hugo is fast but hard to customize, and it takes a precarious stack of templates to (usually) produce the HTML that I want, where I want it. These templates &lt;a href=&#34;https://blog.codinghorror.com/parsing-html-the-cthulhu-way/&#34;&gt;use regexes to parse HTML, which will inevitably break on some page someday&lt;/a&gt;. And finally, RSS-to-email automation is a dark art.&lt;/p&gt;
&lt;p&gt;I hope all this is useful to someone else. Maybe you don&amp;rsquo;t need these hacks. Maybe a basic Hugo setup works fine for your blog, or you don&amp;rsquo;t mind if your images don&amp;rsquo;t appear exactly right in all channels. Good for you. But there may come a time when you need more. &lt;a href=&#34;https://www.nku.edu/~longa/poems/housman9.html&#34;&gt;I will friend you, if I may, in the dark and cloudy day&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Knowledge and Common Knowledge in a Distributed Environment, Part 2</title>
      <link>https://emptysqua.re/blog/review-common-knowledge-part-2/</link>
      <pubDate>Mon, 25 Aug 2025 10:42:25 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-common-knowledge-part-2/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/odin-1.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Odin riding an eight-legged horse across water, flanked by two ravens.&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;Odin, god of wisdom.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is the second in &lt;a href=&#34;https://emptysqua.re/blog/series/knowledge/&#34;&gt;my series of articles about Knowledge and Common Knowledge in a Distributed Environment&lt;/a&gt;, a beautiful and difficult 1990 paper about distributed systems and epistemology. So far, I analyzed the &amp;ldquo;muddy children puzzle,&amp;rdquo; I defined levels of knowledge, and I used these levels of knowledge to analyzed the Raft protocol (which was published long after this paper).&lt;/p&gt;
&lt;p&gt;Now, the moment you&amp;rsquo;ve been waiting for: coordinated attack!&lt;/p&gt;


&lt;h1&gt;Coordinated attack&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/guan-yu.jpeg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;Chinese general &lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Guan_yu_-Summer_Palace,_Beijing.JPG&#34;&gt;Guan Yu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Two generals are encamped on hilltops, on either side of the enemy army. If they attack simultaneously, they&amp;rsquo;ll win, otherwise they&amp;rsquo;ll fail. They have no initial plan, so they send messengers back and forth, to try to agree on a time to attack. Unfortunately, messengers can be unpredictably slow, or they can be caught by the enemy. How can the generals agree on a time?&lt;/p&gt;
&lt;p&gt;(The paper&amp;rsquo;s authors, Halpern and Moses, call this problem &amp;ldquo;coordinated attack&amp;rdquo; and they say it was introduced by Jim Gray in 1978. The puzzle may be older than that. Sometimes it&amp;rsquo;s called &amp;ldquo;Chinese generals.&amp;rdquo; The &amp;ldquo;Byzantine generals problem&amp;rdquo; is a more complex version that Lamport described later.)&lt;/p&gt;
&lt;p&gt;As you probably know, the generals can never agree. If general &lt;em&gt;A&lt;/em&gt; sends a message saying, &amp;ldquo;attack at dawn,&amp;rdquo; he doesn&amp;rsquo;t know whether &lt;em&gt;B&lt;/em&gt; received it, so he risks attacking alone. So perhaps the protocol is, &amp;ldquo;attack if you proposed a time and received acknowledgment&amp;rdquo;? But if &lt;em&gt;B&lt;/em&gt; receives and acknowledges the message, she doesn&amp;rsquo;t know whether &lt;em&gt;A&lt;/em&gt; got her acknowledgment, so she also risks attacking alone. And so on. No protocol in this asynchronous system can guarantee they attack simultaneously.&lt;/p&gt;
&lt;p&gt;Halpern and Moses (citing earlier work) use a &lt;em&gt;many scenarios&lt;/em&gt; argument to prove that coordinated attack is impossible. If &lt;em&gt;A&lt;/em&gt; sent the message and hasn&amp;rsquo;t observed an acknowledgment, there are many scenarios that fit his observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The message was lost&lt;/li&gt;
&lt;li&gt;The message is still in transit&lt;/li&gt;
&lt;li&gt;The message was received and the acknowledgment was lost&lt;/li&gt;
&lt;li&gt;The message was received and the acknowledgment is still in transit&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;&lt;strong&gt;General &lt;em&gt;A&lt;/em&gt;&amp;rsquo;s knowledge is the set of facts that are true in all the possible scenarios that match his observations.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So &lt;em&gt;A&lt;/em&gt; knows his message was sent, but that&amp;rsquo;s it. If &lt;em&gt;B&lt;/em&gt; receives the message and sends an acknowledgment, she also has four scenarios she can&amp;rsquo;t distinguish. The generals can&amp;rsquo;t be sure they&amp;rsquo;ve agreed to attack unless &lt;em&gt;A&lt;/em&gt; knows that &lt;em&gt;B&lt;/em&gt; knows that &amp;hellip; infinitely. In other words, their attack time must be &lt;em&gt;common knowledge&lt;/em&gt;, and common knowledge is impossible in an asynchronous system.&lt;/p&gt;
&lt;p&gt;(Yes, this is related to &lt;a href=&#34;https://www.the-paper-trail.org/post/2008-08-13-a-brief-tour-of-flp-impossibility/&#34;&gt;FLP impossibility&lt;/a&gt;.)&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/101729.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Stylized drawing of a seated figure with two swords, wearing a cloak adorned with animal heads.&#34; title=&#34;&#34;&gt;&lt;h1&gt;Definition of knowledge&lt;/h1&gt;

&lt;p&gt;Here&amp;rsquo;s my favorite part of the paper. It begins with a mathematical model of a distributed system, which will be familiar to people who use PlusCal, TLA+, or other formalizations. There are some processors (aka nodes or agents) called &lt;em&gt;p&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;, &lt;em&gt;p&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;, &amp;hellip;, &lt;em&gt;p&lt;/em&gt;&lt;sub&gt;n&lt;/sub&gt;. A &lt;em&gt;run&lt;/em&gt; of the system is just like a TLA+ &lt;em&gt;behavior:&lt;/em&gt; a sequence of events starting at time 0 and continuing infinitely (perhaps reaching a final state and staying there forever). The system is characterized by the set of all possible runs, &lt;em&gt;R&lt;/em&gt;. This is just like a TLA+ state graph. A &lt;em&gt;point&lt;/em&gt; (&lt;em&gt;r&lt;/em&gt;, &lt;em&gt;t&lt;/em&gt;) is a moment in a run &lt;em&gt;r&lt;/em&gt; at time &lt;em&gt;t&lt;/em&gt;. At every point, each processor has its own &lt;em&gt;local history&lt;/em&gt;, the events it has observed, including its own actions. E.g., a Raft node knows the actions it&amp;rsquo;s taken and the messages it&amp;rsquo;s received, but it has no direct knowledge of other nodes&amp;rsquo; histories. A &lt;em&gt;protocol&lt;/em&gt; is a function of a processor&amp;rsquo;s local history: each processor deterministically chooses its next action based on its observations so far.&lt;/p&gt;
&lt;p&gt;A processor &lt;em&gt;p&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt; &lt;em&gt;knows&lt;/em&gt; a fact &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; at point (&lt;em&gt;r&lt;/em&gt;, &lt;em&gt;t&lt;/em&gt;) if &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is true at all points of &lt;em&gt;R&lt;/em&gt; that are &lt;em&gt;indistinguishable&lt;/em&gt; from (&lt;em&gt;r&lt;/em&gt;, &lt;em&gt;t&lt;/em&gt;). By indistinguishable points, I mean all points (&lt;em&gt;r&lt;/em&gt;&#39;, &lt;em&gt;t&lt;/em&gt;&#39;) where &lt;em&gt;p&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;&amp;rsquo;s local history is the same as at (&lt;em&gt;r&lt;/em&gt;, &lt;em&gt;t&lt;/em&gt;). For example, if I observed my partner Jennifer go out the front door, and I haven&amp;rsquo;t seen her come back in, then I know she&amp;rsquo;s gone, based on my local history of observations. She might have taken the car or gone for a walk—since I haven&amp;rsquo;t looked in the parking lot, those two scenarios are indistinguishable to me. But &amp;ldquo;Jennifer&amp;rsquo;s gone&amp;rdquo; is true in both scenarios, so I &lt;em&gt;know&lt;/em&gt; that fact.&lt;/p&gt;
&lt;p&gt;In Raft, if the leader has sent a log entry to both followers and only one follower has acknowledged it, then the leader can&amp;rsquo;t distinguish between the scenarios where one or two followers received the entry. But &amp;ldquo;the entry is majority-replicated&amp;rdquo; is true in both scenarios, so the leader knows that fact.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft.png&#34; alt=&#34;The Raft leader has sent an entry to two followers, and one has acknowledged it&#34; title=&#34;The Raft leader has sent an entry to two followers, and one has acknowledged it&#34;&gt;
&lt;/picture&gt;&lt;p&gt;A processor&amp;rsquo;s &lt;em&gt;view&lt;/em&gt; of the system is a function of its history. The view could just be the identity function—the processor&amp;rsquo;s view is its history, i.e. the sequence of all its actions and observations. Or the processor&amp;rsquo;s view could be a &lt;em&gt;summary&lt;/em&gt; of its history, e.g. the Raft election protocol requires a node to remember only the most recent vote it cast, not the sequence of &lt;em&gt;all&lt;/em&gt; its past votes. In TLA+ we usually have some variables for each node, which are updated when nodes take actions or receive messages: these are the nodes&amp;rsquo; &lt;em&gt;views&lt;/em&gt;, their summaries of their local histories!&lt;/p&gt;
&lt;p&gt;Halpern and Moses more formally define a processor&amp;rsquo;s view of the system, and how that relates to levels of knowledge, like distributed knowledge and common knowledge, in Section 6 of the paper. It&amp;rsquo;s great, go read it.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/102550.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Norse-style panel with animal and serpent designs, labeled “Oden forskaper sig.”&#34; title=&#34;&#34;&gt;&lt;h1&gt;Indistinguishability graph&lt;/h1&gt;

&lt;p&gt;As a TLA+ guy, I usually think of a &lt;em&gt;state&lt;/em&gt; as an assignment of values to variables, and the &lt;em&gt;state graph&lt;/em&gt; connects states with directed edges representing possible state transitions. Let&amp;rsquo;s consider the muddy children from the &lt;a href=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/&#34;&gt;previous episode&lt;/a&gt;. Say there are two children. Our variables are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;a&lt;/em&gt;, &lt;em&gt;b&lt;/em&gt;: Whether child &lt;em&gt;a&lt;/em&gt; and/or &lt;em&gt;b&lt;/em&gt; is muddy.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;m&lt;/em&gt;: Whether the father has announced, &amp;ldquo;At least one of you is muddy.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;q&lt;/em&gt;: The number of times the father has asked, &amp;ldquo;Can you prove you&amp;rsquo;re muddy?&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The initial state is:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-3bd13482c86f6f39cdd45591938ed6e8273d02e2ccb455c52b43f2dc26f52b37.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-3bd13482c86f6f39cdd45591938ed6e8273d02e2ccb455c52b43f2dc26f52b37.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;a \in \{\text{true}, \text{false}\}
\land b \in \{\text{true}, \text{false}\}
\land m = \text{false}
\land q = 0&#34;&gt;
&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Then the father makes his announcement (&lt;em&gt;m&lt;/em&gt; becomes true), and then he starts asking his question (&lt;em&gt;q&lt;/em&gt; increases). We know at least one child is muddy; let&amp;rsquo;s say that&amp;rsquo;s child &lt;em&gt;a&lt;/em&gt; and the maybe-muddy one is &lt;em&gt;b&lt;/em&gt;. Here are the states and transitions:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/muddy-children-states.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/muddy-children-states.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;How can we use this graph to represent what the children &lt;em&gt;know&lt;/em&gt;? For any two states, let&amp;rsquo;s add a non-directed edge between them if some child has the same view in both states, i.e. the states are indistinguishable to that child. And let&amp;rsquo;s label the edge with the name of the child who can&amp;rsquo;t distinguish the states:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/muddy-children-states-2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/muddy-children-states-2.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Child &lt;em&gt;b&lt;/em&gt; can&amp;rsquo;t distinguish if it&amp;rsquo;s muddy or clean in the initial state, so there&amp;rsquo;s a blue &amp;ldquo;b&amp;rdquo; edge between the first two initial states. The two states are distinct in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s eyes, because it can see whether &lt;em&gt;b&lt;/em&gt; is muddy, so we don&amp;rsquo;t label the edge &amp;ldquo;a&amp;rdquo;. After the father announces &amp;ldquo;at least one of you is muddy,&amp;rdquo; &lt;em&gt;b&lt;/em&gt; still doesn&amp;rsquo;t know if it&amp;rsquo;s muddy, because it sees that &lt;em&gt;a&lt;/em&gt; is muddy so &lt;em&gt;a&lt;/em&gt; might be the only muddy child. Therefore the two next states are also indistinguishable to &lt;em&gt;b&lt;/em&gt;. Finally the father asks, &amp;ldquo;can you prove you&amp;rsquo;re muddy?&amp;rdquo; As I explained in the previous article, &lt;em&gt;a&lt;/em&gt; and &lt;em&gt;b&lt;/em&gt; now know if they&amp;rsquo;re muddy, so the third states are distinct to both of them and there&amp;rsquo;s no blue edge.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/102593.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Decorative border of three running bears, labeled as a variant from the Ynglinga Saga.&#34; title=&#34;&#34;&gt;&lt;p&gt;Can we do the same for Raft? The real Raft TLA+ spec has dozens of variables. Let&amp;rsquo;s simplify. Let&amp;rsquo;s say there&amp;rsquo;s one log entry, there&amp;rsquo;s a permanent leader, and there is a set &lt;em&gt;F&lt;/em&gt; of two followers named &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; and &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;. Here are our variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;r&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;, &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;: Follower &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; or &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; has received the entry (&amp;ldquo;r&amp;rdquo; for &amp;ldquo;receive&amp;rdquo;).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;a&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;, &lt;em&gt;a&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;: The leader received an acknowledgment from &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; or &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; (&amp;ldquo;a&amp;rdquo; for &amp;ldquo;acknowledge&amp;rdquo;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At first all the variables are false. Then &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; or &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; receives the log entry, and some sequence of receiving and acknowledging leads to the final state, where both followers have received and acknowledged the entry. Here&amp;rsquo;s a state-transition graph, false is white and true is red:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-states.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-states.jpg&#34;&gt;
&lt;/picture&gt;&lt;div style=&#34;text-align: center&#34;&gt;
&lt;p&gt;&lt;span style=&#34;font-style: italic&#34;&gt;A state transition graph.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In the last article I defined two facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; (phi): the log entry exists&lt;/li&gt;
&lt;li&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; (psi): the log entry is majority-replicated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A follower knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; if it received the entry, and the leader knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; if it knows at least one follower knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;, since the leader plus one follower is a majority of the three-node replica set. In other words:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-c592a67f281aff041bf4035ffa2e4f98544a5541da70aed870c1dda606aef665.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-c592a67f281aff041bf4035ffa2e4f98544a5541da70aed870c1dda606aef665.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_{leader} \psi \Longleftarrow K_{leader} S_F \varphi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Let&amp;rsquo;s analyze this with the graph. A follower &lt;em&gt;f&lt;/em&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;, if &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is true in all the states indistinguishable to &lt;em&gt;f&lt;/em&gt; from this state. The only variable in &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;&amp;rsquo;s view is &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;, so all states with the same &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; value are indistinguishable to &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;. For &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;&amp;rsquo;s indistinguishability graph, I&amp;rsquo;ll draw edges between the nodes where &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; is true, and edges between the nodes where &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; is false:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-knowledge-f1.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-knowledge-f1.jpg&#34;&gt;
&lt;/picture&gt;&lt;div style=&#34;text-align: center&#34;&gt;
&lt;p&gt;&lt;span style=&#34;font-style: italic&#34;&gt;The follower f&lt;sub&gt;1&lt;/sub&gt;&#39;s indistinguishability graph.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The graph for &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; is the same, but flipped vertically.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-knowledge-f2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-knowledge-f2.jpg&#34;&gt;
&lt;/picture&gt;&lt;div style=&#34;text-align: center&#34;&gt;
&lt;p&gt;&lt;span style=&#34;font-style: italic&#34;&gt;The follower f&lt;sub&gt;2&lt;/sub&gt;&#39;s indistinguishability graph.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The leader doesn&amp;rsquo;t know whether a follower has received the entry, so states that differ only by their &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; or &lt;em&gt;r&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; values are indistinguishable to the leader. The leader knows what acknowledgments it received, so it &lt;em&gt;can&lt;/em&gt; distinguish states by their &lt;em&gt;a&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; or &lt;em&gt;a&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; values.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-knowledge-leader.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-knowledge-leader.png&#34;&gt;
&lt;/picture&gt;&lt;div style=&#34;text-align: center&#34;&gt;
&lt;p&gt;&lt;span style=&#34;font-style: italic&#34;&gt;The leader&#39;s indistinguishability graph.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Putting it all together:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-states-combined.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/raft-states-combined.jpg&#34;&gt;
&lt;/picture&gt;&lt;div style=&#34;text-align: center&#34;&gt;
&lt;p&gt;&lt;span style=&#34;font-style: italic&#34;&gt;The combined indistinguishability graph.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As I said earlier, &amp;ldquo;the leader knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt;&amp;rdquo; is equivalent to &amp;ldquo;the leader knows that at least one follower knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;.&amp;rdquo; We can evaluate a property like this with the indistinguishability graph. The leader knows &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; at a state &lt;em&gt;S&lt;/em&gt;, if every path from &lt;em&gt;S&lt;/em&gt; along zero or one &lt;span style=&#34;color: #2f9e44; font-weight: bold&#34;&gt;leader edges&lt;/span&gt;, then zero or one &lt;span style=&#34;color: #1971c2; font-weight: bold&#34;&gt;&lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; edges&lt;/span&gt;, leads to a state where &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/leader-knows-f1-knows-phi.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/leader-knows-f1-knows-phi.jpg&#34;&gt;
&lt;/picture&gt;&lt;p&gt;This is a graph-style way of saying:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the leader knows &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; in state &lt;em&gt;S&lt;/em&gt;, if&lt;/li&gt;
&lt;li&gt;in all states &lt;em&gt;T&lt;/em&gt; indistinguishable (to the leader) from &lt;em&gt;S&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; in all states indistinguishable (to &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;) from &lt;em&gt;T&lt;/em&gt; !&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same goes for states where the leader knows &lt;em&gt;f&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/leader-knows-f2-knows-phi.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/leader-knows-f2-knows-phi.jpg&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Putting it all together:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/leader-knows-f1-or-f2-knows-phi.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/leader-knows-f1-or-f2-knows-phi.jpg&#34;&gt;
&lt;/picture&gt;&lt;p&gt;As you&amp;rsquo;d expect, the leader knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; in states where &lt;em&gt;a&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; or &lt;em&gt;a&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; is true; i.e. states where one or the other follower has acknowledged the entry. But it&amp;rsquo;s cool to see how it can be expressed as a graph query. This seems ripe for automatic verification. Halpern and Moses describe how various levels of knowledge, such as distributed knowledge, or &amp;ldquo;everyone knows,&amp;rdquo; or &amp;ldquo;everyone knows that everyone knows,&amp;rdquo; are properties of paths through the indistinguishability graph.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll stop here and let my brain cool off. Next time: agents in a distributed system can achieve common knowledge if they have reasonably reliable clocks.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-2/101730.jpg&#34; style=&#34;max-width: 50%;&#34; alt=&#34;Line drawing of Odin seated on a throne with animal head carvings, one arm outstretched.&#34;/&gt;
&lt;/div&gt;
&lt;p&gt;Odin and other Norse images by &lt;a href=&#34;https://www.nasjonalmuseet.no/en/collection/producer/56446/gerhard-munthe&#34;&gt;Gerhard Munthe&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Speak Up If You&#39;re Stuck in a Zazen Rut</title>
      <link>https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/</link>
      <pubDate>Tue, 19 Aug 2025 11:13:09 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/</guid>
      
      
      
      
      <description>&lt;p&gt;Certain topics seem off-limits in Zen, like: What&amp;rsquo;s the goal of meditation? How do I get better at it? What is everyone else experiencing when they sit? I spoke at the &lt;a href=&#34;https://npzc.org/&#34;&gt;New Paltz Zen Center&lt;/a&gt; about getting stuck at a plateau in meditation skill, and about Zen communities&amp;rsquo; reluctance to discuss peak experiences, progress, and goals. Here&amp;rsquo;s the video and a written version of the talk.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/mx-V0ktgxa4?si=WliNR8hPnhS6ABal&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen style=&#34;margin-bottom: 2em&#34;&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;p&gt;There&amp;rsquo;s a lot we don&amp;rsquo;t talk about in Zen.&lt;/p&gt;
&lt;p&gt;In my experience, we hardly ever talk about what our daily zazen is &lt;em&gt;like&lt;/em&gt;. What&amp;rsquo;s it usually like for you? Is it groggy, dull, joyful, peaceful, deep, distracted? How long do you usually go between thoughts? Do you usually hear every sound outside your window, or do you kind of space out most of the time?&lt;/p&gt;
&lt;p&gt;We also don&amp;rsquo;t describe the deepest experiences we&amp;rsquo;ve ever had. And we hardly talk about progress either: have you gotten better at meditation over the years? In fact, I get the sense a lot of people are allergic to two words I just used: &amp;ldquo;progress&amp;rdquo; and &amp;ldquo;better&amp;rdquo;!&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t read anywhere that we&amp;rsquo;re not supposed to talk about this. Not that I remember. But I&amp;rsquo;ve picked it up from hearing what other people say or don&amp;rsquo;t say. Maybe I just made an assumption that these topics are off-limits, and never questioned it.&lt;/p&gt;
&lt;p&gt;When we first learn to meditate, there&amp;rsquo;s a lot of talk about the contents of our zazen and how to do it better. Like whenever I&amp;rsquo;ve taught beginning instruction, I meditate with people for a few minutes and then I ask them, &amp;ldquo;What happened? What was that like?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;But then that kinda &amp;hellip; dies? I don&amp;rsquo;t ask experienced meditators, &amp;ldquo;What was that last half-hour like for you?&amp;rdquo; They don&amp;rsquo;t ask me either.&lt;/p&gt;
&lt;p&gt;Maybe you&amp;rsquo;re all talking about it in private, in dokusan. Dokusan is like sex, I don&amp;rsquo;t get to see what other people are doing. Maybe you&amp;rsquo;re always asking the teachers, &amp;ldquo;I can&amp;rsquo;t stop worrying about work. How do I reliably achieve samadhi?&amp;rdquo; There&amp;rsquo;s nothing stopping me from asking that in dokusan. I mean, nothing overt. But I don&amp;rsquo;t. Over my years in Zen I&amp;rsquo;ve come to hide my curiosity, partly because I&amp;rsquo;ve heard our community&amp;rsquo;s silence on certain topics and I&amp;rsquo;ve become silent about them too.&lt;/p&gt;
&lt;p&gt;Lately I&amp;rsquo;ve been talking with some of my fellow students, and they mostly agree: we hesitate to talk intimately about meditation, and it&amp;rsquo;s a relief when we occasionally break the silence. Some of the core questions we have about our practice—over time we get the message that we&amp;rsquo;re not supposed to ask. Our strongest spiritual desires—we&amp;rsquo;re not supposed to express them. Our greatest accomplishments, we&amp;rsquo;re not supposed to celebrate them. I often feel this way, and I&amp;rsquo;m not the only one.&lt;/p&gt;
&lt;p&gt;So today is an experiment. I&amp;rsquo;m going to break some silences and see what happens.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-01.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;One of the deepest meditations I ever had was in late August 2004. I was 23 years old, it was the last night of my last sesshin at the end of a year living at &lt;a href=&#34;https://emptysqua.re/blog/yokoji-zmc-august-2019/&#34;&gt;Yokoji monastery&lt;/a&gt;, off the grid high up in the mountains in Southern California. I knew that in a few days this monastic life would end, I would fly to New York City and start a new life of some sort. So, on the final night of sesshin, after everyone else had left the zendo, I stayed. The jikido had blown out the candle—we had to be very careful at Yokoji not to burn the monastery down—and all the lights were off—electricity at night at Yokoji was from a couple of car batteries charged from a solar panel during the day, so we used it very sparingly—except there was a dim yellow light in a sconce on the zendo wall. I was sitting in that pool of dim warm light, with the dark room around me and the dark night outside, and the cacti and the pine trees and mountains around me.&lt;/p&gt;
&lt;p&gt;For a little while, I stopped. I barely had any thoughts. I heard every single cricket chirp, without missing one, for minutes upon minutes. There was nothing wrong with the world, and nothing wrong with me. For the first time in years, I wasn&amp;rsquo;t worrying about myself, wasn&amp;rsquo;t trying to fix myself. I had no preferences. Normally, I&amp;rsquo;d be impatient to leave the zendo, go down to sit on the tree stump at the bottom of the hill and smoke a cigarette in the moonlight, then read in bed for a few minutes and do all the little indulgences that would comfort me at the end of the day.&lt;/p&gt;
&lt;p&gt;But this night, I had no preference. I could leave the zendo, or I could stay all night listening to the crickets. Everything was ok, I had finally stopped trying to fix my life.&lt;/p&gt;
&lt;p&gt;Eventually I did leave the zendo of course, smoked my cigarette in the moonlight, and a few days later I flew to New York City and I&amp;rsquo;ve never had a period of zazen quite like that again. And I was pretty angry about that for a while. I gave a year of my life to Zen, and the second I leave the monastery, my samadhi is all shot and I can&amp;rsquo;t get back to that peaceful place anymore. I blamed Zen, and I blamed myself. It was actually pretty reasonable to blame myself—I was smoking pot and getting drunk every night, I was a mess, I wasn&amp;rsquo;t creating the conditions for a strong Zen practice and I knew it. But I couldn&amp;rsquo;t help remember that one night and wish I was there again.&lt;/p&gt;
&lt;p&gt;So this is the trap of being attached to an experience, and comparing the past and the present, right? Zen texts warn us about this trap all the time. But on the other hand, that comparison told me something about how I was living; it was a signpost. I knew I was capable of that kind of samadhi, and the farther my zazen got from the experience of that night, the more clear it was that something was going wrong with how I was living in general.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-02.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;At the beginning of that year at the monastery, when I arrived at Yokoji, there was another young guy my age, Wes, who showed up the same day. I was very impressed with all the monastics at Yokoji, of course, I wanted to be just like them and I wanted them to approve of me. But I kind of hated Wes. He was maybe a year younger than me, big and goofy and enthusiastic. Once he came into the monastery kitchen, took a gallon of milk from the fridge and chugged the entire thing in one go, right in front of everybody.&lt;/p&gt;
&lt;p&gt;My worst fear was that the legit Zen monks at Yokoji would see me and Wes as the same. I had to distinguish myself in their eyes from this milk-chugger. I had to be better than him, more sophisticated. One day, Wes and I and a few of the monks were hanging out in the kitchen, and Wes said that the day before, he&amp;rsquo;d been concentrating on Mu, and he had some kind of mind-blowing experience where everything was dark and empty and Mu was everything and he fell into it. I was so jealous. How could big, goofy Wes have achieved this state already, after a few weeks, and not me!&lt;/p&gt;
&lt;p&gt;That was more than 20 years ago, and I still remember that moment in the kitchen, listening to Wes, feeling my &lt;em&gt;envy&lt;/em&gt;. It got deep into me and grew into something bigger, something that lasted. I think this is one of the reasons we don&amp;rsquo;t generally talk about our zazen experiences in public. What if other people compare themselves to us? What if they feel jealous, or on the other hand, what if their zazen is better and they feel arrogant?&lt;/p&gt;
&lt;p&gt;A subtler danger is that, by describing something aloud, I make it into a thing. I fix that memory, like a sticking a needle through an insect specimen. I took that risk by describing my samadhi to you just now. Now my memory of that final night has been permanently changed by describing it to you. It&amp;rsquo;s changed and it&amp;rsquo;s hardened.&lt;/p&gt;
&lt;p&gt;And you know what? Life goes on. I get jealous sometimes, or self-satisfied other times, and my practice continues and I get over it. I have nice experiences, and then they end, and I&amp;rsquo;m disappointed, and I get over that too. Jealousy and comparison and attachment to past experiences, these are just natural human feelings, and I don&amp;rsquo;t think we have to be so concerned about protecting ourselves from them. The benefits of talking about practice openly might outweigh these risks.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/dogen.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Sometimes I have arguments with Dogen. The arguments are a bit one-sided, because I&amp;rsquo;m angry and he isn&amp;rsquo;t, plus he&amp;rsquo;s been dead for 800 years. He sits there in his black robe. He has a pale round baby face and a little rosebud mouth, like in that one painting I&amp;rsquo;ve seen of him. He&amp;rsquo;ll start with some innocuous &lt;a href=&#34;https://villagezendo.org/practice/suggested-readings/fukanzazengi/&#34;&gt;instruction&lt;/a&gt;, like: &amp;ldquo;Sit stably in samadhi.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I say: I&amp;rsquo;ve been trying for half my life and hardly ever achieved stable samadhi. The best sit I ever had was after a year in a monastery, do I have go back to the monastery?&lt;/p&gt;
&lt;p&gt;Dogen: There is no reason to leave your own seat at home and take a meaningless trip to the dusty places of other countries. The treasure house will open of itself, and you will be able to use it at will.&lt;/p&gt;
&lt;p&gt;Me: I&amp;rsquo;ve been waiting more than 20 years and the treasure house seems mostly closed. I definitely can&amp;rsquo;t &amp;ldquo;use it at will,&amp;rdquo; I have so little control. Time swiftly passes by and opportunity is lost. I need to change something before I waste any more time.&lt;/p&gt;
&lt;p&gt;Dogen: How can we distinguish practice from enlightenment? The Vehicle of Reality is in the Self. Why should we waste our efforts trying to attain it?&lt;/p&gt;
&lt;p&gt;Me: At this point I&amp;rsquo;ve &lt;a href=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/&#34;&gt;long since given up on &amp;ldquo;attaining enlightenment.&amp;rdquo;&lt;/a&gt; I&amp;rsquo;m just trying to be a bit more enlightened from moment to moment, and I&amp;rsquo;m looking for a meditation practice that supports me better. Surely it&amp;rsquo;s ok to experiment a bit and see if something works better?&lt;/p&gt;
&lt;p&gt;Dogen: Learn to step back, turning the light inwards, illuminating the Self. Doing so, your body and mind will drop off naturally, and Original Self will manifest. If you wish to attain suchness, practice suchness immediately.&lt;/p&gt;
&lt;p&gt;Me: This is beautiful. I really mean it, Dogen, I appreciate your beautiful poetic writing. But it&amp;rsquo;s not very helpful for me. Can you break it down at all?&lt;/p&gt;
&lt;p&gt;Dogen: Think of not-thinking. How do you think of not-thinking? Beyond thinking. This is the essential way of zazen. The zazen which I am talking about is not step-by-step meditation. It is simply the dharma gate of peace and comfort. It is the practice-enlightenment of the ultimate Way.&lt;/p&gt;
&lt;p&gt;Me: But it doesn&amp;rsquo;t feel like &amp;ldquo;the dharma gate of peace and comfort&amp;rdquo; most of the time. Instead, it&amp;rsquo;s been a dharma gate of struggle and frustration.&lt;/p&gt;
&lt;p&gt;At this point Dogen sits in serene and irritating silence.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-03.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;ve felt quite stuck in my zazen. Some retreats are deep, some are just &amp;ldquo;whatever,&amp;rdquo; some I spend in awful anxious obsession. I can&amp;rsquo;t predict or control it. And my half-hour morning meditation has just been groggy mind-wandering; I wonder if it&amp;rsquo;s worth it or if it would be better to just sleep in.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been on this disappointing plateau for many years, and I stopped asking teachers about it at some point. I got embarrassed to admit how stuck I was. I teach meditation to other people, I give dharma talks, I&amp;rsquo;ve been doing this so long, I&amp;rsquo;m ashamed to admit how little I still understand about how my mind works and how to guide my own zazen. And also I&amp;rsquo;d gotten the impression that everything I&amp;rsquo;ve just said isn&amp;rsquo;t Zen—I&amp;rsquo;m not supposed to evaluate my progress, I&amp;rsquo;m not supposed to want progress, I&amp;rsquo;m not supposed to want my zazen to be a certain way!&lt;/p&gt;
&lt;p&gt;So this June, my partner Keishin and I did a weeklong retreat with a company called &lt;a href=&#34;https://www.jhourney.io/&#34;&gt;Jhourney&lt;/a&gt;, which is trying to teach people to reach specific states in meditation. This company was founded a few years ago in San Francisco by some young guys who&amp;rsquo;d been practicing jhana meditation. The jhanas are levels of meditative absorption, they&amp;rsquo;re described in a few suttas of the Pali canon, written 2000 years ago. There are eight levels of jhana, give or take. In the Mahāsaccaka Sutta, Buddha says,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I recall once, when my father was working, and I was sitting in the cool shade of a rose-apple tree, then—quite secluded from sensuality, secluded from unskillful mental qualities—I entered and remained in the first jhana: rapture and pleasure born from seclusion, accompanied by directed thought and evaluation. Could that be the path to Awakening? Then following on that memory came the realization: That is the path to Awakening.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In recent years some Western Buddhists have become very interested in bringing the jhanas to the West and learning or rediscovering how to enter these states reliably. And these young San Francisco guys had been studying under some teachers, comparing notes with each other about what sort of meditation techniques worked more or less well to enter the jhana states, and they decided to create a company to teach the jhanas to others. That&amp;rsquo;s Jhourney.&lt;/p&gt;
&lt;p&gt;So, doing this Jhourney retreat was a chance to get out of my zazen rut. Jhourney is fairly scientific. They say a certain percentage of people on their retreats reach the first jhana or the second jhana. They run experiments with different methods for teaching the jhanas, and they measure their retreats&amp;rsquo; outcomes and try to optimize the path to the jhanas. That seemed really attractive to me, just a fresh way of doing things. It seemed like the opposite of Zen. Plus, I could walk into a room of strangers and admit how frustrated and stupid I feel, because I didn&amp;rsquo;t care what they thought of me.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-04.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The Jhourney retreat was, indeed, the opposite of Zen. We were handed a 120-page instruction book the first night of retreat. Most of the retreat was unscheduled free time, but we had a lot to do in that time. We had to read most of the instruction book in the first few days. The book told us to try a bunch of experiments on ourselves and write down the results. Like, when I recite a metta mantra, does it produce a feeling somewhere in my body? What&amp;rsquo;s that like? What about if I visualize a tree of compassion growing from my chest, or if I just smile? How is it different to sit or lie down, to be silent or listen to music, to sit for 45 minutes, or an hour, or more? We tried all these experiments in our meditations, figured out what seemed to work to produce a peaceful, openhearted, effortless feeling. That feeling is the ember that can grow into a roaring fire, and that&amp;rsquo;s the first jhana. Once we could produce that feeling, we kept refining and experimenting and writing down the results.&lt;/p&gt;
&lt;p&gt;We had regular interviews with &amp;ldquo;coaches,&amp;rdquo; asking what we tried, how well it worked, and what we were going to try next. One time I meditated with my coach for 20 minutes, narrating my experience moment by moment, while she directed me on how to use my attention and my imagination to steer the meditation.&lt;/p&gt;
&lt;p&gt;The retreat was mostly silent. But partway through, the retreat managers split us into groups of four to have dinner together and discuss our experiences with the other participants. We were encouraged to say what we&amp;rsquo;d experienced, whether it was a jhana or something else, and swap tips about how to go deeper in meditation. I have to admit I was really apprehensive about this—what if someone else has gone much deeper than I have, will I be really jealous and disappointed? What if that person is some meditation noobie who doesn&amp;rsquo;t deserve to succeed more than me, someone who got to some profound jhana from beginner&amp;rsquo;s luck? If that happens I&amp;rsquo;ll feel like a fool. It&amp;rsquo;ll be Wes in the kitchen all over again. Luckily I had reached Jhana One a couple times before this dinner, and that was on par with the other people, so I felt good about myself. But I noticed how the fear of being jealous was a big deal for me!&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-08.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Talking in detail about what we&amp;rsquo;d tried and how it was going, it was helpful. And it brought us together. On sesshin, it&amp;rsquo;s easy for me to assume that some people have it all figured out. If you&amp;rsquo;re sitting still and upright, I&amp;rsquo;m guessing you&amp;rsquo;ve got zazen figured out. I am &lt;em&gt;also&lt;/em&gt; sitting still and upright, and my zazen is a catastrophe, but I assume I&amp;rsquo;m the only one who&amp;rsquo;s struggling. But if we talk about it, and I hear that you&amp;rsquo;re struggling too, I&amp;rsquo;m not alone anymore.&lt;/p&gt;
&lt;p&gt;On the Jhourney retreat, the coaches set up laptops in a separate room, where we could go watch videos of past participants describing the jhanas they reached. I did not go watch the videos, again because of my fear of jealousy. And also because, towards the end of the retreat, I was enjoying meditation so much I didn&amp;rsquo;t want to spend my free time doing much else.&lt;/p&gt;
&lt;p&gt;One of the goals of the Jhourney retreat is to enter a jhana. The jhanas for most people go in a certain order, starting at Jhana One, and indeed the jhana that I entered matched the description of Jhana One. I was following the instructions: I relaxed and I smiled a little bit, I welcomed all feelings and thoughts that arose. Whereas usually in zazen I try to drop my thoughts, and let feelings come and go, with jhana meditation I welcomed each thought and feeling, I invited them to join my meditation. I focused on a feeling in my body, Jhourney calls this an &amp;ldquo;openhearted feeling,&amp;rdquo; for me it&amp;rsquo;s like a bittersweet heartache. It was the bittersweet joy and relief at finally being compassionate with myself.&lt;/p&gt;
&lt;p&gt;I sat like this for an hour and a half, occasionally shifting position, but I didn&amp;rsquo;t want to get up. At some point there was a warm achy swelling feeling in my heart, on the actual left side of my chest. I understood for the first time why all our old phrases for emotions talk about the &amp;ldquo;heart lifting&amp;rdquo; or &amp;ldquo;heart bursting,&amp;rdquo; for the first time I noticed that these feelings are literally centered in my heart. My whole body tingled, I felt joyful and excited, my heart raced, and I felt like I was being lifted off the floor by the lightness of my joy.&lt;/p&gt;
&lt;p&gt;Of course, as soon as this happened, I started to wonder: How do I make this last? This is clearly Jhana One, how do I progress through this to Jhana Two? And I lost it, like a kid who gets so excited the first time they balance on a bike that they fall right off. I entered Jhana One a few times over the retreat, and I found it fascinating and exciting. After so many years, something totally new was happening in my meditation.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-09.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;So what? So I figured out how to make a nice tingling feeling, big whoop.&lt;/p&gt;
&lt;p&gt;Jhourney has a slogan, something like: come for the jhanas and stay for the transformed relationship with your emotions.&lt;/p&gt;
&lt;p&gt;Well, now that I&amp;rsquo;m back home, my zazen is transformed. I&amp;rsquo;ve gone from sitting half an hour each morning to a full hour. And the hour feels easy. Instead of resisting distracting thoughts, or coldly watching them pass by, I actually embrace them as a welcome addition to the whole messy total. Like, if I catch myself thinking about rock climbing, I just think, &amp;ldquo;I love rock climbing,&amp;rdquo; and welcome it in to quietly join the meditation. If I have a Radiohead song stuck in my head, I think, &amp;ldquo;May Radiohead be happy,&amp;rdquo; and I welcome Thom Yorke and the band to join the meditation. They usually quiet down after a while. But I&amp;rsquo;m not trying to make them be quiet, I&amp;rsquo;m welcoming them in.&lt;/p&gt;
&lt;p&gt;Zazen isn&amp;rsquo;t a battle against my mind anymore, so I enjoy it, and find it much less effortful. I haven&amp;rsquo;t returned to Jhana One, which is a little disappointing, I was hoping to continue to later jhanas, which sound much more valuable than just tingly excitement. That&amp;rsquo;s ok, I&amp;rsquo;ll keep working on it and trying things.&lt;/p&gt;
&lt;p&gt;Since jhanas have gotten more attention in the West in the last few years, there&amp;rsquo;s been a common reaction among Western Buddhists: trying to achieve a jhana state is &lt;em&gt;striving&lt;/em&gt;, and it will only reinforce the ego. It&amp;rsquo;s counterproductive, it&amp;rsquo;s a symptom of the exact thing Buddha said is the source of suffering: thirst, trishna, the desire for things to be different from how they are.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s funny when Zen people have this reaction. It&amp;rsquo;s funny because jhana in Pali is the same as the Sanskrit word dhyana, which in Chinese is pronounced Chan, which in Japanese is pronounced Zen. We are the jhana school. It&amp;rsquo;s funny, too, that some Zen people are allergic to saying there are levels of meditation, when our koan curriculum is hundreds of ordered, numbered, pass-fail exams. Is it more dangerous to have meditation goals than koan goals?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-11.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The key to entering jhana is, in my experience, to &lt;em&gt;very gently&lt;/em&gt; try, and not be too attached to the outcome. It&amp;rsquo;s like pissing—it&amp;rsquo;s learning a way of relaxing and letting in the joy, or letting out the piss or whatever.&lt;/p&gt;
&lt;p&gt;The zazen instructions I&amp;rsquo;ve read have said to relax, of course, but the missing part was for me to &lt;em&gt;enjoy&lt;/em&gt; letting the mind be as it is. The stereotype is that jhana meditation is about striving for a goal. But it&amp;rsquo;s paradoxical, like so many things in Buddhism: the more I accept my mind as it is, the more I welcome all my thoughts and emotions as they naturally arise, the less effort I make to change how I feel, the more easily I fall into a jhana.&lt;/p&gt;
&lt;p&gt;After the Jhourney retreat I had dokusan, and a VZ teacher told me that the way I&amp;rsquo;m meditating now, after the retreat, sounds perfectly compatible with Zen. I won&amp;rsquo;t name the teacher in case they&amp;rsquo;re listening to me now and changing their mind, concluding that in fact I&amp;rsquo;ve fallen into heresy and they disown me. But I &lt;em&gt;also&lt;/em&gt; think it&amp;rsquo;s compatible with Zen —I think the content is mostly the same, but the teaching method is totally different, and it&amp;rsquo;s what I needed to get out of my rut. That&amp;rsquo;s just upaya, skillful means: the message was better tailored for me.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-05.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Right now, I think there&amp;rsquo;s a middle path. On one side of the path, you can fall into obsessive desire to achieve something in meditation. You can compare yourself with others and get jealous. You can get attached to an experience in the past and disappointed by your experience now. That&amp;rsquo;s a danger that lots of Zen texts warn us against, and it&amp;rsquo;s a real danger. But also, I&amp;rsquo;ve found that I can get jealous and attached and disappointed, and it isn&amp;rsquo;t fatal. Eventually I get over it.&lt;/p&gt;
&lt;p&gt;But what about the danger on the other side of the middle path? What about giving up, deciding that I&amp;rsquo;m not supposed to want to get better at meditation, that I can&amp;rsquo;t get better at meditation, that my groggy mind-wandering is the best there is? I&amp;rsquo;ve fallen down on that side of the path and spent years there, wasting thousands of hours sitting on the cushion with very little benefit. I now think it&amp;rsquo;s helpful to &lt;em&gt;evaluate&lt;/em&gt; my meditation. After a period of zazen, do I feel relaxed, happy, focused? Do I enjoy it, do I look forward to it? Is it getting easier, more enjoyable, over the years? How does the rest of my day go after an hour of zazen in the morning?&lt;/p&gt;
&lt;p&gt;There are dangers in asking these questions. I could get so focused on the relative that I forget the absolute—like, thinking that if my zazen doesn&amp;rsquo;t feel good, it isn&amp;rsquo;t the manifestation of Buddha mind. I can hear Dogen saying, &amp;ldquo;You are endowed with the essential functioning of the Buddha Way, why pursue worthless pleasures that are like sparks from a flint?&amp;rdquo; And that&amp;rsquo;s a great point, Dogen, we &lt;em&gt;should&lt;/em&gt; remember that this mind has always been the Buddha mind, there is nothing to strive for and no one who can attain it. But also, it&amp;rsquo;s helpful for me to set some goals, and evaluate my progress, and distinguish what works and what doesn&amp;rsquo;t. It&amp;rsquo;s like the foot before and the foot behind in walking.&lt;/p&gt;
&lt;p&gt;Besides, the old man backs me up! Buddha said in the Kalama Sutta, that we should evaluate various practices and behaviors for ourselves. If they lead to harm and suffering, abandon them; if they lead to welfare and happiness, enter and remain in them. Don&amp;rsquo;t just follow tradition or a teacher or scripture.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-10.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Why did it take me so long to admit my frustration with meditation, and try something new? I take responsibility for this, I&amp;rsquo;m a perfectionist, and I care what other people think of me. I get embarrassed if I don&amp;rsquo;t understand something, so I hide it.&lt;/p&gt;
&lt;p&gt;I also think that, for someone like me, there are some dangers lurking in Zen specifically. A lot of the old koans look like competitions between monks. They&amp;rsquo;re one-upping each other, they&amp;rsquo;re pretentious, they&amp;rsquo;re quoting sutras and Chinese literature, they&amp;rsquo;re hitting each other. &lt;a href=&#34;https://emptysqua.re/blog/are-you-prepared-to-save-a-life/&#34;&gt;Nansen killed a cat&lt;/a&gt; because no one could answer his question. Gutei cut off his servant&amp;rsquo;s finger because his servant didn&amp;rsquo;t understand. And then the commentaries compare them to generals and swordsmen, or tigers and dragons. I &lt;em&gt;like&lt;/em&gt; this fierceness, but it can make Zen into a competition where I don&amp;rsquo;t want to admit my weakness. I think this competitive, even violent streak, has passed from China through Japan and the samurai era all the way to today. Even at the fuzzy cuddly &lt;a href=&#34;https://villagezendo.org/&#34;&gt;Village Zendo&lt;/a&gt;, we have this aspect. So if you&amp;rsquo;re like me, check yourself: are you stuck in a rut, because you&amp;rsquo;re afraid to admit when you don&amp;rsquo;t get it?&lt;/p&gt;
&lt;p&gt;When I was embarrassed to talk openly with teachers, at least I had spiritual friends. The Buddha was a big fan of spiritual friendship. In the Kalyāṇamitta Sutta there&amp;rsquo;s a story where Buddha&amp;rsquo;s student Ananda says, &amp;ldquo;I&amp;rsquo;ve heard that good friends are half of the spiritual life.&amp;rdquo; Buddha replies, &amp;ldquo;No, Ananda, no, that&amp;rsquo;s not true at all. Good friends are the &lt;em&gt;whole&lt;/em&gt; of the spiritual life. Someone with good friends can expect to develop and cultivate the noble eightfold path.&amp;rdquo; In other Pali suttas the Buddha says that monastics and householders all benefit from spiritual friendship, from &lt;em&gt;kalyanamitta&lt;/em&gt;, that if a friend has attained a greater understanding on some subject of the dharma, we should engage our friend in discussion to learn what they know.&lt;/p&gt;
&lt;p&gt;For me, spiritual friendships with my peers has been essential. With teachers it&amp;rsquo;s hard for me to really express my doubt and confusion, to say, &amp;ldquo;I hate this entire book of 100 koans,&amp;rdquo; &amp;ldquo;My zazen is a catastrophe,&amp;rdquo; &amp;ldquo;I wasted every minute of that sesshin thinking about work,&amp;rdquo; &amp;ldquo;I passed that koan by imitating a car horn, but I still don&amp;rsquo;t understand it.&amp;rdquo; But with my close friends it&amp;rsquo;s easy to be real. It&amp;rsquo;s a relief to admit what a mess my practice is, how I doubt myself and the practice. I find out that most people are going through this kind of thing, at least the ones I talk to, and I&amp;rsquo;m not alone and there&amp;rsquo;s nothing wrong with me. And once I&amp;rsquo;ve calmed down a bit, hope returns. I can imagine ways to change my practice, to improve it.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-06.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Let me close with some caveats and clarifications and qualifications about the jhanas, and about Zen silences. I&amp;rsquo;m not saying everyone should practice jhana meditation. I am saying that if you feel stuck, try something new, maybe try some techniques from other traditions. I&amp;rsquo;m not claiming that the teachers and ancestors created all these Zen silences deliberately. I think they mostly arise subtly and implicitly in the community. I&amp;rsquo;m not claiming these silences are absolute: sometimes we break them, different people are bolder or more cautious. I&amp;rsquo;m not even claiming they&amp;rsquo;re all worthless and we should break them: We &lt;em&gt;should&lt;/em&gt; be cautious talking about our practice with others, we should be mindful of Right Speech. We should be careful talking about goals.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;m saying: Real Zen is freedom. Freedom to break silences, or maintain them, according to circumstances. It&amp;rsquo;s a human instinct to conform to the group, but Zen is liberation from that conformity, freedom to go with the flow and to go against it.&lt;/p&gt;
&lt;p&gt;As for me, I&amp;rsquo;m going to talk more with my teachers, more with my friends, about how my practice is really going. Intimately, in detail. I won&amp;rsquo;t pretend anymore, or hide my confusion and my curiosity.&lt;/p&gt;
&lt;p&gt;I hope you all talk more openly with your spiritual friends, and trust your own wisdom, especially when it doesn&amp;rsquo;t seem to conform.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a story about when Buddha was dying, he was talking with his beloved student Ananda, who was probably panicking a bit at the thought of life without his teacher. Ananda asked how the sangha should continue without the Buddha. Buddha said everyone in the sangha must be islands unto themselves, or perhaps lamps unto themselves. The Pali word &lt;em&gt;dipa&lt;/em&gt; is the same for island and lamp, so maybe he&amp;rsquo;s saying we must all be our own light to illuminate the path, or all be our own islands, safe in the flood of samsara.&lt;/p&gt;
&lt;p&gt;He said, in the Mahaparinibbana Sutta:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am frail, Ananda, old, aged, far gone in years. This is my eightieth year, and my life is spent. Even as an old cart, Ananda, is held together with much difficulty, so this body is kept going only with supports.&lt;/p&gt;
&lt;p&gt;Therefore, Ananda, be islands unto yourselves, refuges unto yourselves, seeking no external refuge; with the truth as your island, the truth as your refuge, seeking no other refuge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;img src=&#34;https://emptysqua.re/blog/speak-up-if-youre-stuck-in-a-zazen-rut/zazen-07.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Photos © A. Jesse Jiryu Davis.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Knowledge and Common Knowledge in a Distributed Environment, Part 1</title>
      <link>https://emptysqua.re/blog/review-common-knowledge-part-1/</link>
      <pubDate>Wed, 13 Aug 2025 11:24:35 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-common-knowledge-part-1/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/saraswati.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;This otherworldly painting centers upon a pale goddess dressed in white who is seated on a pink-tipped white lotus that hovers in an indeterminate space. Two massive red poppy plants in full bloom and bud, ultimately based on European botanical drawings that were adopted by Mughal artists and from there by Mewar artists and from there by a painter in Sawar, flank the goddess. The background is a solid field of rich chocolate brown.&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://asia.si.edu/explore-art-culture/collections/search/edanmdm:fsg_S2018.1.40/&#34;&gt;Saraswati, goddess of knowledge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We usually reason about distributed systems by asking, what can one process &lt;em&gt;know&lt;/em&gt; about the state of the system? E.g., if a majority of &lt;a href=&#34;https://raft.github.io/&#34;&gt;Raft&lt;/a&gt; nodes tell the leader that they have replicated a log entry, the leader &lt;em&gt;knows&lt;/em&gt; the entry is durable. Therefore the leader can take various actions, like replying to the user who submitted the log entry. On the other hand, if a Raft follower hasn&amp;rsquo;t heard from the leader in a while, it might &lt;em&gt;suspect&lt;/em&gt; the leader has crashed, but it can never know for sure. A process must somehow gather enough information to deduce the facts it must know to take the correct action. I think about knowledge all the time when I&amp;rsquo;m analyzing a distributed algorithm.&lt;/p&gt;
&lt;p&gt;On the other hand, my main tool for checking the correctness of an algorithm is TLA+ and the TLC model-checker. There&amp;rsquo;s no notion of knowledge in a TLA+ specification, just a mindless state machine obeying if-then instructions. When I read a TLA+ spec I might think, &amp;ldquo;In this state, the leader knows the log entry is majority-replicated.&amp;rdquo; But that&amp;rsquo;s anthropomorphism. The spec just says: if this, then do that.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m studying the 1990 paper, &amp;ldquo;&lt;a href=&#34;http://arxiv.org/abs/cs/0006009&#34;&gt;Knowledge and Common Knowledge in a Distributed Environment&lt;/a&gt;&amp;rdquo;, by Joseph Halpern and Yoram Moses. They bring a theory of knowledge and belief to distributed systems. It&amp;rsquo;s a hard paper for me, so I&amp;rsquo;ll blog it as I read, a few sections at a time. My goals are to understand the material and explain it to you. I also want to explore a new direction: integrating knowledge into TLA+. This might be a dead end, we&amp;rsquo;ll see!&lt;/p&gt;


&lt;h1&gt;Muddy Children&lt;/h1&gt;

&lt;p&gt;The paper begins with the muddy children puzzle, apparently a classic of epistemology:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imagine &lt;em&gt;n&lt;/em&gt; children playing together. The mother of these children has told them that if they get dirty there will be severe consequences. So, of course, each child wants to keep clean, but each would love to see the others get dirty. Now it happens during their play that some of the children, say &lt;em&gt;k&lt;/em&gt; of them, get mud on their foreheads. Each can see the mud on others but not on his own forehead. So, of course, no one says a thing. Along comes the father, who says, &amp;ldquo;At least one of you has mud on your head,&amp;rdquo; thus expressing a fact known to each of them before he spoke (if &lt;em&gt;k&lt;/em&gt; &amp;gt; 1). The father then asks the following question, over and over: &amp;ldquo;Can any of you prove you have mud on your head?&amp;rdquo; Assuming that all the children are perceptive, intelligent, truthful, and that they answer simultaneously, what will happen?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Halpern and Moses introduce this puzzle to warm up the reader&amp;rsquo;s thinking about knowledge in distributed systems. When is it important for one process to know what another process knows? What is &amp;ldquo;common knowledge&amp;rdquo; and when is it needed?&lt;/p&gt;
&lt;p&gt;The solution to the muddy children puzzle is: the first &lt;em&gt;k&lt;/em&gt; - 1 times the father asks the question, all children simultaneously reply &amp;ldquo;no&amp;rdquo;: they can&amp;rsquo;t prove they&amp;rsquo;re muddy. The &lt;em&gt;k&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt; time, the muddy children say &amp;ldquo;yes&amp;rdquo; and the clean children say &amp;ldquo;no.&amp;rdquo; But if the father hadn&amp;rsquo;t announced &amp;ldquo;at least one is muddy&amp;rdquo; at the start, the muddy children would never figure out they&amp;rsquo;re muddy. Even when &lt;em&gt;k&lt;/em&gt; &amp;gt; 1, and all the children can see with their own eyes that at least one is muddy, the father&amp;rsquo;s announcement of this fact is crucial.&lt;/p&gt;
&lt;p&gt;This result is well-known to logicians, so the paper doesn&amp;rsquo;t explain it in depth. But I will. Let&amp;rsquo;s work the problem for &lt;em&gt;k&lt;/em&gt; from 1 to 3. (The number of clean children is actually irrelevant.)&lt;/p&gt;
&lt;p&gt;Here are our variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;k&lt;/em&gt;: the number of muddy children.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;m&lt;/em&gt;: the father&amp;rsquo;s announcement, &amp;ldquo;at least one of you is muddy.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;q&lt;/em&gt;: the number of times the father has asked, &amp;ldquo;can you prove you&amp;rsquo;re muddy?&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;One muddy child&lt;/h2&gt;

&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k1.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k1.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;The first time the father asks the question, the muddy child sees that all the other children are clean. Since the father announced &lt;em&gt;m&lt;/em&gt;, the muddy child knows it must be the muddy one, and says &amp;ldquo;yes.&amp;rdquo; Incidentally, the muddy child knows &lt;em&gt;k&lt;/em&gt; = 1. The other children see one muddy child. They don&amp;rsquo;t know whether they&amp;rsquo;re also muddy (they don&amp;rsquo;t know if &lt;em&gt;k&lt;/em&gt; is 1 or 2), so they say &amp;ldquo;no&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The purpose of the father&amp;rsquo;s announcement &lt;em&gt;m&lt;/em&gt; is clear here: without it, the muddy child doesn&amp;rsquo;t know if the reason it sees only clean children is because &lt;em&gt;k&lt;/em&gt; = 0, or because &lt;em&gt;k&lt;/em&gt; = 1 and it has mud on its face.&lt;/p&gt;
&lt;h2&gt;Two muddy children&lt;/h2&gt;

&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s where things get freaky. The father announces &lt;em&gt;m&lt;/em&gt;, which all the children can already see is true, and yet his announcement is necessary for solving the puzzle.&lt;/p&gt;
&lt;p&gt;When the father asks his question the first time, can Child &lt;em&gt;a&lt;/em&gt; answer &amp;ldquo;yes&amp;rdquo;? It sees that Child &lt;em&gt;b&lt;/em&gt; is muddy. It considers some possibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;a&lt;/em&gt; could have mud on its own face or not.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;b&lt;/em&gt; could &lt;em&gt;think&lt;/em&gt; it has mud on its own face, or not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two &lt;em&gt;pairs&lt;/em&gt; of possibilities lead to four possible worlds in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s mind:&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2-worlds-1.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2-worlds-1.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;But the father&amp;rsquo;s announcement &lt;em&gt;m&lt;/em&gt; is &amp;ldquo;common knowledge&amp;rdquo;: that is, everyone knows that &lt;em&gt;k&lt;/em&gt; ≥ 1, and everyone knows that everyone knows &amp;hellip; (ad infinitum) &amp;hellip; that &lt;em&gt;k&lt;/em&gt; ≥ 1. So &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; knows the world where all children are clean is impossible:&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2-worlds-2.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2-worlds-2.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;Now the father asks both children, &amp;ldquo;Can you prove you&amp;rsquo;re muddy?&amp;rdquo; Since &lt;em&gt;a&lt;/em&gt; could be muddy or clean, depending on which world is real, it replies &amp;ldquo;no.&amp;rdquo; Child &lt;em&gt;b&lt;/em&gt; replies &amp;ldquo;no&amp;rdquo; for the same reason (swapping &lt;em&gt;a&lt;/em&gt; and &lt;em&gt;b&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Child &lt;em&gt;a&lt;/em&gt; hears &lt;em&gt;b&lt;/em&gt;&amp;rsquo;s answer, and now &lt;em&gt;a&lt;/em&gt; has learned something. Child &lt;em&gt;a&lt;/em&gt; knows that &lt;em&gt;b&lt;/em&gt; sees a muddy child! If &lt;em&gt;b&lt;/em&gt; hadn&amp;rsquo;t seen a muddy child, then &lt;em&gt;b&lt;/em&gt; would&amp;rsquo;ve said &amp;ldquo;yes&amp;rdquo;, because of &lt;em&gt;m&lt;/em&gt;. This eliminates the other world where &lt;em&gt;b&lt;/em&gt; sees no muddy child:&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2-worlds-3.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k2-worlds-3.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;All worlds where &lt;em&gt;a&lt;/em&gt; is clean are now eliminated, so the next time the father asks his question, &lt;em&gt;a&lt;/em&gt; replies &amp;ldquo;yes&amp;rdquo;: it knows it&amp;rsquo;s muddy. Child &lt;em&gt;b&lt;/em&gt; does the same.&lt;/p&gt;
&lt;p&gt;So the father&amp;rsquo;s announcement of &lt;em&gt;m&lt;/em&gt; is crucial to &lt;em&gt;a&lt;/em&gt;, even though &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;k&lt;/em&gt; ≥ 1 already: &lt;em&gt;m&lt;/em&gt; ensures that &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; knows &lt;em&gt;k&lt;/em&gt; ≥ 1.&lt;/p&gt;
&lt;h2&gt;Three muddy children&lt;/h2&gt;

&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k3.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/k3.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;ll work this problem similarly, but without diagrams. At the start, before the father announces &lt;em&gt;m&lt;/em&gt;, &lt;em&gt;a&lt;/em&gt; sees 2 muddy children, so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Initial condition, before father announces m:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  k ∈ [2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  b knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    k ∈ [1, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      k ∈ [0, 3]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The text above is my attempt to represent statements like, &amp;ldquo;&lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;k&lt;/em&gt; is between 2 and 3 inclusive, and &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; knows &lt;em&gt;k&lt;/em&gt; is between 1 and 3 inclusive,&amp;rdquo; and so on. Why does &lt;em&gt;a&lt;/em&gt; know &lt;em&gt;b&lt;/em&gt; knows that? Because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; sees mud on &lt;em&gt;c&lt;/em&gt; (definitely 1 muddy child)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;b&lt;/em&gt; could think its own face is muddy (maybe 1 more muddy child)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;b&lt;/em&gt; (in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s mind) might see mud on &lt;em&gt;a&lt;/em&gt; (maybe 1 more muddy child)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hence &lt;em&gt;b&lt;/em&gt; (in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s mind) could think there are 1, 2, or 3 muddy children.&lt;/p&gt;
&lt;p&gt;How could &lt;em&gt;a&lt;/em&gt; think &lt;em&gt;b&lt;/em&gt; thinks &lt;em&gt;c&lt;/em&gt; thinks there are 0 muddy children, even though &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;c&lt;/em&gt; sees the mud on &lt;em&gt;b&lt;/em&gt;&amp;rsquo;s face? Well, if &lt;em&gt;a&lt;/em&gt; is clean (in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s mind), and &lt;em&gt;b&lt;/em&gt; thinks its own face is clean (in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s mind), then &lt;em&gt;b&lt;/em&gt; thinks &lt;em&gt;c&lt;/em&gt; might see no muddy children (still in &lt;em&gt;a&lt;/em&gt;&amp;rsquo;s mind). So &lt;em&gt;a&lt;/em&gt; thinks it&amp;rsquo;s possible for &lt;em&gt;b&lt;/em&gt; to think &lt;em&gt;c&lt;/em&gt; sees no muddy children, although &lt;em&gt;a&lt;/em&gt; knows that&amp;rsquo;s wrong!&lt;/p&gt;
&lt;p&gt;But then the father announces &lt;em&gt;m&lt;/em&gt;. Now it&amp;rsquo;s common knowledge that &lt;em&gt;k&lt;/em&gt; ≥ 1:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;After father announces m:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  k ∈ [2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  b knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    k ∈ [1, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;      k ∈ [1, 3]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The father asks the question, &amp;ldquo;can you prove you&amp;rsquo;re muddy?&amp;rdquo;, once:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;After father announces m, and q=1:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  k ∈ [2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  b knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;    k ∈ [2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      k ∈ [1, 3]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Child &lt;em&gt;c&lt;/em&gt; says &amp;ldquo;no,&amp;rdquo; which must mean it sees a muddy child. Child &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; knows &lt;em&gt;c&lt;/em&gt; is muddy as well, so &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; knows &lt;em&gt;k&lt;/em&gt; ≥ 2.&lt;/p&gt;
&lt;p&gt;The father asks a second time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;After father announces m, and q=2:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;  k ∈ [3, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  b knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    k ∈ [2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c knows:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      k ∈ [1, 3]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since &lt;em&gt;b&lt;/em&gt; said &amp;ldquo;no,&amp;rdquo; and &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; knew &lt;em&gt;k&lt;/em&gt; ≥ 2, &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;b&lt;/em&gt; sees 2 muddy children. Child &lt;em&gt;a&lt;/em&gt; also knows &lt;em&gt;b&lt;/em&gt; is muddy, so &lt;em&gt;a&lt;/em&gt; knows &lt;em&gt;k&lt;/em&gt; ≥ 3. Since &lt;em&gt;a&lt;/em&gt; only sees 2 muddy children, &lt;em&gt;a&lt;/em&gt; knows the third muddy child is itself and &lt;em&gt;k&lt;/em&gt; = 3. The third time the father asks, &lt;em&gt;a&lt;/em&gt; says &amp;ldquo;yes.&amp;rdquo; The other muddy children are reasoning identically to &lt;em&gt;a&lt;/em&gt;, so they also say &amp;ldquo;yes.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/princess-bride.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;The battle of wits in The Princess Bride&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;The battle of wits in The Princess Bride.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;k&lt;/em&gt; muddy children&lt;/h2&gt;

&lt;p&gt;We&amp;rsquo;ve seen that for each &lt;em&gt;k&lt;/em&gt; from 1 to 3, when the father asks the question for the &lt;em&gt;k&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt; time, all muddy children answer &amp;ldquo;yes&amp;rdquo; and the others answer &amp;ldquo;no.&amp;rdquo; Let&amp;rsquo;s say &lt;em&gt;a&lt;/em&gt; sees 3 muddy children and assumes it has no mud on its own face, i.e. it assumes &lt;em&gt;k&lt;/em&gt; = 3. If the father asks 3 times and all children still answer &amp;ldquo;no,&amp;rdquo; then &lt;em&gt;a&lt;/em&gt; knows its assumption was false, so &lt;em&gt;a&lt;/em&gt; knows it&amp;rsquo;s muddy and &lt;em&gt;k&lt;/em&gt; = 4. Child &lt;em&gt;a&lt;/em&gt; then correctly answers &amp;ldquo;yes&amp;rdquo; to the 4&lt;sup&gt;th&lt;/sup&gt; question. Same for the other children. And so on for all &lt;em&gt;k&lt;/em&gt; &amp;gt; 4, inductively.&lt;/p&gt;
&lt;h1&gt;A hierarchy of states of knowledge&lt;/h1&gt;

&lt;p&gt;We&amp;rsquo;ve seen a weird phenomenon, where the father&amp;rsquo;s announcement of a fact that everyone already knows somehow gives the children useful information. I explained to myself and to you how this works in the muddy children puzzle. Halpern and Moses explain it in general, by defining a hierarchy of states of knowledge. (I think this is based on &lt;a href=&#34;https://en.wikipedia.org/wiki/Common_knowledge_(logic)&#34;&gt;prior work in philosophy&lt;/a&gt;, and the authors are porting it to computer science.) To begin, they introduce the notation:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-2b2ca63c5e7138679c0f6143f264cb57442827ea489153f9fe1bc67ff3031a4c.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-2b2ca63c5e7138679c0f6143f264cb57442827ea489153f9fe1bc67ff3031a4c.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_i \varphi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;This is read, “&lt;em&gt;i&lt;/em&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;.” Some agent (or process or whatever) &lt;em&gt;i&lt;/em&gt; knows a fact &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; (Greek letter &amp;ldquo;phi&amp;rdquo; for &amp;ldquo;fact&amp;rdquo;). I&amp;rsquo;ll discuss the authors&amp;rsquo; definition of knowledge later, it&amp;rsquo;s terrific, stay tuned! For now, let&amp;rsquo;s just say an agent&amp;rsquo;s knowledge can depend only on the agent&amp;rsquo;s local history, i.e. its initial state and the actions it&amp;rsquo;s taken and observed. Also, knowledge is always true. A &lt;em&gt;belief&lt;/em&gt; can be false, but if an agent &lt;em&gt;knows&lt;/em&gt; &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;, then &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is a true fact.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the authors&amp;rsquo; hierarchy of knowledge, from weakest to strongest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-b51468a193d61576f54d9d2375f873b752a4bbd3e8e0f2f4106310958fe782ba.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-b51468a193d61576f54d9d2375f873b752a4bbd3e8e0f2f4106310958fe782ba.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;D_G \varphi&#34;&gt;
&lt;/picture&gt;     “the group &lt;em&gt;G&lt;/em&gt; has distributed knowledge of &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;”&lt;br&gt;&lt;br&gt;A fact &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is distributed knowledge if someone with a global view could infer &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; from everything known by every agent in some group &lt;em&gt;G&lt;/em&gt;, even if no individual agent in &lt;em&gt;G&lt;/em&gt; knows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-630941185931f823d4d15178f7eedbdbd6f561435cb8a5c4b5d77b49885a418b.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-630941185931f823d4d15178f7eedbdbd6f561435cb8a5c4b5d77b49885a418b.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;S_G \varphi&#34;&gt;
&lt;/picture&gt;     “someone in &lt;em&gt;G&lt;/em&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;”&lt;br&gt;&lt;br&gt;Defined as: &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-2b2ca63c5e7138679c0f6143f264cb57442827ea489153f9fe1bc67ff3031a4c.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-2b2ca63c5e7138679c0f6143f264cb57442827ea489153f9fe1bc67ff3031a4c.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_i \varphi&#34;&gt;
&lt;/picture&gt; for some &lt;em&gt;i&lt;/em&gt; in &lt;em&gt;G&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-97db64e56179153b2030f4a3d22de6f04d29d165e6caaee9aaeb8e24e297a313.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-97db64e56179153b2030f4a3d22de6f04d29d165e6caaee9aaeb8e24e297a313.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G \varphi&#34;&gt;
&lt;/picture&gt;     “everyone in &lt;em&gt;G&lt;/em&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;”&lt;br&gt;&lt;br&gt;Defined as: &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-2b2ca63c5e7138679c0f6143f264cb57442827ea489153f9fe1bc67ff3031a4c.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-2b2ca63c5e7138679c0f6143f264cb57442827ea489153f9fe1bc67ff3031a4c.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_i \varphi&#34;&gt;
&lt;/picture&gt; for all &lt;em&gt;i&lt;/em&gt; in &lt;em&gt;G&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-cc8abdd4bf430f4dc54fb0799e878d9e071bc09217ded56347be211e2fe538b9.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-cc8abdd4bf430f4dc54fb0799e878d9e071bc09217ded56347be211e2fe538b9.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G^k \varphi&#34;&gt;
&lt;/picture&gt;     “&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-b607a74a7267e802fd44dc5acfec2c00fe224f3ab4a25f242a202478c9e9a162.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-b607a74a7267e802fd44dc5acfec2c00fe224f3ab4a25f242a202478c9e9a162.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E^k&#34;&gt;
&lt;/picture&gt;-&lt;em&gt;knowledge&lt;/em&gt; in &lt;em&gt;G&lt;/em&gt;”&lt;br&gt;&lt;br&gt;Everyone in &lt;em&gt;G&lt;/em&gt; knows that everyone in &lt;em&gt;G&lt;/em&gt; knows that &amp;hellip; everyone in &lt;em&gt;G&lt;/em&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;, where &amp;ldquo;everyone in &lt;em&gt;G&lt;/em&gt; knows that&amp;rdquo; is repeated &lt;em&gt;k&lt;/em&gt; times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-58cef3ef72cb48756580bc9f2689d04f439c598eb8c81bd1717c3f8fd7eba368.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-58cef3ef72cb48756580bc9f2689d04f439c598eb8c81bd1717c3f8fd7eba368.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C_G \varphi&#34;&gt;
&lt;/picture&gt;     “&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; is &lt;em&gt;common knowledge&lt;/em&gt; in &lt;em&gt;G&lt;/em&gt;”&lt;br&gt;&lt;br&gt;Defined as, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-cc8abdd4bf430f4dc54fb0799e878d9e071bc09217ded56347be211e2fe538b9.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-cc8abdd4bf430f4dc54fb0799e878d9e071bc09217ded56347be211e2fe538b9.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G^k \varphi&#34;&gt;
&lt;/picture&gt; for all &lt;em&gt;k&lt;/em&gt; ≥ 1.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The authors point out, using this framework, that when the muddy children puzzle begins, &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-a95c63a435fd660b906530fecb78989355bd93307eccb168aba7fa8acfddc73a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-a95c63a435fd660b906530fecb78989355bd93307eccb168aba7fa8acfddc73a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G^{k-1} m&#34;&gt;
&lt;/picture&gt; is true. E.g., if there are two muddy children, then everyone knows &lt;em&gt;m&lt;/em&gt;. If there are three muddy children, everyone knows that everyone knows &lt;em&gt;m&lt;/em&gt;, because everyone sees at least 2 muddy children, so everyone knows everyone else sees at least 1 muddy child. But to &lt;em&gt;solve&lt;/em&gt; the puzzle, they must upgrade their knowledge from &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-a95c63a435fd660b906530fecb78989355bd93307eccb168aba7fa8acfddc73a.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-a95c63a435fd660b906530fecb78989355bd93307eccb168aba7fa8acfddc73a.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G^{k-1} m&#34;&gt;
&lt;/picture&gt; to &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f57d032309ee092385638b38fef3536340b9f44aa3069b094176a9c418733c3d.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f57d032309ee092385638b38fef3536340b9f44aa3069b094176a9c418733c3d.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G^k m&#34;&gt;
&lt;/picture&gt;, which is what the father&amp;rsquo;s announcement does. (The father&amp;rsquo;s announcement goes farther, making &lt;em&gt;m&lt;/em&gt; common knowledge, but all he &lt;em&gt;must&lt;/em&gt; do is upgrade &lt;em&gt;m&lt;/em&gt; by one level, to &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f57d032309ee092385638b38fef3536340b9f44aa3069b094176a9c418733c3d.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f57d032309ee092385638b38fef3536340b9f44aa3069b094176a9c418733c3d.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G^k m&#34;&gt;
&lt;/picture&gt;.)&lt;/p&gt;
&lt;p&gt;This is a useful way to think about nodes in a distributed system: each has limited knowledge, but there is distributed knowledge implicit in the whole system. To correctly take certain actions, nodes need a certain level of knowledge or higher. Nodes exchange messages to promote their knowledge up the hierarchy. Each level in the hierarchy implies all the lower levels:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-0019cc31054281c200f7dafb2720ef4f98bebcae93c6d80698af5b29a285f051.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-0019cc31054281c200f7dafb2720ef4f98bebcae93c6d80698af5b29a285f051.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;C_G \varphi \Longrightarrow ... \Longrightarrow E_G^{k+1} \varphi \Longrightarrow E_G^k \varphi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;
&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-7729f9959f5d5e17418d6199f50703b89cbea3cf1e682cf19f4c3dd67b9aa0b2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-7729f9959f5d5e17418d6199f50703b89cbea3cf1e682cf19f4c3dd67b9aa0b2.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\Longrightarrow ... \Longrightarrow E_G \varphi \Longrightarrow S_G \varphi \Longrightarrow D_G \varphi&#34;&gt;
&lt;/picture&gt;&lt;h1&gt;Knowledge hierarchies in Raft&lt;/h1&gt;

&lt;p&gt;Halpern and Moses don&amp;rsquo;t talk much about actual distributed protocols, but I kept thinking about Raft—how does the Raft protocol look if it&amp;rsquo;s recast as a flow of knowledge?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say a Raft leader creates a log entry. I&amp;rsquo;ll call the fact that the entry exists &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;, and right now only the leader knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-1.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-1.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;The leader sends the log entry to its two followers, but they haven&amp;rsquo;t acknowledged it yet:&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-2.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-2.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;Now &lt;strong&gt;everyone&lt;/strong&gt; knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;, but the leader doesn&amp;rsquo;t know any follower knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;. Let&amp;rsquo;s say &lt;em&gt;G&lt;/em&gt; is the set of all nodes and &lt;em&gt;F&lt;/em&gt; is the set of followers. Using Halpern and Moses&amp;rsquo;s notation:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-97db64e56179153b2030f4a3d22de6f04d29d165e6caaee9aaeb8e24e297a313.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-97db64e56179153b2030f4a3d22de6f04d29d165e6caaee9aaeb8e24e297a313.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G \varphi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;
&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-253f819a6c82fbc301b0c663d13118b36a1216ea0b7cf61e295b28a6b4a38e39.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-253f819a6c82fbc301b0c663d13118b36a1216ea0b7cf61e295b28a6b4a38e39.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\neg K_{leader} S_F \varphi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Let&amp;rsquo;s call the fact &amp;ldquo;the entry is durable&amp;rdquo; &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt;, the Greek letter psi. This fact is true if the entry is replicated to at least a majority of nodes, so it&amp;rsquo;s certainly true if it&amp;rsquo;s replicated by all nodes—that is, if everyone knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt; then &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; is true. But currently only God can see that all nodes have the entry, so &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; is &lt;strong&gt;distributed&lt;/strong&gt; knowledge.&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f90a171282a358b8a266142a08c43d49ed17aaf016970c35c3bde0fb3a4980b0.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f90a171282a358b8a266142a08c43d49ed17aaf016970c35c3bde0fb3a4980b0.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G \varphi \Longrightarrow D_G \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;
&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-696a35732b704f4d60fbfd0e500d574ce097f906e393a4f6ac59d605b5f284f3.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-696a35732b704f4d60fbfd0e500d574ce097f906e393a4f6ac59d605b5f284f3.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\neg S_G \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Then the leader receives an acknowledgment.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-3.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-3.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;We can say the leader knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; if it knows any follower knows &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-959f488598ceedf44cf203da9022bbd83509805891a8286cb0fbfcbe8a41f932.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\varphi&#34;&gt;
&lt;/picture&gt;:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-c592a67f281aff041bf4035ffa2e4f98544a5541da70aed870c1dda606aef665.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-c592a67f281aff041bf4035ffa2e4f98544a5541da70aed870c1dda606aef665.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;K_{leader} \psi \Longleftarrow K_{leader} S_F \varphi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;(This is true because the leader + one follower is a majority. If there were more than 3 nodes we&amp;rsquo;d need a different rule.) Now &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; has been upgraded, from &lt;strong&gt;distributed&lt;/strong&gt; knowledge to something that &lt;strong&gt;someone&lt;/strong&gt; knows:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-d35098203db42f9782bac599e04e8576256f7c2b8e2b94a7f617575d07fa9b07.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-d35098203db42f9782bac599e04e8576256f7c2b8e2b94a7f617575d07fa9b07.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;S_G \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;The leader tells the followers that the entry is committed.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-4.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/raft-knowledge-4.png&#34;&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;p&gt;Now &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; has been upgraded again, from a fact that &lt;strong&gt;someone&lt;/strong&gt; knows to a fact that &lt;strong&gt;everyone&lt;/strong&gt; knows.&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-0ff846268b18e94c2b960f8fa875569e61a382170e92aabe27a299a96e85063b.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-0ff846268b18e94c2b960f8fa875569e61a382170e92aabe27a299a96e85063b.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;E_G \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;But (according to the &lt;a href=&#34;https://raft.github.io/&#34;&gt;Raft paper&lt;/a&gt;) followers don&amp;rsquo;t tell the leader which entries they know are committed, so &lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f301f970eb09c336d31954a22511ffad807e5a02b0371810267307048c281891.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\psi&#34;&gt;
&lt;/picture&gt; doesn&amp;rsquo;t become something that everyone knows that everyone knows, much less &lt;strong&gt;common&lt;/strong&gt; knowledge.&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-86a7e458981ae12c4c0bca0f61e292b280f116575959d7c972fac657257e7c11.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-86a7e458981ae12c4c0bca0f61e292b280f116575959d7c972fac657257e7c11.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\neg E_G^2 \psi&#34;&gt;
&lt;/picture&gt;&lt;p&gt;
&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f741677e536befc2e85017131e5095e4809e34903ff2015e2121535b77fcd6b5.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/formula-f741677e536befc2e85017131e5095e4809e34903ff2015e2121535b77fcd6b5.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\neg C_G \psi&#34;&gt;
&lt;/picture&gt;&lt;h1&gt;Onward&lt;/h1&gt;

&lt;p&gt;This covers the first three sections of the paper. We&amp;rsquo;ve seen Halpern and Moses&amp;rsquo;s knowledge hierarchy, and how it&amp;rsquo;s useful for analyzing the muddy children puzzle and Raft. Next, we&amp;rsquo;ll briefly visit the famous Byzantine Generals, who are still trying to decide when to attack their common enemy. After that we&amp;rsquo;ll get into the meat of the paper, which offers a remarkably satisfying definition of knowledge.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-common-knowledge-part-1/lakshmi-saraswati.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Hindu goddesses Lakshami and Saraswati playing castanets and a tambura. Watercolour drawing.&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://wellcomecollection.org/works/bxw2ajua/images?id=j6au7hbk&#34;&gt;Lakshmi and Saraswati&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Optimizing a Meditation Retreat with a SAT Solver</title>
      <link>https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/</link>
      <pubDate>Tue, 29 Jul 2025 22:00:52 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20240104_000096740001.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Well, a SAT solver can optimize the retreat &lt;em&gt;schedule&lt;/em&gt;. A meditation retreat as a whole is a living creature, beyond any individual&amp;rsquo;s control. But I can provide a schedule that functions smoothly and reduces distractions. I used Python and &lt;em&gt;integer programming&lt;/em&gt;, a technique for mathematical optimization.&lt;/p&gt;
&lt;h1&gt;The problem&lt;/h1&gt;

&lt;p&gt;The &lt;a href=&#34;https://villagezendo.org&#34;&gt;Village Zendo&lt;/a&gt; holds two weeklong Zen meditation retreats every summer, and one every winter. An essential part of Zen retreats is &lt;em&gt;interviews&lt;/em&gt; (&amp;ldquo;dokusan&amp;rdquo; or &amp;ldquo;daisan&amp;rdquo;) between students and teachers, where students can ask questions or present their understanding of &lt;a href=&#34;https://en.wikipedia.org/wiki/Koan&#34;&gt;koans&lt;/a&gt;. At a Village Zendo retreat, we want each student to see one teacher per day. We assign students to groups, and call each group out of the meditation hall to see a teacher at the start of a meditation period. Certain students serve as &lt;em&gt;jishas&lt;/em&gt;; they&amp;rsquo;re in charge of calling a group of students and escorting them to the door of the interview room. The students in the group line up, and enter the room one at time for a short conversation with the teacher.&lt;/p&gt;
&lt;p&gt;Besides ensuring one interview per student per day, there are many other rules and goals. Dear reader, if you&amp;rsquo;re a programmer, you are probably already beginning to model this as an optimization problem. The objects in the system are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Students. Some of the students are jishas, and some have other jobs which affect the optimization problem.&lt;/li&gt;
&lt;li&gt;Teachers. This summer there were 8 teachers for 35 students.&lt;/li&gt;
&lt;li&gt;Rooms. This summer there were 2 interview rooms. Our abbot used his own room for interviews.&lt;/li&gt;
&lt;li&gt;Groups. Each student belongs to one group.&lt;/li&gt;
&lt;li&gt;Slots. There are time slots for interviews in the morning, afternoon, and evening meditation periods.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20240104_000096740003.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The challenge is to assign students to groups, then schedule jishas to bring groups to teachers during slots, so that all groups see a teacher once a day. Ideally, a group sees a variety of teachers during the week, and the workload is spread evenly among the teachers, and evenly among the jishas.&lt;/p&gt;
&lt;p&gt;For the last few years my fellow Zen student, the mathematician &lt;a href=&#34;https://www.artofmathematics.org/users/volker-ecke&#34;&gt;Volker Kenko Ecke&lt;/a&gt;, had been manually solving this problem for each retreat with a big spreadsheet. He assigned students to groups, groups to slots, etc. by hand, then wrote formulas to check that all the rules and goals were satisfied. It was impressive, but I felt the itch to automate it. This is the difference between mathematicians and programmers.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20240104_000096740005.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Translating the problem into SAT&lt;/h1&gt;

&lt;p&gt;Last summer I offered to write a program. This was not a problem for LLMs. I vaguely knew that a SAT solver was the natural tool. I chose Google&amp;rsquo;s open source &lt;a href=&#34;https://developers.google.com/optimization/cp/cp_solver&#34;&gt;CP-SAT solver&lt;/a&gt;, which solves &lt;em&gt;integer programming&lt;/em&gt; problems: optimization problems involving assigning integers to variables, constrained by simple formulas. It has a nice Python API.&lt;/p&gt;
&lt;p&gt;To translate the interview scheduling problem into Solverese, I made two big binary matrices. One is group assignments: the columns are groups, the students are rows, and there&amp;rsquo;s a 1 wherever a student is assigned to a group and 0 everywhere else.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;students&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;Student&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;last&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Davis&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;first&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Jesse&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;dharma_name&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Jiryu&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;Student&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;last&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Cohen&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;first&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Steven&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;dharma_name&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Jindai&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# ... and so on ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;MAX_STUDENTS_PER_GROUP&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;groups&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;Group&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;number&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ceil&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#204a87&#34;&gt;len&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;students&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;MAX_STUDENTS_PER_GROUP&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;ortools.sat.python&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;cp_model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;cp_model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;CpModel&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Matrix of bools, each is true if a student is part of a group.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;group_assignments&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;):&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;new_bool_var&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;display_name&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt; group &lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;number&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;students&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;groups&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, you create a variable with &lt;code&gt;model.new_bool_var&lt;/code&gt;. Later I&amp;rsquo;ll show you how these are used in constraints like &amp;ldquo;each student is in exactly one group.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Besides the two-dimensional group assignment matrix, the other binary matrix has &lt;em&gt;four&lt;/em&gt; dimensions: teachers, jishas, groups, and slots. If a certain jisha takes a certain group to see a certain teacher at a certain time slot, then that cell of the matrix has a 1, otherwise 0. I called these combinations &lt;em&gt;shifts&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Matrix of bools, each is true if a teacher, jisha, and group is assigned&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# to a slot. # Key is a Shift object, value is a variable to which the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# solver assigns a value.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;shifts&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;Shift&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;j&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;display_name&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;):&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;new_bool_var&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;j&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;product&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;teachers&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;jishas&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;groups&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;slots&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Creating a schedule is equivalent to assigning 1s and 0s to the cells in these two matrices, in a way that satisfies all the constraints and optimizes some goal. So I started adding constraints.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20240104_000096740006.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Expressing constraints&lt;/h1&gt;

&lt;p&gt;First, a student can only be in one group. That means the &lt;em&gt;sum&lt;/em&gt; of group assignments along a student row is 1:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Each student is in 1 group.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;students&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;add&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#204a87&#34;&gt;sum&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;group_assignments&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)]&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;groups&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And each group can only have MAX_STUDENTS_PER_GROUP members:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;groups&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Enforce MAX_STUDENTS_PER_GROUP.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;add&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#204a87&#34;&gt;sum&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;group_assignments&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)]&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;st&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;students&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;MAX_STUDENTS_PER_GROUP&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Giving interviews is tiring for teachers, they should only have one shift per day. For each teacher/day combination, I create an integer variable with &lt;code&gt;model.new_int_var&lt;/code&gt;, constrain it to equal the number of shifts for that teacher on that day, and also constrain it to be less than or equal to one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;days&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;list&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#204a87&#34;&gt;sorted&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#204a87&#34;&gt;set&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;day&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;slots&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;teachers&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;n_teacher_shifts&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;sum&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shifts&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;items&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;teacher&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;d&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;days&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;shifts_today&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;new_int_var&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;add&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;shifts_today&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;sum&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shifts&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;items&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;teacher&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;slot&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;day&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;d&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;add&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;shifts_today&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a dozen other rules, and they&amp;rsquo;re all similarly easy to express. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A teacher can only see one group per slot.&lt;/li&gt;
&lt;li&gt;The abbot prefers to do interviews only in evening slots.&lt;/li&gt;
&lt;li&gt;The abbot has a particular jisha; other jishas are free-floating.&lt;/li&gt;
&lt;li&gt;Jishas should only do one shift per day.&lt;/li&gt;
&lt;li&gt;Each group should get one interview per day, and see each teacher only once during the retreat.&lt;/li&gt;
&lt;li&gt;Since there are two interview rooms, so only two teachers can do interviews per slot (but the abbot uses his own room).&lt;/li&gt;
&lt;li&gt;Some students are &lt;em&gt;monitors&lt;/em&gt;. At least one monitor should be in the meditation hall at all times.&lt;/li&gt;
&lt;li&gt;&amp;hellip; and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;Once I banged the model into shape, I told the solver to find a solution:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;solver&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;cp_model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;CpSolver&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Determinism.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;solver&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;parameters&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;random_seed&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Stop trying after about 5 minutes - but use a virtual clock&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# that always stops after the same number of operations.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;solver&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;parameters&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;max_deterministic_time&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;solver&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;parameters&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;num_workers&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;solver&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;solve&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &amp;ldquo;determinism&amp;rdquo; arguments ensure I get the same solution each time I run the same code, which helps me debug.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20250109_000410970004.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Refinements&lt;/h1&gt;

&lt;p&gt;Once the program basically worked, I generated a PDF of the schedule and the group assignments, and made some improvements. For example, could I reduce the jishas&amp;rsquo; workload? A jisha leaves the meditation hall for two reasons: if she leads a group of students to see a teacher, and also if her &lt;em&gt;own&lt;/em&gt; group is called to see a teacher. On past retreats, a jisha might lead Group 1 to Teacher A, while her own Group 2 was seeing Teacher B. This led to hectic improvisation; not very peaceful. I called these &amp;ldquo;jisha conflicts&amp;rdquo; and told my program to eliminate them. This was a bit complex. Recall that every &amp;ldquo;shift&amp;rdquo; is a combination of teacher, jisha, group, and slot. So for every shift S1, and every other shift S2 with the same slot but a different group, either S1&amp;rsquo;s jisha isn&amp;rsquo;t in S2&amp;rsquo;s group, or S2&amp;rsquo;s group isn&amp;rsquo;t doing interview during this slot.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Avoid jisha conflicts: when a jisha is serving one teacher while&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# their group is called # for another. To prevent conflicts, if a&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# jisha j is assigned to a shift it implies # j isn&amp;#39;t in groups seen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# by other teachers in that slot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shift&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shifts&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;j&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;product&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;teachers&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;jishas&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;groups&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shift&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;group&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Another shift with the same time slot but different group.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;other_shift&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shifts&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;Shift&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;t&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;j&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;display_name&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;shift&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;slot&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# If shift is true, then other_shift is false or shift&amp;#39;s jisha&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# isn&amp;#39;t in other_shift&amp;#39;s group.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;j_in_other_group&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;group_assignments&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;name2student&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;shift&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;jisha_name&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;],&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;g&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;add_bool_or&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;j_in_other_group&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;negated&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;other_shift&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;negated&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;])&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;only_enforce_if&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;shifts&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;shift&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was slightly mind-bending to code, but it only took a few hours of thinking and reading the CP-SAT docs. It eliminated a historical source of stress for the jishas.&lt;/p&gt;
&lt;p&gt;I wondered, could I simplify jishas&amp;rsquo; lives even more? For each jisha I calculated the number of &lt;em&gt;extra trips&lt;/em&gt; she took out of the meditation hall: i.e., the number of times she led out a group of students besides her own. I told the model to minimize the sum of the squares of the extra trips, so it would try to both reduce the workload and spread it evenly. It&amp;rsquo;s &lt;a href=&#34;https://github.com/ajdavis/interview-schedule-anonymized/blob/7f76a4c09e8404e6fa98e12195ea35d09bda269e/summer-schedule.py#L407-L435&#34;&gt;about 20 lines of code&lt;/a&gt;, and it reduced the average jisha&amp;rsquo;s time out of the meditation hall from 3 trips to 0 or 1. This was another big improvement over past retreats.&lt;/p&gt;
&lt;p&gt;As I said, a SAT solver can&amp;rsquo;t optimize your meditation, but it can help create good conditions!&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20250109_000410970005.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Minimizing changes&lt;/h1&gt;

&lt;p&gt;I&amp;rsquo;ve been on dozens of Zen retreats, and I&amp;rsquo;ve been involved in organizing some of them. Constant change is the norm, just like Buddha taught. People join late, leave early, get sick. And maybe we realize partway through the retreat that we should change the program, add a new constraint or goal we didn&amp;rsquo;t think of until we put the schedule to use. Once my program had made a schedule, I knew it wouldn&amp;rsquo;t be &amp;ldquo;final.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So, each time the program runs, it checks the time. If the retreat hasn&amp;rsquo;t started, it can make a new schedule from scratch, freely reassigning groups and shifts. Once the retreat starts, the groups are permanent, and the program must do its best to meet its goals without changing group assignments. Shifts are malleable so long as they&amp;rsquo;re in the future, but the program knows it can&amp;rsquo;t change the past. So if we suddenly decided some group of students must see the abbot, the program can satisfy that only by changing shifts starting &lt;em&gt;after&lt;/em&gt; the current time.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20250111_000410970009.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Retrospective&lt;/h1&gt;

&lt;p&gt;I&amp;rsquo;ve scheduled four weeklong retreats with this program, improving the code each time. (I put &lt;a href=&#34;https://github.com/ajdavis/interview-schedule-anonymized&#34;&gt;a version with participants&amp;rsquo; names redacted on GitHub&lt;/a&gt;.) I&amp;rsquo;ve learned what questions to ask the registrar, the abbot, and the retreat managers before the retreat begins, so I create a schedule that fits their requirements. I still have to personally edit and run the code—this is a script, not a software product—but it&amp;rsquo;s becoming routinized.&lt;/p&gt;
&lt;p&gt;The program generates a nicely formatted PDF of the schedule, that we can print out and post on the wall at the retreat center:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/schedule.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The PDF includes a timestamp and a fingerprint (a hash of all the variable assignments), to avoid confusion when there are multiple printed copies floating around among the retreat staff.&lt;/p&gt;
&lt;p&gt;The program also generates a PDF of group assignments, and a card with each student&amp;rsquo;s individual schedule. It also outputs a text file with statistics, like how many shifts each teacher and jisha has, so that the retreat managers and I can evaluate the quality of the solution.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20250109_000410900007.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The smartest decisions I made were the first ones: to encode the schedule as two boolean matrices and use a SAT solver. Now, I hardly ever have to think, &amp;ldquo;How do I solve this problem?&amp;rdquo; I only have to figure out, &amp;ldquo;How do I express this constraint or goal in terms of CP-SAT?&amp;rdquo; Furthermore, I spend little time checking the solution. If I code a new constraint, then I spend a few minutes checking that the latest schedule satisfies it as I expect, but after that I mostly trust the program. Even when I make a consequential change, like adding a constraint or removing a day, I&amp;rsquo;m confident the program will rearrange the puzzle pieces to fit. If I had tried to write my own scheduling algorithm, changes would be much harder and riskier.&lt;/p&gt;
&lt;p&gt;I was particularly satisfied last week, when a teacher got sick and canceled on the first day of retreat. With the old handwritten spreadsheet, I think it would&amp;rsquo;ve been hard to adapt the schedule, and there wouldn&amp;rsquo;t be enough time to make a really good one. But now that it&amp;rsquo;s automated, I simply deleted the sick teacher and reran the program. It found no solution, so I relaxed a constraint—now some teachers will have to take two shifts per day—and voilà, a perfectly good new schedule popped out 5 minutes later.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/optimizing-a-meditation-retreat-with-a-sat-solver/ajdavis_20250111_000349610010.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images: My photos from Village Zendo retreats. All rights reserved.&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Aerial Silks in the Gunks</title>
      <link>https://emptysqua.re/blog/aerial-silks-millbrook/</link>
      <pubDate>Mon, 28 Jul 2025 15:10:02 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/aerial-silks-millbrook/</guid>
      
      
      
      
      <description>&lt;p&gt;July 26, 2025. &lt;a href=&#34;https://www.instagram.com/mntnbug/&#34;&gt;Kelsey Roman&lt;/a&gt; performing with aerial silks suspended from the &lt;a href=&#34;https://www.mountainproject.com/route/123342477/nectar-vector&#34;&gt;Nectar Vector&lt;/a&gt; climb on Millbrook, a cliff in the Gunks. Rock guide &lt;a href=&#34;https://www.advancedrockcraft.com/&#34;&gt;Dustin Portzline&lt;/a&gt; is above, managing the rigging and holding a parasol.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/aerial-silks-millbrook/aerial-silks-millbrook-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>RIP Sojourner</title>
      <link>https://emptysqua.re/blog/rip-sojourner/</link>
      <pubDate>Mon, 07 Jul 2025 22:16:48 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/rip-sojourner/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/rip-sojourner/ajdavis_20240109_1010.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Sojourner Truth Armstrong Davis, Jennifer&amp;rsquo;s and my Chinese dwarf hamster, died last night. She had lived with a cancerous affliction of the womb, a malady common to her kind, for several months. We had expected her demise soon after the first signs—hamsters rush headlong through all activities, and life itself—but she endured for a season, bearing her disease with the same insouciance she displayed in all circumstances.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://emptysqua.re/blog/sojourner/&#34;&gt;Soji joined our household a year and a half ago&lt;/a&gt;, leaving behind her infant siblings at PetCo to commence a solitary existence. &lt;a href=&#34;https://emptysqua.re/blog/rip-shirley/&#34;&gt;Experience had informed us that sisterly companionship harbored risk&lt;/a&gt;, so Soji&amp;rsquo;s house was a hermitage from the day of her arrival. Her society consisted chiefly of two humans, who held her in the deepest affection. Whether our fondness was reciprocated in equal measure &lt;a href=&#34;https://emptysqua.re/blog/which-beings-are-sentient/&#34;&gt;is a matter of speculation&lt;/a&gt;; yet she would settle contentedly into our hands, emitting her customary clicks and chirps, as if to signify approval.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/rip-sojourner/ajdavis_20230929_0239.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The original Sojourner Truth was born a few towns distant from us, and in her sixtieth year commenced a tour of the nation, speaking against slavery. Sojourner the hamster was also an outspoken creature. Most afternoons she stationed herself within her modest wooden dwelling, and from this redoubt delivered an oratory of squeaks, clucks, and chirps loud enough to be heard from the adjoining room.&lt;/p&gt;
&lt;p&gt;Unlike her namesake, Soji addressed an audience of none. No observer, however, would ever mistake her isolation for loneliness. She was happier than any hamster we had known before, for which the likeliest explanation was her assured seclusion. She was a connoisseur of aloneness. Assured that her territory was uncontested, and eternally unencumbered by any relative or rival, she spent her days in appreciation of her boundless solitude.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/rip-sojourner/ajdavis_20240109_0974.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>From Python Programmer to Distributed Systems Researcher in 10 Years Without a PhD</title>
      <link>https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/</link>
      <pubDate>Mon, 30 Jun 2025 22:29:35 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/</guid>
      
      
      
      
      <description>&lt;p&gt;Phil Eaton wrote &amp;ldquo;&lt;a href=&#34;https://notes.eatonphil.com/2025-02-15-from-web-developer-to-database-developer-in-10-years.html&#34;&gt;From Web Developer To Database Developer In 10 Years&lt;/a&gt;&amp;rdquo; recently. I was inspired to write my own version.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In high school, I wanted to be a modern dancer, with a company like &lt;a href=&#34;https://pilobolus.org/&#34;&gt;Pilobolus&lt;/a&gt; or &lt;a href=&#34;https://streb.org/company/&#34;&gt;Streb&lt;/a&gt;. But I also kind of wanted to be a programmer. Pixar&amp;rsquo;s Toy Story, the first 3D-animated feature film, was released when I was a high school sophomore. I was fascinated by this simulated world, revealed by simulated light to a simulated camera. How did they make it? I somehow learned (however I learned things before I had Internet access) that 3D graphics were rendered with linear algebra and C++. So I got books about both from my neighborhood library in Shaker Heights, Ohio. I don&amp;rsquo;t recall much about the linear algebra book, but the C++ book is a core memory: a giant glossy volume, with a copy of Borland Turbo C++ for DOS on a CD in a slip attached to the inside back cover. I installed it on the family computer, a Tandy with a 386 processor, which looked like this:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/tandy.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;When I installed Turbo C++, the IDE looked like this:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/turbo-c.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I taught myself basic C++, but on my own I didn&amp;rsquo;t manage to learn 3D graphics.&lt;/p&gt;
&lt;p&gt;Meanwhile, I reached my senior year of high school, and auditioned for the prestigious dance program at Ohio State University. I was admitted to the waitlist, but that was a superficial victory. I saw in that audition another man my age who was a true dancer. He was unselfconscious, emotional, natural, free. I would never dance like that. By the time I got a letter from OSU, saying I&amp;rsquo;d been accepted from the waitlist, I&amp;rsquo;d already decided to major in computer science and do 3D graphics for a living.&lt;/p&gt;
&lt;p&gt;(Is this what really happened? It&amp;rsquo;s been 28 years. This is how I remember and tell my life story now.)&lt;/p&gt;
&lt;p&gt;I got my CS degree from Oberlin College with a focus on 3D graphics. For some reason I didn&amp;rsquo;t apply to Pixar. Why didn&amp;rsquo;t you follow your dream, young Jesse? I ended up in Austin, Texas, working for a tiny company that made flight safety analysis software. At least I was doing 3D graphics: our software turned data from jets&amp;rsquo; &lt;a href=&#34;https://en.wikipedia.org/wiki/Quick_access_recorder&#34;&gt;flight recorders&lt;/a&gt; into 3D animations, so airlines could review near-accidents and adjust their procedures.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s fast-forward. I bounced around from &lt;a href=&#34;https://emptysqua.re/blog/yokoji-zmc-august-2019/&#34;&gt;a Zen monastery&lt;/a&gt; to &lt;a href=&#34;https://en.wikipedia.org/wiki/Amplify_(company)&#34;&gt;an NYC education startup&lt;/a&gt;, then freelanced for a few years, and joined MongoDB in 2011 (&amp;ldquo;10gen&amp;rdquo; at the time). I&amp;rsquo;d abandoned 3D graphics by now. I&amp;rsquo;d learned Python, and I used MongoDB at one of my freelance gigs—it was so much more fun than SQL, I thought the idea might have legs. MongoDB was unique in NYC at the time: the only company building technically demanding open-source software in the city. Google and Amazon hadn&amp;rsquo;t yet opened offices here. MongoDB hired me as a developer advocate promoting MongoDB to the Python community. They called me a &amp;ldquo;Python evangelist&amp;rdquo;—my mom loved the title, she said it sounded like I was a snake handler.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/snake-handling.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/File:Handling_serpents_at_the_Pentecostal_Church_of_God._(Kentucky)_by_Russell_Lee._-_NARA_-_541335.jpg&#34;&gt;Handling serpents at the Pentecostal Church of God&lt;/a&gt;, by Russell Lee&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I made myself a minor celebrity in the Python world, speaking at PyCon many years in a row and publishing a lot of popular Python content on this blog. But mostly I was an engineer. I worked on &lt;a href=&#34;https://pymongo.readthedocs.io/&#34;&gt;PyMongo&lt;/a&gt;, I created the async alternative Python driver &lt;a href=&#34;https://motor.readthedocs.io/&#34;&gt;Motor&lt;/a&gt;, and for a while I led development of our &lt;a href=&#34;https://mongoc.org/&#34;&gt;C&lt;/a&gt; and &lt;a href=&#34;https://www.mongodb.com/docs/languages/cpp/&#34;&gt;C++&lt;/a&gt; drivers. I was promoted a few times, to Staff Engineer. &lt;a href=&#34;https://emptysqua.re/blog/choosing-the-adventurous-route-video/&#34;&gt;After 7 years on the Drivers Team, I hit a plateau&lt;/a&gt;—my learning had slowed and I didn&amp;rsquo;t know how to get another promotion. So I decided to switch teams. I rotated through three MongoDB teams, trying each one for a month or two, until I settled on the Replication Team, which maintains MongoDB&amp;rsquo;s consensus protocol, deep in the core database server.&lt;/p&gt;
&lt;p&gt;The Replication Team transformed my career, because they read and wrote research papers about distributed systems. Before, I had deepened my knowledge by studying programming languages. Now, I was learning about much higher-level concepts: consensus, fault tolerance, causality, isolation and consistency. The team met regularly to read papers in our field, both new and old. I got excited and read more papers on my own, and joined &lt;a href=&#34;https://charap.co/category/reading-group/&#34;&gt;an external distributed systems reading group&lt;/a&gt;. I also met TLA+ for the first time. With two colleagues, I published &lt;a href=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/&#34;&gt;my first research paper&lt;/a&gt;, about testing the conformance between TLA+ specifications and implementations. (I&amp;rsquo;m still researching this topic, 5 years later.) I made a plan to read dozens of papers and a few textbooks; my boss and grandboss agreed that if I completed the curriculum I&amp;rsquo;d designed for myself, I&amp;rsquo;d be promoted to Senior Staff Engineer, and soon enough this actually happened.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/from-python-programmer-to-distributed-systems-researcher-in-10-years/cliff.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;a href=&#34;https://emptysqua.re/blog/after-244-days-off/&#34;&gt;In 2022 I took most of a year off to climb&lt;/a&gt;, and moved to New Paltz, &lt;a href=&#34;https://portfolio.emptysqua.re/rock-climbing&#34;&gt;a world class climbing mecca&lt;/a&gt;. While I was away, I considered my reentry. How could I contribute best to MongoDB? And how could I keep my calendar clear of team standups and planning meetings and all the other cruft that would prevent me from running out to the cliff when the weather was good? MongoDB had a small research group, almost entirely in Australia at the time, led by &lt;a href=&#34;https://scholar.google.com/citations?user=9-arDNQAAAAJ&amp;amp;hl=en&#34;&gt;Michael Cahill&lt;/a&gt;. I asked to join it when I returned, working four days a week at four fifths of my previous salary, and Michael assented. I was gambling with my career: I only had a BA, I had only published one paper, and my primary mentor would be on the other side of the world. On the other hand, I could use the writing and speaking skills I&amp;rsquo;d learned as a &amp;ldquo;Python evangelist,&amp;rdquo; and devote myself to the distributed systems theory I enjoyed. I returned to MongoDB in December 2022 as a Senior Staff Research Engineer.&lt;/p&gt;
&lt;p&gt;How did I get this coveted position—the freedom of academia with an industrial salary? I didn&amp;rsquo;t have an advanced degree, but I had some advantages. My previous boss supported my decision to transfer. I&amp;rsquo;d been at MongoDB for 11 years, a long time at any company, especially a fast-growing one like ours. The one paper I&amp;rsquo;d published was good, and accepted by a top conference. I&amp;rsquo;d not only read a lot of papers, I&amp;rsquo;d spread my knowledge by leading discussions of them in reading groups. And I came to Michael with ideas he liked, such as &lt;a href=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/&#34;&gt;predictive auto-scaling&lt;/a&gt; or improving the consistency of &lt;a href=&#34;https://www.mongodb.com/docs/manual/core/read-preference/&#34;&gt;MongoDB&amp;rsquo;s secondary reads&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s the story: that&amp;rsquo;s how I progressed from Python programmer to distributed systems researcher in ten years, without a PhD. Since then, a lot has happened. Michael Cahill retired soon after I joined the research group, and I was unmentored for a year. Luckily I was able to hire a famous expert, &lt;a href=&#34;https://muratbuffalo.blogspot.com/&#34;&gt;Murat Demirbas&lt;/a&gt;, to lead &lt;a href=&#34;https://www.mongodb.com/company/research/distributed-systems-research-group&#34;&gt;MongoDB&amp;rsquo;s distributed systems research&lt;/a&gt;. I&amp;rsquo;m still finding my stride in my new career, but at least I have guidance.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Taming Google Scholar Alerts</title>
      <link>https://emptysqua.re/blog/taming-google-scholar-alerts/</link>
      <pubDate>Mon, 23 Jun 2025 17:09:48 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/taming-google-scholar-alerts/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/taming-google-scholar-alerts/edward-bird.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;A grayscale ink drawing of a bearded old man writing with fountain pen and ink pot, wearing .&#34; title=&#34;&#34;&gt;&lt;p&gt;Since I joined &lt;a href=&#34;https://www.mongodb.com/company/research/distributed-systems-research-group&#34;&gt;MongoDB&amp;rsquo;s Distributed Systems Research Group&lt;/a&gt; a few years ago, I&amp;rsquo;ve relied on Google Scholar alerts to tell me when an author in my specialty publishes a new paper, or (much more often) when a new paper cites an author I&amp;rsquo;m interested in. Google Scholar can send me email alerts, but their frequency isn&amp;rsquo;t configurable: daily only. And if the same new paper is included in multiple alerts (e.g., it cites several authors I follow), Google Scholar won&amp;rsquo;t deduplicate it. It&amp;rsquo;s common for me to get a half-dozen emails in the morning about overlapping sets of papers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/ajdavis/google-scholar-alert-summary/&#34;&gt;I&amp;rsquo;ve made a little Google Apps Script automation that solves these problems for me&lt;/a&gt;. Now, I&amp;rsquo;ve configured a GMail filter so my daily Google Scholar alerts skip my inbox and don&amp;rsquo;t bother me. Once a week, my script summarizes and deduplicates all the alerts, then deletes them from my GMail archive. This turned a daily chore of cleaning up my inbox into a much easier weekly chore: skimming the titles of this week&amp;rsquo;s papers, in a single email.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://www.artic.edu/artworks/82683/bearded-scholar-writing&#34;&gt;Image by Edward Bird&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gunks, June 2025</title>
      <link>https://emptysqua.re/blog/gunks-june-2025/</link>
      <pubDate>Mon, 23 Jun 2025 10:13:58 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/gunks-june-2025/</guid>
      
      
      
      
      <description>&lt;p&gt;Climbing in the Gunks on June 21 and 22, 2025.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1167.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1173.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1230.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1264.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1266.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1342.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1347.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-june-2025/9M9A1358.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Genuine Climbing retreat for women with cancer</title>
      <link>https://emptysqua.re/blog/genuine-climbing-june-2025/</link>
      <pubDate>Sun, 15 Jun 2025 07:53:34 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/genuine-climbing-june-2025/</guid>
      
      
      
      
      <description>&lt;p&gt;June 2025. A &lt;a href=&#34;https://genuineclimbing.org/climbing-with-cancer-2/&#34;&gt;climbing retreat in the Gunks for women with cancer&lt;/a&gt; led by Anyssa Lucena of Genuine Climbing. Most of the women had never climbed outside, or never climbed at all. Their awe of the rock reminded me of when I fell in love with climbing, years ago. &lt;a href=&#34;https://www.ericbates.com/&#34;&gt;Eric Bates&lt;/a&gt; and a small crew were shooting a documentary of the retreat. He made &lt;a href=&#34;https://www.youtube.com/watch?v=BswG4bnk6To&#34;&gt;Five Dot Fun&lt;/a&gt;, one of my favorites; I was excited to meet him and watch him work.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-10.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-11.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-12.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-13.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-14.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-15.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/genuine-climbing-june-2025/genuine-climbing-16.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  


&lt;p&gt;Shot on a mix of digital and Portra 400.&lt;/p&gt;
&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Opus 40</title>
      <link>https://emptysqua.re/blog/opus-40/</link>
      <pubDate>Sat, 14 Jun 2025 10:20:00 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/opus-40/</guid>
      
      
      
      
      <description>&lt;p&gt;Opus 40, a sculpture park in Saugerties, New York, hand-built by Harvey Fite over 37 years from local bluestone.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/opus-40/opus-40-1.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/opus-40/opus-40-2.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/opus-40/opus-40-3.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/opus-40/opus-40-4.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/opus-40/opus-40-5.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Can We Rely On Timers For Distributed Algorithms?</title>
      <link>https://emptysqua.re/blog/timers-distributed-algorithms/</link>
      <pubDate>Thu, 05 Jun 2025 10:45:23 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/timers-distributed-algorithms/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/clock3.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Distributed systems people are suspicious of timers. Whenever we discuss an algorithm that requires two servers to measure the same duration, the first question is always, &amp;ldquo;Can we rely on timers?&amp;rdquo; I think the answer is &amp;ldquo;yes&amp;rdquo;, as long as you add a safety margin a little over 1/500&lt;sup&gt;th&lt;/sup&gt;.&lt;/p&gt;


&lt;h1&gt;Example: Leader Leases Rely on Timers&lt;/h1&gt;

&lt;p&gt;Leader-based consensus protocols like Raft try to elect one leader at a time, but it&amp;rsquo;s possible to have multiple leaders for a short period. (In theory, in a Raft group of 2&lt;em&gt;f&lt;/em&gt;+1 servers, as many as &lt;em&gt;f&lt;/em&gt; can be leaders at once!) In this situation, you risk violating &lt;a href=&#34;https://jepsen.io/consistency/models/read-your-writes&#34;&gt;read-your-writes&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A client updates some data on the current (highest-term) leader.&lt;/li&gt;
&lt;li&gt;That leader majority-replicates the change and acknowledges it to the client.&lt;/li&gt;
&lt;li&gt;The same client reads stale data from an old leader that hasn&amp;rsquo;t stepped down.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A Raft leader must always suspect that it&amp;rsquo;s been deposed, so it acts paranoid: for each query, it checks with a majority to confirm it&amp;rsquo;s still in charge (&lt;a href=&#34;https://raft.github.io/raft.pdf&#34;&gt;Raft paper&lt;/a&gt; §8). This guarantees read-your-writes at a high cost in communication latency.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;leader lease&lt;/em&gt; guarantees a single leader, but it relies on timers. In &lt;a href=&#34;https://github.com/ongardie/dissertation&#34;&gt;Diego Ongaro&amp;rsquo;s thesis&lt;/a&gt;, he proposes a simple lease mechanism for Raft: The leader starts a timer at time &lt;em&gt;t&lt;/em&gt;, and sends heartbeat messages to all its followers. Once a majority has responded, the leader knows they won&amp;rsquo;t vote for another leader until &lt;em&gt;t&lt;/em&gt; + &lt;em&gt;election timeout&lt;/em&gt; * &lt;em&gt;ε&lt;/em&gt;, where &lt;em&gt;ε&lt;/em&gt; is the maximum rate of clock drift. Here&amp;rsquo;s Figure 6.3 from Ongaro&amp;rsquo;s thesis:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/raft-lease.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;At MongoDB we made a more sophisticated lease algorithm that improves availability. Stay tuned for our research paper. But just like Ongaro&amp;rsquo;s, our algorithm depends on timers.&lt;/p&gt;
&lt;h1&gt;Why We Distrust Timers&lt;/h1&gt;

&lt;p&gt;In the classic &lt;a href=&#34;https://citeseerx.ist.psu.edu/document?repid=rep1&amp;amp;type=pdf&amp;amp;doi=efc1ff91bc301c3d6344cb308b4f619914a0e871&#34;&gt;&lt;em&gt;asynchronous system model&lt;/em&gt;&lt;/a&gt;, servers have no clocks and delays are unbounded. &lt;a href=&#34;https://www.the-paper-trail.org/post/2008-08-13-a-brief-tour-of-flp-impossibility/&#34;&gt;&lt;em&gt;FLP impossibility&lt;/em&gt;&lt;/a&gt; says that in this model, no consensus algorithm is guaranteed to make progress. I believe this means no protocol can both 1) always eventually elect one leader and 2) prevent multiple concurrent leaders. This is what makes Raft leaders paranoid.&lt;/p&gt;
&lt;p&gt;But if we relax the model slightly—allowing bounded timer inaccuracy—we can design more efficient protocols. In the &lt;a href=&#34;https://www.computer.org/csdl/journal/td/1999/06/l0642/13rRUwbs2fY&#34;&gt;&lt;em&gt;timed asynchronous system model&lt;/em&gt;&lt;/a&gt;, servers use timers with bounded drift. This is, as I&amp;rsquo;ll argue below, a more realistic model, and it enables &lt;em&gt;leader leases&lt;/em&gt;: a majority promises not to elect another leader for a duration. The leaseholder can serve reads without checking with peers.&lt;/p&gt;
&lt;p&gt;At &lt;a href=&#34;https://www.mongodb.com/company/research/distributed-systems-research-group&#34;&gt;MongoDB Distributed Systems Research Group&lt;/a&gt;, we’re developing lease protocols for Raft and MongoDB. So I spent a few days researching this question: how reliable are timers?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/clock1.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Timer uncertainty&lt;/h1&gt;

&lt;p&gt;With leader leases, the leader starts a timer for some duration &lt;em&gt;d&lt;/em&gt;, and sends a message to its followers telling them to start timers for the same duration. Leases guarantee consistency so long as the leader believes its timer expires &lt;em&gt;before&lt;/em&gt; any of its followers believe theirs do. I formulated this as the &lt;strong&gt;Timer Rule:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If server &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; starts a timer &lt;em&gt;t&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt;,&lt;br&gt;
then sends a message to server &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;,&lt;br&gt;
which receives the message and starts a timer &lt;em&gt;t&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;,&lt;br&gt;
and &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; thinks &lt;em&gt;t&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; is ≥ &lt;em&gt;d&lt;/em&gt; * &lt;em&gt;ε&lt;/em&gt; old,&lt;br&gt;
then &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; thinks &lt;em&gt;t&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; is ≥ &lt;em&gt;d&lt;/em&gt; old.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We should make the safety margin &lt;em&gt;ε&lt;/em&gt; large enough to guarantee this, but not unnecessarily large (which would hurt availability). Here are some sources of uncertainty:&lt;/p&gt;
&lt;h2&gt;Clock frequency error, a.k.a. &amp;ldquo;drift&amp;rdquo;&lt;/h2&gt;

&lt;p&gt;Any server&amp;rsquo;s timer depends on a quartz oscillator (&amp;ldquo;XO&amp;rdquo;) on its motherboard, even if the server is a VM. All XOs are manufactured with some inaccuracy, and their speed is affected by age and temperature. XOs slow down if they&amp;rsquo;re too cold &lt;strong&gt;or&lt;/strong&gt; too hot! Cloud providers control temperature fairly well in their data centers and they swap out components periodically, but not all servers are so well cared for.&lt;/p&gt;
&lt;p&gt;NTP clients (ntpd or the more modern chronyd) measure oscillator drift over time (even across reboots) and compensate for it, &lt;a href=&#34;https://www.eecis.udel.edu/~mills/ntp/html/discipline.html&#34;&gt;&lt;em&gt;disciplining&lt;/em&gt; the oscillator to near-perfection&lt;/a&gt;. In the last couple years, &lt;a href=&#34;https://aws.amazon.com/about-aws/whats-new/2023/11/amazon-time-sync-service-microsecond-accurate-time/&#34;&gt;cloud providers have achieved clock synchronization within tens of microseconds&lt;/a&gt;, implying minuscule clock drift. However, for maximum safety let&amp;rsquo;s assume that NTP isn&amp;rsquo;t functioning at all. The servers&amp;rsquo; timers are undisciplined and freely drifting.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s weirdly hard to find the model numbers for XOs commonly used in servers, but I think I found some examples (&lt;a href=&#34;https://www.ti.com/document-viewer/LMK3H0102/datasheet&#34;&gt;1&lt;/a&gt;, &lt;a href=&#34;https://abracon.com/Oscillators/ASEseries.pdf&#34;&gt;2&lt;/a&gt;, &lt;a href=&#34;https://ecsxtal.com/store/pdf/ECS-2520MV.pdf&#34;&gt;3&lt;/a&gt;). They usually advertise a maximum drift of ±50 ppm (parts per million) over a vast range of operating temperatures. (See &lt;a href=&#34;https://en.wikipedia.org/wiki/Crystal_oscillator#Temperature_effects&#34;&gt;this formula&lt;/a&gt; for the accuracy of XOs in general.) This matches the &lt;a href=&#34;https://github.com/aws/clock-bound/blob/main/clock-bound-d/README.md#chrony-configuration&#34;&gt;50 ppm value&lt;/a&gt; that the AWS ClockBound engineers consider worst-case.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/clock5.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Clock slewing&lt;/h2&gt;

&lt;p&gt;When ntpd or chronyd make routine clock adjustments, they &lt;em&gt;slew&lt;/em&gt; it gradually to the correct time instead of &lt;em&gt;stepping&lt;/em&gt; it discontinuously. The max slew rate for ntpd is 500 ppm hardcoded, for chronyd it&amp;rsquo;s 1000 ppm configurable.&lt;/p&gt;
&lt;p&gt;Every few years the authorities announce a &lt;a href=&#34;https://en.wikipedia.org/wiki/Leap_second&#34;&gt;leap second&lt;/a&gt; to account for recent unpredictable changes in the earth&amp;rsquo;s rotation. It is announced 6 months ahead. Leap seconds &lt;a href=&#34;https://engineering.fb.com/2022/07/25/production-engineering/its-time-to-leave-the-leap-second-in-the-past/&#34;&gt;will probably be canceled starting in 2035&lt;/a&gt;, in favor of a leap &lt;em&gt;hour&lt;/em&gt; every few thousand years. If there are any leap seconds before 2035, different time providers may unfortunately implement them differently. AWS and GCP are likely to slew the clock for 24 hours (a 7 ppm slew rate) and Azure &lt;a href=&#34;https://www.libertysys.com.au/2024/03/mister-cloud-part-3/#not-leap-smeared&#34;&gt;may ignore the leap second&lt;/a&gt;, introducing a 7 ppm discrepancy between timers in Azure and non-Azure during the 24 hours. But we can ignore leap seconds; we only care about the max slew rate, regardless of the reason for slewing.&lt;/p&gt;
&lt;p&gt;Bottom line: the max slew rate is 1000 ppm.&lt;/p&gt;
&lt;h2&gt;VM interruptions&lt;/h2&gt;

&lt;p&gt;Hypervisors have &lt;em&gt;paravirtual clocks&lt;/em&gt;: the VM&amp;rsquo;s clock is a passthrough to the host clock, so when the VM wakes from a pause its clock is still up to date. AWS and GCP use the KVM hypervisor, which has &lt;a href=&#34;https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/chap-kvm_guest_timing_management&#34;&gt;kvm-clock&lt;/a&gt;. Azure uses &lt;a href=&#34;https://learn.microsoft.com/en-us/azure/virtual-machines/linux/time-sync#:~:text=Virtual%20machine%20interactions%20with%20the,in%20Linux%20VMs%20to%20compensate&#34;&gt;Hyper-V, which has VMICTimeSync&lt;/a&gt;. Xen is going out of style, but it has something poorly documented called pvclock. If a VM pauses and resumes, its paravirtual clock won&amp;rsquo;t be affected. If it&amp;rsquo;s &lt;a href=&#34;https://learn.microsoft.com/en-us/azure/virtual-machines/maintenance-and-updates#maintenance-that-doesnt-require-a-reboot&#34;&gt;live-migrated to a different physical host&lt;/a&gt;, then presumably the accuracy of its timers across the migration depends on the clock synchronization between the source host and target host. Major cloud providers now sync their clocks to within a millisecond, usually much less.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&#34;&gt;Martin Kleppmann warns&lt;/a&gt; about checking a timer and then acting upon the result: your VM could be paused indefinitely between the check and the action. But his article is about mutual exclusion with a lease, and we&amp;rsquo;re just trying to guarantee read-your-writes. For us, the server only needs to be a leaseholder &lt;em&gt;sometime&lt;/em&gt; between receiving a request and replying.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/clock6.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;So what&amp;rsquo;s ε?&lt;/h1&gt;

&lt;p&gt;Ideally, clock slewing cancels out frequency error to approximately zero. After all, the NTP client slews the clock in order to get it back in sync. But let&amp;rsquo;s pessimistically assume that clock slewing &lt;em&gt;adds&lt;/em&gt; to frequency error. The sum of the errors above is 50 ppm (max frequency error) + 1000 ppm (chronyd&amp;rsquo;s max slew rate) = 1050 ppm. If two servers&amp;rsquo; clocks are drifting apart as fast as possible, that&amp;rsquo;s 2100 ppm, or a little over 1/500&lt;sup&gt;th&lt;/sup&gt;. So if we set &lt;em&gt;ε&lt;/em&gt; = 1.0021, we&amp;rsquo;ll definitely obey the Timer Rule above. For example, if &lt;em&gt;election timeout&lt;/em&gt; is 5 seconds (MongoDB&amp;rsquo;s default in &lt;a href=&#34;https://www.mongodb.com/products/platform/atlas-database&#34;&gt;Atlas&lt;/a&gt;), this means waiting an extra 11 ms to be sure.&lt;/p&gt;
&lt;h1&gt;How important is ε?&lt;/h1&gt;

&lt;p&gt;We could probably get away with ignoring drift, and set &lt;em&gt;ε&lt;/em&gt; = 1, and still never violate the Timer Rule. Recall the scenario I care about: &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; starts a timer and sends a message to &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;, which starts its own timer. &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;&amp;rsquo;s timer mustn&amp;rsquo;t expire before &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt;&amp;rsquo;s. There&amp;rsquo;s some builtin safety already, because &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;&amp;rsquo;s timer will &lt;em&gt;start&lt;/em&gt; after &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt;&amp;rsquo;s. The delay depends on network latency and processing time on each server; it will probably be much larger than timer inaccuracy.&lt;/p&gt;
&lt;p&gt;If we rely on leases for consistency, then even if &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; thinks its timer expires too soon, it&amp;rsquo;s still hard to observe a consistency violation. There&amp;rsquo;s only a short time while &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; thinks it still has a lease, but &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; thinks &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt;&amp;rsquo;s lease expired. During this window all the following must happen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; runs for election, and wins.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; receives a write command, majority-replicates it, and acknowledges it to the client.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; receives a query for some data that was just overwritten on &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt;, thinking it still has a lease, runs the query on stale data and replies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This sequence would violate read-your-writes. It&amp;rsquo;s hard to imagine all those events in the milliseconds or microseconds between the two timers&amp;rsquo; expirations. It seems more likely to me that the Timer Rule is violated, not because ε is a smidgen too small, but because some misconfiguration makes a timer wildly inaccurate.&lt;/p&gt;
&lt;p&gt;In MongoDB&amp;rsquo;s version of Raft, the violation is even less likely, because the MongoClient &lt;a href=&#34;https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.md#using-electionid-and-setversion-to-detect-stale-primaries&#34;&gt;will know that &lt;em&gt;S&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; has been deposed&lt;/a&gt;. You have to use multiple MongoClients, or restart your client application, to lose this information and observe the consistency violation. See &lt;a href=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/&#34;&gt;my causal consistency article&lt;/a&gt; for a technique to handle this case.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/clock4.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;In summary, a user would have to be very unlucky to observe a consistency violation with leases: they&amp;rsquo;d need a Raft group with two leaders due to a network partition, but the user can talk to both sides. They&amp;rsquo;d have to use two clients (to disable MongoClient&amp;rsquo;s deposed leader check), write to the new leader, and quickly read the overwritten data from the old leader. The old leader would have to somehow fail to step down, while the new leader won election surprisingly quickly. And finally, the lease mechanism would have to fail because clock frequency error was worse than ε.&lt;/p&gt;
&lt;p&gt;In meta-summary, I think distributed systems implementors should rely on timers more. Whenever I discuss leases with engineers, the first question is, &amp;ldquo;Can we rely on timers?&amp;rdquo; It&amp;rsquo;s good to ask this question, but sometimes we have to take &amp;ldquo;yes&amp;rdquo; for an answer.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/timers-distributed-algorithms/clock8.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Images: &lt;a href=&#34;https://wellcomecollection.org/&#34;&gt;Wellcome Collection&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Conformance Checking at MongoDB: Testing That Our Code Matches Our TLA&#43; Specs</title>
      <link>https://emptysqua.re/blog/mongodb-conformance-checking/</link>
      <pubDate>Tue, 03 Jun 2025 09:03:01 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/mongodb-conformance-checking/</guid>
      
      
      
      
      <description>&lt;p&gt;&lt;a href=&#34;https://www.mongodb.com/blog/post/engineering/conformance-checking-at-mongodb-testing-our-code-matches-our-tla-specs&#34;&gt;Cross-posted from the MongoDB engineering blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At MongoDB, we design a lot of distributed algorithms—algorithms with lots of concurrency and complexity, and dire consequences for mistakes. We formally specify some of the scariest algorithms in TLA+, to check that they behave correctly in every scenario. But how do we know that our implementations conform to our specs? And how do we keep them in sync as the implementation evolves?&lt;/p&gt;
&lt;p&gt;This problem is called &lt;em&gt;conformance checking&lt;/em&gt;. In 2020, my colleagues and I experimented with two MongoDB products, to see if we could test their fidelity to our TLA+ specs. Here&amp;rsquo;s a video of my presentation on this topic at the VLDB conference. (It&amp;rsquo;ll be obvious to you that I recorded it from my New York apartment in deep Covid lockdown.) Below, I write about our experience with conformance checking from 2025&amp;rsquo;s perspective. I&amp;rsquo;ll tell you what worked for us in 2020 and what didn&amp;rsquo;t, and what developments there have been in the field in the five years since our paper.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/IIGzXX72weQ?si=_z2r0Y5W0S-Igob4&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;h1&gt;Agile modelling&lt;/h1&gt;

&lt;p&gt;Our conformance-checking project was born when I read &lt;a href=&#34;https://arxiv.org/abs/1111.2826&#34;&gt;a paper&lt;/a&gt; from 2011—&amp;ldquo;Concurrent Development of Model and Implementation&amp;rdquo;—which described a software methodology called &lt;em&gt;eXtreme Modelling&lt;/em&gt;. The authors argued that there&amp;rsquo;s a better way to use languages like TLA+, and I was convinced. They advocated a combination of agile development and rigorous formal specification:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Multiple specifications model aspects of the system.&lt;/li&gt;
&lt;li&gt;Specifications are written just prior to the implementation.&lt;/li&gt;
&lt;li&gt;Specifications evolve with the implementation.&lt;/li&gt;
&lt;li&gt;Tests are generated from the model, and/or trace-checking verifies that test traces are legal in the specification.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I was excited about this vision. Too often, an engineer tries to write one huge TLA+ spec for the whole system. It&amp;rsquo;s too complex and detailed, so it&amp;rsquo;s not much easier to understand than the implementation code, and state-space explosion dooms model checking. The author abandons the spec and concludes that TLA+ is impractical. In the eXtreme Modelling style, a big system is modeled by a collection of small specs, each focusing on an aspect of the whole. This was the direction MongoDB was already going, and it seemed right to me.&lt;/p&gt;
&lt;p&gt;In eXtreme Modelling, the &lt;em&gt;conformance&lt;/em&gt; of the spec and implementation is continuously tested. The authors propose two conformance checking techniques. To understand these, let&amp;rsquo;s consider what a TLA+ spec is: it&amp;rsquo;s a description of an algorithm as a &lt;em&gt;state machine&lt;/em&gt;. The state machine has a set of variables, and each &lt;em&gt;state&lt;/em&gt; is an assignment of specific values to those variables. The state machine also has a set of allowed &lt;em&gt;actions&lt;/em&gt;, which are transitions from one state to the next state. You can make a &lt;em&gt;state graph&lt;/em&gt; by drawing states as nodes and allowed actions as edges. A &lt;em&gt;behavior&lt;/em&gt; is any path through the graph.&lt;/p&gt;
&lt;p&gt;This diagram shows the whole state graph for some very simple imaginary spec. One of the spec&amp;rsquo;s behaviors is highlighted in &lt;span style=&#34;color: #2f9e44; font-weight: bold&#34;&gt;green&lt;/span&gt;.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/state-space.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/state-space.excalidraw.png&#34;&gt;
&lt;/picture&gt;
&lt;/div&gt;
&lt;p&gt;The spec has a set of behaviors &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;spec&lt;/sub&gt;, and the implementation has a set of behaviors &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;impl&lt;/sub&gt;. An implementation &lt;em&gt;refines&lt;/em&gt; a spec if &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;impl&lt;/sub&gt; ⊂ &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;spec&lt;/sub&gt;. If the converse is also true, if &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;spec&lt;/sub&gt; ⊂ &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;impl&lt;/sub&gt;, then this is called &lt;em&gt;bisimulation,&lt;/em&gt; and it&amp;rsquo;s a nice property to have, though not always necessary for a correctly implemented system. You can test each direction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test-case generation&lt;/strong&gt;: For every behavior in &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;spec&lt;/sub&gt;, generate a test case that forces the implementation to follow the same sequence of transitions. If there&amp;rsquo;s a spec behavior the implementation can&amp;rsquo;t follow, then &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;spec&lt;/sub&gt; ⊄ &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;impl&lt;/sub&gt;, and the test fails.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trace-checking&lt;/strong&gt;: For every behavior in &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;impl&lt;/sub&gt;, generate a &lt;em&gt;trace&lt;/em&gt;: a log file that records the implementation&amp;rsquo;s state transitions, including all implementation variables that match spec variables. If the behavior recorded in the trace isn&amp;rsquo;t allowed by the spec, then &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;impl&lt;/sub&gt; ⊄ &lt;em&gt;B&lt;/em&gt;&lt;sub&gt;spec&lt;/sub&gt; and the test fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are the state spaces of a spec and an implementation. Non-conforming behaviors are highlighted in &lt;span style=&#34;color: #e03131; font-weight: bold&#34;&gt;red&lt;/span&gt;:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/state-spaces.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/state-spaces.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Both techniques can be hard, of course. For test-case generation, you must somehow control every decision the implementation makes, squash all nondeterminism, and force it to follow a specific behavior. If the spec&amp;rsquo;s state space is huge, you have to generate a huge number of tests, or choose an incomplete sample.&lt;/p&gt;
&lt;p&gt;Trace-checking, on the other hand, requires you to somehow map the implementation&amp;rsquo;s state back to the spec&amp;rsquo;s, and log a snapshot of the system state each time it changes—this is really hard with multithreaded programs and distributed systems. And you need to make the implementation explore a variety of behaviors, via fault-injection and stress-testing and so on. Completeness is usually impossible.&lt;/p&gt;
&lt;p&gt;We found academic papers that demonstrated both techniques on little example applications, but we hadn’t seen them tried on production-scale systems like ours. I wanted to see how well they work, and what it would take to make them practical. I recruited my colleagues &lt;a href=&#34;https://www.linkedin.com/in/judahschvimer/&#34;&gt;Judah Schvimer&lt;/a&gt; and &lt;a href=&#34;https://www.linkedin.com/in/maxhirschhorn/&#34;&gt;Max Hirschhorn&lt;/a&gt; to try it with me. Judah and I tried trace-checking the MongoDB server (in the next section), and Max tried test-case generation with MongoDB Mobile SDK (the remainder of this article).&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/two-techniques.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/two-techniques.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;h1&gt;Trace-checking the MongoDB server&lt;/h1&gt;

&lt;p&gt;For the trace-checking experiment, the first step Judah and I took was to choose a TLA+ spec. MongoDB engineers had already written and model-checked a handful of specs that model different aspects of the MongoDB server (see &lt;a href=&#34;https://www.youtube.com/watch?v=-eAktIBUhHA&#34;&gt;this presentation&lt;/a&gt; and &lt;a href=&#34;https://www.youtube.com/watch?v=x9zSynTfLDE&#34;&gt;this one&lt;/a&gt;). We chose &lt;a href=&#34;https://github.com/mongodb/mongo/blob/master/src/mongo/tla_plus/Replication/RaftMongo/RaftMongo.tla&#34;&gt;RaftMongo.tla&lt;/a&gt;, which focuses on how servers learn the &lt;em&gt;commit point&lt;/em&gt;, which I&amp;rsquo;ll explain now.&lt;/p&gt;
&lt;p&gt;MongoDB is typically deployed as a &lt;em&gt;replica set&lt;/em&gt; of cooperating servers, usually three of them. They achieve consensus with a &lt;a href=&#34;https://www.usenix.org/conference/nsdi21/presentation/zhou&#34;&gt;Raft-like protocol&lt;/a&gt;. First, they elect one server as the leader. Clients send all writes to the leader, which appends them to its &lt;em&gt;log&lt;/em&gt; along with a monotonically increasing logical timestamp. Followers replicate the leader&amp;rsquo;s log asynchronously, and they tell the leader how up-to-date they are. The leader keeps track of the &lt;em&gt;commit point&lt;/em&gt;—the logical timestamp of the newest majority-replicated write. All writes up to and including the commit point are &lt;em&gt;committed&lt;/em&gt;, all the writes after it are not. The commit point must be correctly tracked even when leaders and followers crash, messages are lost, a new leader is elected, uncommitted writes are rolled back, and so on.&lt;/p&gt;
&lt;p&gt;RaftMongo.tla models this protocol, and it checks two invariants: A &lt;em&gt;safety&lt;/em&gt; property, which says that no committed write is ever lost, and a &lt;em&gt;liveness&lt;/em&gt; property, which says that all servers eventually learn the newest commit point.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/commit-point.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/commit-point.excalidraw.png&#34;&gt;
&lt;/picture&gt;
&lt;/div&gt;
&lt;p&gt;Judah and I wanted to test that MongoDB&amp;rsquo;s C++ implementation matched our TLA+ spec, using trace-checking. Here are the steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run randomized tests of the implementation.&lt;/li&gt;
&lt;li&gt;Collect execution traces.&lt;/li&gt;
&lt;li&gt;Translate the execution traces into TLA+.&lt;/li&gt;
&lt;li&gt;Check the trace is permitted by the spec.&lt;/li&gt;
&lt;/ol&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/pipeline.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/pipeline.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;The MongoDB server team has hundreds of integration tests handwritten in JavaScript, from which we chose about 300 for this experiment. We also have randomized tests; we chose one called the &amp;ldquo;rollback fuzzer&amp;rdquo; which does random CRUD operations while randomly creating and healing network partitions, causing uncommitted writes to be logged and rolled back.&lt;/p&gt;
&lt;p&gt;We added tracing code to the MongoDB server and ran each test with a three-node replica set. Since all server processes ran on one machine and communicated over localhost, we didn&amp;rsquo;t worry about clock synchronization: we just merged the three logs, sorting by timestamp. We wrote a Python script to read the combined log and convert it into a giant TLA+ spec named Trace.tla with a sequence of states for the whole three-server system. Trace.tla asserted only one property: &amp;ldquo;This behavior conforms to RaftMongo.tla.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s some more detail about the Python script. At each moment during the test, the system has some state &lt;em&gt;V&lt;/em&gt;, which is the values of the state variables for each node. The script tries to reconstruct all the changes to &lt;em&gt;V&lt;/em&gt; and record them in Trace.tla. It begins by setting &lt;em&gt;V&lt;/em&gt; to a hardcoded initial state &lt;em&gt;V&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt;, and outputs it as the first state of the sequence:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-tla+&#34; data-lang=&#34;tla+&#34;&gt;\* Each TLA+ tuple is
\* &amp;lt;&amp;lt;action, committedEntries, currentTerm, log, role, commitPoint,
\*   serverLogLocation&amp;gt;&amp;gt;
\* We know the first state: all nodes are followers with empty logs.
Trace == &amp;lt;&amp;lt;
  &amp;lt;&amp;lt;&amp;#34;Init&amp;#34;,                               \* action name
    &amp;lt;&amp;lt;&amp;#34;Follower&amp;#34;,&amp;#34;Follower&amp;#34;,&amp;#34;Follower&amp;#34;&amp;gt;&amp;gt;, \* role per node
    &amp;lt;&amp;lt;1, 1, 1&amp;gt;&amp;gt;,                          \* commitPoint per node
    &amp;lt;&amp;lt;&amp;lt;&amp;lt;...&amp;gt;&amp;gt;,&amp;lt;&amp;lt;...&amp;gt;&amp;gt;,&amp;lt;&amp;lt;...&amp;gt;&amp;gt;&amp;gt;&amp;gt;,          \* log per node
    &amp;#34;&amp;#34;&amp;gt;&amp;gt;,                                 \* trace log location (empty)
\* ... more states will follow ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The script reads events from the combined log and updates &lt;em&gt;V&lt;/em&gt;. Here&amp;rsquo;s an example where Node 1 was the leader in state &lt;em&gt;V&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;, then Node 2 logs that it became leader. The script combines these to produce &lt;em&gt;V&lt;/em&gt;&lt;sub&gt;i+1&lt;/sub&gt; where Node 2 is the leader and Node 1 is now a follower. Note, this is a lie. Node 1 didn&amp;rsquo;t &lt;em&gt;actually&lt;/em&gt; become a follower in the same instant Node 2 became leader. Foreshadowing! This will be a problem for Judah and me.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/state.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/state.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Anyway, the Python script appends a state to the sequence in Trace.tla:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-tla+&#34; data-lang=&#34;tla+&#34;&gt;Trace == &amp;lt;&amp;lt;  
  * ... thousands of events ...  
    &amp;lt;&amp;lt;&amp;#34;BecomePrimary&amp;#34;,                  * action name for debugging  
    &amp;lt;&amp;lt;&amp;#34;Follower&amp;#34;,&amp;#34;**Leader**&amp;#34;,&amp;#34;Follower&amp;#34;&amp;gt;&amp;gt;, * role per node  
    &amp;lt;&amp;lt;1, 1, 1&amp;gt;&amp;gt;,                        * commitPoint per node  
    &amp;lt;&amp;lt;&amp;lt;&amp;lt;...&amp;gt;&amp;gt;,&amp;lt;&amp;lt;...&amp;gt;&amp;gt;,&amp;lt;&amp;lt;...&amp;gt;&amp;gt;&amp;gt;&amp;gt;,        * log per node  
    * trace log location, for debugging:  
    &amp;#34;/home/emptysquare/RollbackFuzzer/node2.log:12345&amp;#34;&amp;gt;&amp;gt;,  
  * ... thousands more events ...  
&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We used the Python script to generate a Trace.tla file for each of the hundreds of tests we&amp;rsquo;d selected: handwritten JavaScript tests and the randomized &amp;ldquo;rollback fuzzer&amp;rdquo; test. Now we wanted to use the model-checker to check that this state sequence was permitted by our TLA+ spec, so we know our C++ code behaved in a way that conforms to the spec. Following &lt;a href=&#34;https://pron.github.io/files/Trace.pdf&#34;&gt;a technique published by Ron Pressler&lt;/a&gt;, we added these lines to each Trace.tla:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-tla+&#34; data-lang=&#34;tla+&#34;&gt;VARIABLES log, role, commitPoint
\* Instantiate our hand-written spec, RaftMongo.tla.
Model == INSTANCE RaftMongo
VARIABLE i \* the trace index

\* Load one trace event.
Read == /\ log = Trace[i][4]
        /\ role = Trace[i][5]
        /\ commitPoint = Trace[i][6]

ReadNext == /\ log&amp;#39; = Trace[i&amp;#39;][4]
            /\ role&amp;#39; = Trace[i&amp;#39;][5]
            /\ commitPoint&amp;#39; = Trace[i&amp;#39;][6]

Init == i = 1 /\ Read
Next == \/ i &amp;lt; Len(Trace) /\ i&amp;#39; = i + 1 /\ ReadNext
        \/ UNCHANGED &amp;lt;&amp;lt;i, vars&amp;gt;&amp;gt; \* So that we don’t get a deadlock error in TLC

TraceBehavior == Init /\ [][Next]_&amp;lt;&amp;lt;vars, i&amp;gt;&amp;gt;

\* To verify, we check the spec TraceBehavior in TLC, with Model!SpecBehavior
\* as a temporal property.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We run the standard TLA+ model-checker (&amp;ldquo;TLC&amp;rdquo;), which tells us if this trace is an allowed behavior in RaftMongo.tla.&lt;/p&gt;
&lt;p&gt;But this whole experiment failed. Our traces never matched our specification. We didn&amp;rsquo;t reach our goal, but we learned three lessons that could help future engineers.&lt;/p&gt;
&lt;h2&gt;What disappointment taught us&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lesson one: It&amp;rsquo;s hard to snapshot a multithreaded program&amp;rsquo;s state.&lt;/strong&gt; Each time a MongoDB node executes a state transition, it has to snapshot its state variables in order to log them. MongoDB is highly concurrent with fairly complex locking within each process—it was built to &lt;em&gt;avoid&lt;/em&gt; global locking. It took us a month to figure out how to instrument MongoDB to get a consistent snapshot of all these values at one moment. We burned most of our budget for the experiment, and we worried we&amp;rsquo;d changed MongoDB too much (on a branch) to test it realistically.&lt;/p&gt;
&lt;p&gt;The 2024 paper &amp;ldquo;Validating Traces of Distributed Programs Against TLA+ Specifications&amp;rdquo; describes how to do trace-checking when you can only log &lt;em&gt;some&lt;/em&gt; of the values (see my summary at the bottom of this page). We were aware of this option back in 2020, and we worried it would make trace-checking too permissive; it wouldn&amp;rsquo;t catch every bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson two: The implementation must actually conform to the spec.&lt;/strong&gt; This is obvious to me now. After all, conformance checking was the point of the project. In our real-life implementation, when an old leader votes for a new one, &lt;em&gt;first&lt;/em&gt; the old leader steps down, &lt;em&gt;then&lt;/em&gt; the new leader steps up. The spec we chose for trace-checking wasn&amp;rsquo;t focused on the election protocol, though, so for simplicity, the spec assumed these two actions happened at once. (Remember I said a few paragraphs ago, &amp;ldquo;This is a lie&amp;rdquo;?) Judah and I knew about this discrepancy—we&amp;rsquo;d deliberately made this simplification in the spec. We tried to paper over the difference with some post-processing in our Python script, but it never worked. By the end of the project, we decided we should have backtracked, making our spec much more complex and realistic, but we&amp;rsquo;d run out of time.&lt;/p&gt;
&lt;p&gt;The eXtreme Modelling methodology says we should write the spec just &lt;em&gt;before&lt;/em&gt; the implementation. But our spec was written long &lt;em&gt;after&lt;/em&gt; most of the implementation, and it was highly abstract. I can imagine another world where we knew about eXtreme Modelling and TLA+ at the start, when we began coding MongoDB. In that world, we wrote our spec before the implementation, with trace-checking in mind. The spec and implementation would&amp;rsquo;ve been structured similarly, and this would all have been much easier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson three: Trace-checking should extend easily to multiple specs.&lt;/strong&gt; Judah and I put in 10 weeks of effort without successfully trace-checking one spec, and most of the work was specific to that spec, RaftMongo.tla. Sure, we learned general lessons (you&amp;rsquo;re reading some of them) and wrote some general code, but even if we&amp;rsquo;d gotten trace-checking to work for one spec we&amp;rsquo;d be practically starting over with the next spec. Our original vision was to gather execution traces from all our tests, and trace-check them against all of our specifications, on every git commit. We estimated that the marginal cost of implementing trace-checking for more specs wasn&amp;rsquo;t worth the marginal value, so we stopped the project.&lt;/p&gt;
&lt;h2&gt;Practical trace-checking&lt;/h2&gt;

&lt;p&gt;If we started again, we&amp;rsquo;d do it differently. We&amp;rsquo;d ensure the spec and implementation conform at the start, and we&amp;rsquo;d fix discrepancies by fixing the spec or the implementation right away. We&amp;rsquo;d model easily observed events like network messages, to avoid snapshotting the internal state of a multithreaded process.&lt;/p&gt;
&lt;p&gt;I still think trace-checking is worthwhile. I know it&amp;rsquo;s worked for other projects. In fact MongoDB is sponsoring a grad student &lt;a href=&#34;https://fhackett.com/&#34;&gt;Finn Hackett&lt;/a&gt;, whom I&amp;rsquo;m mentoring, to continue trace-checking research.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s move on to the second half of our project.&lt;/p&gt;
&lt;h1&gt;Test-case generation for MongoDB Mobile SDK&lt;/h1&gt;

&lt;p&gt;The MongoDB Mobile SDK is a database for mobile devices that syncs with a central server. (Since we wrote the paper, MongoDB has &lt;a href=&#34;https://www.mongodb.com/resources/resources/resource-library/docs-atlas-device-sync-getting-started?xs=494444&#34;&gt;sunsetted the product&lt;/a&gt;.) Mobile clients can make changes locally. These changes are periodically uploaded to the server and downloaded by other clients. The clients and the server all use the same algorithm to resolve write conflicts: &lt;a href=&#34;https://en.wikipedia.org/wiki/Operational_transformation&#34;&gt;Operational Transformation&lt;/a&gt;, or OT. Max wanted to test that the clients and server implement OT correctly, meaning they resolve conflicts the same way, eventually resulting in identical data everywhere.&lt;/p&gt;
&lt;p&gt;Originally, the clients and server shared one C++ implementation of OT, so we knew they implemented the same algorithm. But in 2020, we&amp;rsquo;d recently rewritten the server in Go, so testing their conformance became urgent.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/realm.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/mongodb-conformance-checking/realm.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;My colleague Max Hirschhorn used &lt;em&gt;test-case generation&lt;/em&gt; to check conformance. This technique goes in the opposite direction from trace-checking: trace-checking starts with an implementation and checks that its behaviors are allowed by the spec, but test-case generation starts with a spec and checks that its behaviors are in the implementation.&lt;/p&gt;
&lt;p&gt;But first, we needed a TLA+ spec. Before this project, the mobile team had written out the OT algorithm in English and implemented it in C++. Max manually translated the algorithm from C++ to TLA+. In the mobile SDK, clients can do 19 kinds of operations on data; six of these can be performed on arrays, resulting in 21 array merge rules, which are implemented in about 1000 lines of C++. Those 21 rules are the most complex, and Max focused his specification there. He used the model-checker to verify that his TLA+ spec ensured all participants eventually had the same data. This translation was a gruelling job, but the model-checker caught Max&amp;rsquo;s mistakes quickly, and he finished in two weeks.&lt;/p&gt;
&lt;p&gt;There was one kind of write conflict that crashed the model-checker: if one participant swapped two array elements, and another moved an element, then the model-checker crashed with a Java StackOverflowError. Surprisingly, this was an actual infinite-recursion bug in the algorithm. Max verified that the bug was in the C++ code. It had hidden there until he faithfully transcribed it into TLA+ and discovered it with the model-checker. He disabled the element-swap operation in his TLA+ spec, and the mobile team deprecated it in their implementation.&lt;/p&gt;
&lt;p&gt;To test conformance, Max used the model-checker to output the entire state graph for the spec. He constrained the algorithm to three participants, all editing a three-element array, each executing one (possibly conflicting) write operation. With these constraints, the state space is a DAG, with a finite number of behaviors (paths from an initial state to a final state). There are 30,184 states and 4913 behaviors. Max wrote a Go program to parse the model-checker&amp;rsquo;s output and write out a C++ unit test for each behavior.&lt;/p&gt;
&lt;p&gt;Here’s an example unit test. (It&amp;rsquo;s edited down from three participants to two.) At the start, there&amp;rsquo;s an array containing {1, 2, 3}. One client sets the third element of an array to 4 and the second client removes the second element from the array. The test asserts that both clients agree the final array is {1, 4}. The highlighted lines are specific to this generated test. The rest of the code is the same for all tests.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;TEST&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;Transform_Array&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;size_t&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;num_clients&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;TransformArrayFixture&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;test_context&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;num_clients&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;transaction&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[](&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;TableRef&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;array&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;array&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;set_int&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;transaction&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;[](&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;TableRef&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;array&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;array&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;remove&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;sync_all_clients&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;check_array&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;({&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;check_ops&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ArrayErase&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;fixture&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;check_ops&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ArraySet&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These 4913 tests immediately achieved 100% branch coverage of the implementation, which we hadn&amp;rsquo;t accomplished with our handwritten tests (21%) or millions of executions with the &lt;a href=&#34;https://lcamtuf.coredump.cx/afl/&#34;&gt;AFL fuzzer&lt;/a&gt; (92%).&lt;/p&gt;
&lt;h1&gt;Retrospective&lt;/h1&gt;

&lt;p&gt;Max&amp;rsquo;s test-case generation worked quite well. He discovered a bug in the algorithm, and he thoroughly checked that the mobile SDK&amp;rsquo;s Operational Transformation code conforms to the spec. Judah&amp;rsquo;s and my trace-checking experiment didn&amp;rsquo;t work: our spec and code were too far apart, and adding tracing to MongoDB took too long. Both techniques can work, given the right circumstances and strategy. Both techniques can fail, too! We published our results and lessons as a paper in VLDB 2020, titled &amp;ldquo;&lt;a href=&#34;https://arxiv.org/abs/2006.00915&#34;&gt;eXtreme Modelling in Practice&lt;/a&gt;.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In the subsequent five years, I&amp;rsquo;ve seen some progress in conformance checking techniques.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test-case generation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.acm.org/doi/abs/10.1145/3552326.3587442&#34;&gt;Model Checking Guided Testing for Distributed Systems&lt;/a&gt;. The &amp;ldquo;Mocket&amp;rdquo; system generates tests from a TLA+ spec, and instruments Java code (with a fair amount of human labor) to force it to deterministically follow each test, and check that its variables have the same values as the spec after each action. The authors tested the conformance of three Java distributed systems and found some new bugs. Their technique is Java-specific but could be adapted for other languages.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://muratbuffalo.blogspot.com/2025/04/multi-grained-specifications-for.html&#34;&gt;Multi-Grained Specifications for Distributed System Model Checking and Verification&lt;/a&gt;. The authors wrote several new TLA+ specs of Zookeeper, at higher and lower levels of abstraction. They checked conformance between the most concrete specs and the implementation, with a technique similar to Mocket: a human programmer instruments some Java code to map Java variables to spec variables, and to make all interleavings deterministic. The model-checker randomly explores spec behaviors, while the test framework checks that the Java code can follow the same behaviors.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/tangruize/SandTable/blob/main/doc/SandTable-Paper.pdf&#34;&gt;SandTable: Scalable Distributed System Model Checking with Specification-Level State Exploration&lt;/a&gt;. This system is &lt;em&gt;not&lt;/em&gt; language-specific: it overrides system calls to control nondeterminism and force the implementation to follow each behavior of the spec. It samples the spec&amp;rsquo;s state space to maximize branch coverage and event diversity while minimizing the length of each behavior. As in the &amp;ldquo;Multi-Grained&amp;rdquo; paper, the SandTable authors wisely developed &lt;em&gt;new&lt;/em&gt; TLA+ specs that closely matched the implementations they were testing, rather than trying to use existing, overly abstract specs like Judah and I did.&lt;/li&gt;
&lt;li&gt;Plus, my colleagues &lt;a href=&#34;https://will62794.github.io/&#34;&gt;Will Schultz&lt;/a&gt; and &lt;a href=&#34;http://muratbuffalo.blogspot.com/&#34;&gt;Murat Demirbas&lt;/a&gt; are publishing a paper in &lt;a href=&#34;https://vldb.org/2025/&#34;&gt;VLDB 2025&lt;/a&gt; that uses test-case generation with a new TLA+ spec of MongoDB&amp;rsquo;s WiredTiger storage layer, the paper is titled &amp;ldquo;Design and Modular Verification of  Distributed Transactions in MongoDB.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Trace-checking:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dariusf.github.io/cpluscal.pdf&#34;&gt;Protocol Conformance with Choreographic PlusCal&lt;/a&gt;. The authors write new specs in an &lt;em&gt;extremely&lt;/em&gt; high-level language that compiles to TLA+. From their specs they generate Go functions for trace-logging, which they manually add to existing Go programs. They check that the resulting traces are valid spec behaviors and find some bugs.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://arxiv.org/pdf/2404.16075v2&#34;&gt;Validating Traces of Distributed Programs Against TLA+ Specifications&lt;/a&gt;. Some veteran TLA+ experts demonstrate in detail how to trace-log from a Java program and validate the traces with TLC, the TLA+ model-checker. They&amp;rsquo;ve written small libraries and added TLC features for convenience. This paper focuses on validating &lt;em&gt;incomplete&lt;/em&gt; traces: if you can only log some of the variables, TLC will infer the rest.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://muratbuffalo.blogspot.com/2025/02/smart-casual-verification-of.html&#34;&gt;Smart Casual Verification of the Confidential Consortium Framework&lt;/a&gt;. The authors started with an existing implementation of a secure consensus protocol. Their situation was like mine in 2020 (new specs of a big old C++ program) and so was their goal: to continuously check conformance and keep the spec and implementation in sync. Using &lt;a href=&#34;https://groups.google.com/d/msgid/tlaplus/2443fd1a-1c35-419a-95b8-72de361f28bdn%40googlegroups.com&#34;&gt;the new TLC features&lt;/a&gt; announced in the &amp;ldquo;Validating Traces&amp;rdquo; paper above, they toiled for months, brought their specs and code into line, found some bugs, and realized the eXtreme Modelling vision.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://fhackett.com/&#34;&gt;Finn Hackett&lt;/a&gt; is a PhD student I&amp;rsquo;m mentoring, he&amp;rsquo;s developed &lt;a href=&#34;https://www.cs.ubc.ca/~bestchai/papers/asplos23-pgo.pdf&#34;&gt;a TLA+-to-Go compiler&lt;/a&gt;. He&amp;rsquo;s now prototyping a trace-checker to verify that the Go code he produces really conforms to its source spec. We&amp;rsquo;re doing a summer project together with &lt;a href=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/&#34;&gt;Antithesis&lt;/a&gt; to thoroughly conformance-check the implementation&amp;rsquo;s state space.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m excited to see growing interest in conformance checking, because I think it&amp;rsquo;s a serious problem that needs to be solved before TLA+ goes mainstream. The &amp;ldquo;Validating Traces&amp;rdquo; paper announced some new trace-checking features in TLC, and TLC&amp;rsquo;s developers are &lt;a href=&#34;https://github.com/tlaplus/tlaplus/issues/1073&#34;&gt;discussing a better way to export a state graph for test-case generation&lt;/a&gt;. I hope these research prototypes lead to standard tools, so engineers can keep their code and specs in sync.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MongoDB and CS Education for All, Part 2</title>
      <link>https://emptysqua.re/blog/cs4all-part-2/</link>
      <pubDate>Tue, 27 May 2025 22:19:52 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/cs4all-part-2/</guid>
      
      
      
      
      <description>&lt;p&gt;In 2016 and 2017, I photographed, reported, and wrote &lt;a href=&#34;https://emptysqua.re/blog/series/cs4all&#34;&gt;a two-part series&lt;/a&gt; on MongoDB&amp;rsquo;s sponsorship of computer science education in NYC public schools. Those articles were eventually deleted from MongoDB&amp;rsquo;s engineering blog, so I&amp;rsquo;m reposting them here, eight years later. &lt;a href=&#34;https://emptysqua.re/blog/cs4all-part-1/&#34;&gt;Part 1 is here&lt;/a&gt;, and Part 2 is below.&lt;/p&gt;
&lt;h1&gt;Investing in CS4All: One Year Later&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-2/cs4all-part2-01.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;When a couple of New York City high school teachers partnered with MongoDB to teach computer science, did they succeed? Their curriculum was untested, and they were teaching in difficult districts where most students are from poor and minority families. &lt;a href=&#34;https://emptysqua.re/blog/cs4all-part-1/&#34;&gt;I talked with these two teachers, Jeremy Mellema and Timothy Chen, back in September&lt;/a&gt;, when they had completed a summer fellowship at MongoDB and had just started teaching their curriculum; at the end of the academic year this spring, I visited Jeremy and Tim again to find out the result.&lt;/p&gt;
&lt;p&gt;Their successes were sparse and partial. They discovered that their students&amp;rsquo; poor reading skills were a barrier to learning to code, and that teaching new coders how to solve problems is, itself, an unsolved problem. With a coarse unit of iteration—a school semester—it is painfully slow to experiment and find teaching methods that work. But even partial wins make a difference for individual kids, and the support of professional engineers at companies like MongoDB can be a powerful accelerant.&lt;/p&gt;
&lt;h1&gt;What Engages Students&lt;/h1&gt;

&lt;p&gt;Jeremy&amp;rsquo;s main struggle was to get his students excited about code. He was assigned to teach a computer science class at Bronx Compass High School in the fall, using the curriculum he wrote during his fellowship at MongoDB last summer. In the beginning he spent too much time lecturing. &amp;ldquo;It felt weird,&amp;rdquo; he said. &amp;ldquo;It should be more like, &amp;lsquo;Let&amp;rsquo;s get down and dirty,&amp;rsquo; and not, &amp;lsquo;Let&amp;rsquo;s have me talk to you.&amp;rsquo;&amp;rdquo; Even when his students did get their hands on computers, the first exercises were simply retyping Python scripts from a textbook. The payoff, watching a script run without throwing an exception, was hardly satisfying to them.&lt;/p&gt;
&lt;p&gt;Things started to click when he introduced &lt;a href=&#34;https://docs.python.org/3.6/library/turtle.html&#34;&gt;Python turtle graphics&lt;/a&gt;, which gave the class more obvious evidence of their accomplishments. It also allowed Jeremy better opportunities to motivate and engage his students directly. &amp;ldquo;Some days I would challenge them to see who could make the craziest drawing,&amp;rdquo; Jeremy says. He would tell his students, &amp;ldquo;That&amp;rsquo;s so cool. I only made a star. You definitely beat me today.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Jeremy teaches both history and computer science, and he finds that some of his lowest-performing history students are his best CS students. &amp;ldquo;It&amp;rsquo;s satisfying to see them in their element,&amp;rdquo; he says. In Jeremy&amp;rsquo;s view a computer science class can touch a student&amp;rsquo;s intellect just as deeply as history. &amp;ldquo;People are multifaceted. You&amp;rsquo;re not only who you are when you&amp;rsquo;re in my history class.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-2/cs4all-part2-02.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Jeremy&amp;rsquo;s computer science class was cancelled this spring; the students at Bronx Compass High School are behind on history credits and there are only three history teachers on staff. For now, computer science is merely an elective, so Jeremy is back teaching history full-time. &amp;ldquo;I really miss teaching CS,&amp;rdquo; he says.&lt;/p&gt;
&lt;p&gt;If he resumes the course, Jeremy thinks it must be livelier. He is reconsidering his use of the videos from &lt;a href=&#34;https://greenteapress.com/wp/think-python-3rd-edition/&#34;&gt;How To Think Like a Computer Scientist&lt;/a&gt;, which he studied last summer on the recommendation of his mentor Shannon Bradshaw, MongoDB&amp;rsquo;s vice president of education. The content helped Jeremy train to teach CS, but when he showed the videos to his kids they were bored. Jeremy hopes to make new videos that will draw them in. His students from the fall semester say he should get their advice. Otherwise, they warned, &amp;ldquo;you might do something that you think is cool but it&amp;rsquo;s actually super corny.&amp;rdquo;&lt;/p&gt;
&lt;h1&gt;A Head Start&lt;/h1&gt;

&lt;p&gt;Although there is no computer science elective this semester, some students are pursuing the topic in other ways. A young woman from his class in the fall, Tatyana Camacho, now interns for the high school&amp;rsquo;s IT department. I had quoted her in my previous article, and Jeremy tells me she loved it. She commanded him to show her father in the next parent-teacher conference: &amp;ldquo;You need to show my dad that I&amp;rsquo;m one of the advanced students.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Jeremy still runs the afternoon Computer Club. I visited the club to meet a student, Daniel Rodriguez, who was tinkering with an Arduino and a circuit board that the school provided. &amp;ldquo;I don&amp;rsquo;t have the ability to get this equipment otherwise, in my predicament,&amp;rdquo; says Daniel. He starts his Arduino projects by copying examples. The wiring is easier than the coding for him, he told me, &amp;ldquo;especially because I&amp;rsquo;m not the best speller in the world.&amp;rdquo; Once he has the example working he modifies it to his own taste. Most recently, he wanted to show a message but, with only LEDs, he can&amp;rsquo;t display much. He researched Morse Code and made a light flash the code for &amp;ldquo;HELLO&amp;rdquo;, like any programmer demonstrating a system for the first time.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-2/cs4all-part2-03.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&amp;ldquo;Most people think that once you plug something in, that&amp;rsquo;s it, it works,&amp;rdquo; says Daniel. &amp;ldquo;But I&amp;rsquo;m the person that makes the circuit run. I tell people, &amp;lsquo;I made it do that.&amp;rsquo; And seeing them fascinated by what I did, it makes me, in turn, fascinated by what I&amp;rsquo;m doing.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Daniel has to return the Arduino at the end of the year. Next year he&amp;rsquo;ll go to a trade school for electricians. Working with the Arduino will give him an advantage, he hopes, and it seems plausible to me. As he finishes school and starts work as an electrician the world will be changing around him: smart appliances and programmable components will be everywhere. An electrician who loves to code will have a big head start.&lt;/p&gt;
&lt;h1&gt;Making Anything They Want&lt;/h1&gt;

&lt;p&gt;Timothy Chen teaches in Hell&amp;rsquo;s Kitchen, at Urban Assembly Gateway School for Technology. I visited his class in May to see how his students had progressed since I last saw them in September. They were involved in a multi-week project called the AP Create Task, part of a national Advanced Placement exam. &amp;ldquo;They are allowed to make literally anything they want, in any language,&amp;rdquo; says Tim. Students submit their code and a one-minute video of the program in action, and they may describe their project either in writing or in audio narration. I was surprised by how 21st Century the test is, and how accommodating it could be to students with a deficit in reading and writing. It must be remarkably difficult, however, to score fairly.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-2/cs4all-part2-04.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The Create Task is many students&amp;rsquo; first time scoping and integrating a sizable project, and there were some flameouts. One young man tried to make a maze game drawn with ASCII characters; it proved too ambitious and he ran out of time. Tim isn&amp;rsquo;t supposed to help students define the scope of their projects, but if they announce they&amp;rsquo;re going to tackle something difficult he will push them to list all the components. In the best case, they realize they don&amp;rsquo;t know how to do most of the project and choose something simpler.&lt;/p&gt;
&lt;p&gt;One of Tim&amp;rsquo;s students, Jasean Maxwell, was building a Go Fish card game in Python, and she was having trouble integrating the pieces. &amp;ldquo;It has to be a certain order and it&amp;rsquo;s hard to make that order when you don&amp;rsquo;t know, really, what you&amp;rsquo;re doing. I&amp;rsquo;m struggling, putting it all together.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Another student, Cecilia Gonzalez, was writing a Choose Your Own Adventure game. She says the AP Create Task encourages students to work in pairs. &amp;ldquo;We work sort of together but not exactly.&amp;rdquo; Each must create at least one significant part of the program independently. Cecilia&amp;rsquo;s game is based on a monster of urban legend called The Rake, which comes closer when you think about it. The game begins by asking questions such as the player&amp;rsquo;s name and height. She told me the player&amp;rsquo;s answers will determine &amp;ldquo;some things that are going to happen,&amp;rdquo; but she didn&amp;rsquo;t give away any spoilers.&lt;/p&gt;
&lt;p&gt;When Tim began teaching the class in September, he hadn&amp;rsquo;t written the ending yet, either. His greatest fear was his students would learn the curriculum faster than he could write it. By May it was clear that wasn&amp;rsquo;t a problem. &amp;ldquo;Some of the students can&amp;rsquo;t read very well, and that was a big barrier because all the things I made were text,&amp;rdquo; he says. &amp;ldquo;Everything just took longer than expected.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-2/cs4all-part2-05.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Problem Solving&lt;/h1&gt;

&lt;p&gt;How do you teach problem solving? This is Tim&amp;rsquo;s great unanswered question from the year. Perhaps if high school computer science were taught like math, as a series of small problems with only one right answer each, then how to solve those problems wouldn&amp;rsquo;t be such a mystery. But high school CS is taught like art class. Tim&amp;rsquo;s students invent new projects and somehow solve the unpredictable problems that arise in them.&lt;/p&gt;
&lt;p&gt;Tim speculates that he would learn problem solving himself by watching an experienced programmer solve a new problem, hit roadblocks, and overcome them. Indeed, &lt;a href=&#34;https://emptysqua.re/blog/mentoring/&#34;&gt;that is how I have taught problem solving to MongoDB interns&lt;/a&gt;. Together, we attack problems without knowing the answers beforehand. It requires an entire summer of one-on-one collaboration. &amp;ldquo;I don&amp;rsquo;t think that model works very well with the kids,&amp;rdquo; says Tim, &amp;ldquo;especially if they are not very good with sitting still for an extended period. I&amp;rsquo;m not sure how to reach them.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Tim, like Jeremy, wants to make more multimedia to reach students despite their poor reading skills. &amp;ldquo;I want to rethink how it should be done before I start this time. I kind of jumped into it too quickly.&amp;rdquo; Tim&amp;rsquo;s main goal is to give kids the chance to fall in love with programming and continue on their own. Many other goals are still out of reach: students at his school score low on the AP test, and few of them are likely to get a college degree in CS or be professional coders. Still, Tim hopes that a more varied course, with audio and video, could bring students farther. &amp;ldquo;The big hurdle for everyone is teaching problem solving. If I can get that, everything else is easy. I&amp;rsquo;m still trying to figure out how to do that.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-2/cs4all-part2-06.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;The View From 2025&lt;/h1&gt;

&lt;p&gt;After I wrote the 2017 followup you just read, I lost track of the two teachers, Jeremy Mellema and Timothy Chen. MongoDB had donated money and teacher training to the CS4All program in 2016 and 2017, but I don&amp;rsquo;t know if we continued after that. CS4All&amp;rsquo;s champion at MongoDB, our VP of Education Shannon Bradshaw, left the company in 2020.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s 2025 now and CS4All &lt;a href=&#34;https://blueprint.cs4all.nyc/outcomes&#34;&gt;is supposed to achieve its goals this year&lt;/a&gt;: every NYC public school student should have at least one &amp;ldquo;meaningful CS experience&amp;rdquo; every 3 years throughout their school career: either a computer science class or 11+ hours of CS instruction within some class. The plan was, instead of hiring specialist teachers, to train 5000 existing teachers in computer science, throughout all 1800 schools.&lt;/p&gt;
&lt;p&gt;The Research Alliance for New York City Schools at NYU published dozens of reports on CS4All&amp;rsquo;s progress over the last decade, culminating in &lt;a href=&#34;https://steinhardt.nyu.edu/research-alliance/research/expanding-computer-science-education-all&#34;&gt;a final report this December&lt;/a&gt;. There&amp;rsquo;s good news and bad news. On the one hand, &amp;ldquo;Student participation in CS has
expanded exponentially: About 54,000 students participated in a
CS unit or course during the first year of the initiative; this grew to
nearly 170,000 in 2022-23—an astounding 200 percent increase.
(Preliminary data show CS reaching nearly 200,000 in 2023-24.)
The number of schools offering CS courses—and the total number
of CS courses available—have also increased dramatically. The
percentage of schools offering CS each year tripled (from 15% to 45%), while the number
of CS courses increased nearly 10-fold, from 534 to over 5,000. Altogether, by 2022-23,
93 percent of schools offered students at least one CS experience at some point over the
previous three- or four-year time period (corresponding to the three- or four-year grade
bands).&amp;rdquo; However, by 2023 only 38% of students had had a CS experience in the last 3 years. Girls participated less than boys, and Black and Latino students less than White and Asian—these disparities were worse among older students, but CS4All narrowed disparities over the course of the decade.&lt;/p&gt;
&lt;p&gt;According to the report, about 3000 teachers were trained, compared to the goal of 5000, and this wasn&amp;rsquo;t enough to meet CS4All&amp;rsquo;s goals. Not all trained teachers actually taught CS. A quarter of them left the NYC public school system some time after training, and many who remained stopped teaching CS. Computer science classes competed on the schedule with other subjects, and teachers often didn&amp;rsquo;t have enough time to prepare CS lessons. At the high school level, NYC had trouble finding qualified specialist teachers—even in the current &lt;a href=&#34;https://sfstandard.com/2025/05/20/silicon-valley-white-collar-recession-entry-level/&#34;&gt;tech industry downturn&lt;/a&gt;, I imagine there are few programmers who&amp;rsquo;d accept the salary and conditions of NYC public schools.&lt;/p&gt;
&lt;p&gt;The city is still committed to some sort of CS education for all students, and now &lt;a href=&#34;https://infohub.nyced.org/in-our-schools/programs/computer-science-for-all-overview&#34;&gt;New York State has announced required CS curriculum for all students and CS training for all teachers&lt;/a&gt;. It seems like the obstacles I observed almost ten years ago were a difficulty for the program&amp;rsquo;s whole history, but it nevertheless made progress and had a statewide and national influence.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MongoDB and CS Education for All, Part 1</title>
      <link>https://emptysqua.re/blog/cs4all-part-1/</link>
      <pubDate>Tue, 27 May 2025 12:18:34 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/cs4all-part-1/</guid>
      
      
      
      
      <description>&lt;p&gt;In 2016 and 2017, I photographed, reported, and wrote &lt;a href=&#34;https://emptysqua.re/blog/series/cs4all&#34;&gt;a two-part series&lt;/a&gt; on MongoDB&amp;rsquo;s sponsorship of computer science education in NYC public schools. Those articles were eventually deleted from MongoDB&amp;rsquo;s engineering blog, so I&amp;rsquo;m reposting them here, eight years later. Part 1 is below, and Part 2 is &lt;a href=&#34;https://emptysqua.re/blog/cs4all-part-2&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;MongoDB&amp;rsquo;s Investment in CS Education&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image1.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Until last year, Jeremy Mellema was a history teacher. Now, he&amp;rsquo;s teaching computer programming. When I visited his class in the Bronx this month, he had 30 students with 30 Macbooks, completing exercises in Python. They had just finished a lesson on data types, and now they were tackling variables. In Jeremy&amp;rsquo;s class, the first variable assignment is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;tupac&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Greatest of All Time!!&amp;#34;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1&gt;Initiative&lt;/h1&gt;

&lt;p&gt;A year ago, New York City mayor Bill de Blasio &lt;a href=&#34;http://www.nytimes.com/2015/09/16/nyregion/de-blasio-to-announce-10-year-deadline-to-offer-computer-science-to-all-students.html&#34;&gt;announced Computer Science for All&lt;/a&gt;, an $80 million public-private partnership. The goal is to teach computer science to every student at every public school. But first, the schools need curriculum, and 5000 teachers need training.&lt;/p&gt;
&lt;p&gt;Here at MongoDB, our VP of Education Shannon Bradshaw oversees MongoDB University, which trains IT professionals to use MongoDB. When he heard about CS4All, he wanted us to contribute. He proposed that we set aside budget for two paid fellowships, and recruit public school teachers to spend the summer with us. We would develop them as teachers, and help build curricula they could take back into schools this fall. MongoDB staff would share our expertise, our office space, our equipment, and the MongoDB software itself.&lt;/p&gt;
&lt;p&gt;Shannon pitched his proposal to the company like this: &amp;ldquo;As many of us know, it&amp;rsquo;s still unusual for students to encounter computer science, let alone databases, in their classrooms before entering college. I believe this absence directly contributes to the gender and racial disparity we see today across our industry.&amp;rdquo; The &lt;a href=&#34;http://www.csnyc.org/computer-science-all&#34;&gt;CS4All project&lt;/a&gt; improves access to these subjects for many more students in our city, and MongoDB could be part of it from the beginning.&lt;/p&gt;
&lt;h1&gt;Introducing Tim and Jeremy&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image2.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The teachers we hired for the summer are at opposite ends of a spectrum. At the more technical end is Tim Chen. He majored in Math and took some CS classes in college. When he applied to work for the NYC Department of Education, there was a job opening for a Tech Teacher. &amp;ldquo;I didn&amp;rsquo;t know what &amp;rsquo;tech&amp;rsquo; meant at the time,&amp;rdquo; he said, &amp;ldquo;but I lucked out because it was teaching really basic software engineering.&amp;rdquo; His first year teaching, he taught the 9th grade curriculum for the Software Engineering Program. Now, he&amp;rsquo;s an experienced CS teacher, and his students this fall are comparatively advanced programmers. He spent his summer at MongoDB outlining an ambitious two-year curriculum in Python and Javascript.&lt;/p&gt;
&lt;p&gt;Jeremy Mellema, on the other hand, was teaching 10th grade world history when he was tapped to teach a CS class last year. He took an intensive training with the NYC Department of Education&amp;rsquo;s Software Engineering Program, where he spent a day apiece working in Python, HTML, CSS, JavaScript, Scratch, and Arduino. That fall was a struggle. &amp;ldquo;It was pretty overwhelming,&amp;rdquo; he said. Jeremy is a skilled teacher, but on the topic of software, he &amp;ldquo;just didn&amp;rsquo;t know a whole lot.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image3.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Jeremy joined us this summer to meet software engineers and learn to code more like a professional. His presence is particularly valuable because he&amp;rsquo;s not a specialist: he&amp;rsquo;ll ensure the curriculum he builds can be taught by other teachers who aren&amp;rsquo;t already coders. Shannon assigned him the book How to Think Like a Computer Scientist, and he spent the summer turning its contents into something high schoolers can learn from. He told me, &amp;ldquo;I was excited to actually get my hands dirty with what it means to be a programmer and learn how to do things besides basic Scratch programming, drag and drop, and to see how you actually use it in the real world.&amp;rdquo;&lt;/p&gt;
&lt;h1&gt;Foreshadowing&lt;/h1&gt;

&lt;p&gt;Now that school is back in session, I&amp;rsquo;m watching Tim and Jeremy&amp;rsquo;s progress with fascination. Their students come to CS from a different angle than I did: I have the typical background of a computer programmer in this country, with college-educated parents and all the privileges that typically pave the way to a career in software. I taught myself to code in high school and got a Bachelor&amp;rsquo;s in CS from Oberlin College. But the students in Tim and Jeremy&amp;rsquo;s classes nearly all come from low-income families and qualify for federal meal assistance. These students have self-selected into the computer track, and they are mostly boys; but there are a half-dozen girls in both classes. The majority are Black or Hispanic, and Jeremy&amp;rsquo;s class also includes many recent immigrants from the Caribbean and Bangladesh. If young people like them succeed as software engineers, it will go a long way to addressing the inequalities of our industry and our society as a whole.&lt;/p&gt;
&lt;p&gt;But the goal for Tim and Jeremy this year is more modest. They will prove and refine their curricula in their classrooms; then the materials can be used by any teacher in the New York City public schools. I visited their classes to watch their plans being put to the test.&lt;/p&gt;
&lt;h1&gt;Jeremy&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image4.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;When I saw Jeremy at Bronx Compass High School, he&amp;rsquo;d had the students for one week. He was nervous the month before about how class would go: &amp;ldquo;I&amp;rsquo;m always afraid it&amp;rsquo;s going to bomb, or the students won&amp;rsquo;t find it interesting.&amp;rdquo; But the kids were hacking enthusiastically. Watching them reminded me of my own joy when I first learned to code.&lt;/p&gt;
&lt;p&gt;Class time is spent working independently while Jeremy checks in with each student. The kids present a huge range of skills: some have taken computer science classes for several years, and others have just moved to New York and are getting their first exposure to CS. Jeremy worries about challenging all his students according to their level.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image5.jpg&#34; style=&#34;display:inline;width:auto;height:auto;vertical-align:middle&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;br&gt;
An advanced student, Tatiana, is able to use this wide skill range to make the class more effective for the novice students. I noticed her sitting sideways in her chair so she could coach the students to her side and behind her. &amp;ldquo;It&amp;rsquo;s nice to help people who struggle with coding because it&amp;rsquo;s like a mixture of math and grammar,&amp;rdquo; she said. &amp;ldquo;When you write &amp;lsquo;print&amp;rsquo;, if you write it with a capital P your code won&amp;rsquo;t work. It&amp;rsquo;s like teaching them a new language.&amp;rdquo; Tatiana plans to keep coding after high school: &amp;ldquo;I like making things work the way I want them to work.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Since my formal CS education began in college, I&amp;rsquo;ve never sat in a high school programming class. To my surprise, 30 students can be trusted to stay on task, even when they&amp;rsquo;re on laptops with the whole Internet in reach. Jeremy doesn&amp;rsquo;t mind when they take detours to the Web. He said, &amp;ldquo;If they just need YouTube to play music to drown out their classmates that&amp;rsquo;s fine.&amp;rdquo; I saw his student Amaury with headphones on and YouTube playing in the background in order to concentrate, the same way I use music at the MongoDB office. Jeremy gives him space to work in his own style: &amp;ldquo;It&amp;rsquo;s fine. As soon as I ask Amaury a question, his headphones are off and he answers.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I asked Amaury to take off his headphones for a minute and tell me how the class was going. &amp;ldquo;It&amp;rsquo;s cool learning how a computer works using just a bunch of freaking inputs. Put in a couple lines of code and you can have the computer do crazy stuff.&amp;rdquo; Then his headphones went back on and he got back to hacking.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image6.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;By the end of this year, Jeremy hopes his kids will advance from sandboxed programming environments to real-world tools—that they&amp;rsquo;ll install Python on their computers and code on their own. &amp;ldquo;The kids I&amp;rsquo;ve seen really do well,&amp;rdquo; he said, &amp;ldquo;this will open the door for them to take the world by storm. There&amp;rsquo;s a lot of really smart and talented kids who are not in an advantaged place. This will put them at an advantage.&amp;rdquo;&lt;/p&gt;
&lt;h2&gt;Tim&lt;/h2&gt;


&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image7.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Tim Chen teaches a few blocks from the MongoDB office, at Urban Assembly Gateway School for Technology on the west side of Manhattan. His students have specialized in CS more than Jeremy&amp;rsquo;s, and he has more time with them: 45 minutes a day, 5 days a week. His curriculum is accordingly more ambitious. This year he&amp;rsquo;s teaching introductory programming in Python, aligned with the upcoming version of the Advanced Placement test in Computer Science Principles. Beginning with Python makes entry to the subject easy for Tim&amp;rsquo;s students, but by the end of the year they&amp;rsquo;ll cover nearly every topic of a first semester college class. Next year they&amp;rsquo;ll switch to Javascript. They&amp;rsquo;ll build web applications with MongoDB, Express, and Node. These three technologies (along with Angular.js) make up the famous &lt;a href=&#34;https://www.mongodb.com/resources/languages/mean-stack&#34;&gt;MEAN Stack&lt;/a&gt;, which Tim learned this summer from &lt;a href=&#34;https://learn.mongodb.com/&#34;&gt;MongoDB University&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When I asked Tim how his curriculum performed in its first week, he said there&amp;rsquo;d been no surprises yet—but he&amp;rsquo;s still in the process of writing it. Only the first semester&amp;rsquo;s material is complete. &amp;ldquo;My greatest fear is that they&amp;rsquo;ll move faster than I can create it,&amp;rdquo; he said. Still, he expects his students to be forgiving if they hit any bumps. &amp;ldquo;I told them this is my first year trying this out, so we&amp;rsquo;re going to try things, and if it doesn&amp;rsquo;t work out we&amp;rsquo;re going to try different things. They&amp;rsquo;re cool with it.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image8.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Scaling up&lt;/h1&gt;

&lt;p&gt;Teaching one class in Hell&amp;rsquo;s Kitchen and one in the Bronx is only the start. Tim wants to create a curriculum that any teacher can pick up and deliver to their students. Jeremy has the same goal, and because of his background as a history teacher, rather than a computer scientist, he is focused on making his course effective for teachers like him.&lt;/p&gt;
&lt;p&gt;For MongoDB&amp;rsquo;s VP of Education Shannon Bradshaw, this teacher-training is the most unexpected, and the most inspiring, aspect of our involvement. &amp;ldquo;Most of the people who will teach computer science in the New York City public schools are transitioning from another discipline,&amp;rdquo; he said. &amp;ldquo;They&amp;rsquo;re never going to be hardcore software engineers. They&amp;rsquo;re professional teachers; that&amp;rsquo;s what they want to do.&amp;rdquo; But in nine years, computer science will be a core class in New York City, and the existing staff will have to teach it. &amp;ldquo;If we can contribute to figuring that out, how to make this transition as effective as possible for computing education in New York City and maybe elsewhere in the country, then we&amp;rsquo;ve gone miles beyond where I imagined us going.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/cs4all-part-1/image9.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Liza Mills</title>
      <link>https://emptysqua.re/blog/liza-mills-the-sting/</link>
      <pubDate>Thu, 22 May 2025 17:38:37 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/liza-mills-the-sting/</guid>
      
      
      
      
      <description>&lt;p&gt;Gunks climber &lt;a href=&#34;https://www.instagram.com/liza___mills/&#34;&gt;Liza Mills&lt;/a&gt; on the routes &lt;a href=&#34;https://www.mountainproject.com/route/106148478/lisa&#34;&gt;Lisa&lt;/a&gt; and &lt;a href=&#34;https://www.mountainproject.com/route/105898985/the-sting&#34;&gt;The Sting&lt;/a&gt; with her husband Jordan and their cat &lt;a href=&#34;https://www.instagram.com/dwaynetheadventurecat/&#34;&gt;Dwayne&lt;/a&gt;. The Sting ends with a dramatic dyno to a ledge; Liza latched it and sent the route on her first try of the day, but I&amp;rsquo;m including a photo of her falling on a repeat attempt, for spice.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-10.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-11.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-12.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-13.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/liza-mills-the-sting/mills-14.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;Shot on 35mm Portra 400 with a Canon EOS10, which has a rather fast motor drive for sports photography.&lt;/p&gt;
&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Jesse&#39;s Notes from the Antithesis BugBash Conference</title>
      <link>https://emptysqua.re/blog/notes-from-antithesis-bugbash/</link>
      <pubDate>Tue, 20 May 2025 22:33:34 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/notes-from-antithesis-bugbash/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image004.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Antithesis has pioneered &amp;ldquo;autonomous testing&amp;rdquo;, a term they coined for their platform, which combines a &lt;a href=&#34;https://antithesis.com/blog/deterministic_hypervisor/&#34;&gt;deterministic hypervisor&lt;/a&gt; with coverage-guided fuzzing and fault injection. &lt;a href=&#34;https://antithesis.com/case_studies/mongodb_productivity/&#34;&gt;My company MongoDB was one of their first customers&lt;/a&gt;—I evaluated their platform for MongoDB in 2020. At the time we had several very rare replication bugs, including one that manifested once in many &lt;strong&gt;years&lt;/strong&gt; of stress testing (tens of thousands of hours of compute), and Antithesis found them reliably in about 24 hours.&lt;/p&gt;
&lt;p&gt;Antithesis was in quiet mode until mid-2024 when they suddenly turned on the marketing switch, started blogging, and announced the &lt;a href=&#34;https://www.bugbash.antithesis.com/&#34;&gt;BugBash&lt;/a&gt; conference. This is a small (about 200 attendees) conference with invited speakers (they accepted lightning talk proposals). MongoDB&amp;rsquo;s distributed systems researcher Will Schultz gave a lightning talk on &lt;a href=&#34;https://github.com/will62794/spectacle&#34;&gt;Spectacle&lt;/a&gt;, and our new Atlas Federation engineer Stephanie Wang gave a regular talk on lessons learned from her time building a cloud DB at MotherDuck.&lt;/p&gt;
&lt;p&gt;The Antithesis CEO Will Wilson calls this &amp;ldquo;not a software testing conference, but a software &lt;em&gt;correctness&lt;/em&gt; conference&amp;rdquo;. Of course there was a fair amount of Antithesis marketing and education, but mostly the talks were about non-Antithesis software correctness topics.&lt;/p&gt;
&lt;p&gt;Antithesis is posting and summarizing the conference videos on their &lt;a href=&#34;https://antithesis.com/blog/&#34;&gt;blog&lt;/a&gt;, two have been posted as of 2025-05-24.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image001.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Talks&lt;/h1&gt;

&lt;p&gt;I attended almost all of the talks. Here are my notes from the ones I saw.&lt;/p&gt;
&lt;h2&gt;Will Wilson&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why is software buggy?&lt;/strong&gt; He&amp;rsquo;s the Antithesis CEO. He offered various theories about why we tolerate bugs, including my favorite, &amp;ldquo;it&amp;rsquo;s economically optimal to tolerate medium-quality software in most circumstances&amp;rdquo;, and his own favorite, &amp;ldquo;software engineering is a young discipline whose major innovations in quality are yet to come—soon we hope&amp;rdquo;.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image002.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Zac Hatfield-Dodds&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why is everything under-tested?&lt;/strong&gt; He&amp;rsquo;s now at Anthropic, but he has a longer history as the author of the Python property-based testing library Hypothesis. I &lt;a href=&#34;https://pyfound.blogspot.com/2020/05/property-based-testing-for-python.html&#34;&gt;covered his work&lt;/a&gt; when I was a blogger for the Python Software Foundation. He described property-based testing and its advantages, and theorized that the main barrier to adoption is awareness and education. There has been a major uptick in Hypothesis usage recently.&lt;/p&gt;
&lt;p&gt;Zac said something like, &amp;ldquo;coverage-guided fuzzing reduces the time to find a bug from exponential to polynomial&amp;rdquo;. This sent me on &lt;a href=&#34;https://emptysqua.re/blog/how-long-must-i-test/&#34;&gt;a side-quest, not yet completed, about the efficiency of randomized testing&lt;/a&gt;.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image003.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Stephanie Wang&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reliability lessons from building a cloud data warehouse from 0 to 1.&lt;/strong&gt; Stephanie was at MotherDuck until very recently, now at MongoDB on Atlas Data Federation. She described the challenges of layering a cloud DB on top of the DuckDB embedded columnar database.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image005.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Ben Eggers&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You should test in prod.&lt;/strong&gt; This is like Netflix&amp;rsquo;s &amp;ldquo;Chaos Engineering&amp;rdquo;, though I guess that term is passé for obscure factional reasons. Eggers describes testing in prod at OpenAI: how to convince your manager, what to test, how to test it, how to do so safely. You should do it during waking hours, warn everyone ahead of time, and have a concrete hypothesis that you&amp;rsquo;re testing. E.g., don&amp;rsquo;t test &amp;ldquo;what happens when I take down this database?&amp;rdquo; Test, &amp;ldquo;I think when I take down this database that service X will go offline but service Y will stay up.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image006.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Kyle Kingsbury&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Jepsen 17: ACID jazz.&lt;/strong&gt; Standard Jepsen talk: he tested a few distributed services and found some interesting consistency violations. Extremely entertaining as always.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image007.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Ryan Worl&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Multi-Tenant Bill of Rights: observability for all.&lt;/strong&gt; An argument for investing in observability, with the twist that it helps &amp;ldquo;noisy neighbor&amp;rdquo; interference among tenants.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image008.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Mitchell Hashimoto&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can we test it? Yes, we can!&lt;/strong&gt; Hashimoto created Vagrant, Terraform, and other cloud infrastructure tech, now he&amp;rsquo;s made a surprising pivot and develops a terminal emulator called Ghostty. He talked about refactoring code to make it unittestable and how to test GUIs.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image009.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Lawrie Green&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Autonomous testing masterclass: How to succeed in software testing without really trying (or, a guide to autonomous testing).&lt;/strong&gt; Principles for using Antithesis. Don&amp;rsquo;t test specific sequences of steps, test software &lt;em&gt;properties&lt;/em&gt; and let Antithesis generate all the sequences. Properties can include &amp;ldquo;X is always true&amp;rdquo; or &amp;ldquo;never true&amp;rdquo; or &amp;ldquo;eventually true&amp;rdquo;, or &amp;ldquo;a state is unreachable&amp;rdquo; or &amp;ldquo;a state is sometimes reachable&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I noticed that &amp;ldquo;always&amp;rdquo; and &amp;ldquo;eventually&amp;rdquo; are temporal operators in TLA+ (and other temporal logics) and they can be evaluated on a single &lt;em&gt;behavior&lt;/em&gt; of a program. &amp;ldquo;Eventually&amp;rdquo; is a &lt;em&gt;liveness&lt;/em&gt; property that in theory can only be evaluated on an infinitely-long behavior. But Antithesis lets you configure a test like, &amp;ldquo;once all fault-injection ceases, the system achieves state X before some timeout T&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Sometimes&amp;rdquo; and &amp;ldquo;unreachable&amp;rdquo; are &lt;a href=&#34;https://www.hillelwayne.com/post/hyperproperties/&#34;&gt;hyperproperties&lt;/a&gt; that must be evaluated on the &lt;em&gt;set of all possible behaviors&lt;/em&gt;. They&amp;rsquo;re not supported in TLA+. It&amp;rsquo;s cool that Antithesis allows them.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image010.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h2&gt;Ankush Desai&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gain confidence in system correctness using formal and semi-formal methods.&lt;/strong&gt; Ankush talked about &lt;a href=&#34;https://p-org.github.io/P/whatisP/&#34;&gt;P&lt;/a&gt;, his formal modeling language. Unlike TLA+, P has a very Pythonic syntax and an intuitive structure: communicating processes.&lt;/p&gt;
&lt;p&gt;I think Ankush said that procedure bodies in P can actually be coded in Python, they don&amp;rsquo;t just &lt;em&gt;look&lt;/em&gt; like Python? Unclear.&lt;/p&gt;
&lt;p&gt;P has two conformance-assurance features that interest me, but they seem shady. 1) The docs say P can be compiled to C/C#/Java, but Ankush didn&amp;rsquo;t mention this in his talk so maybe it&amp;rsquo;s not fully implemented. 2) P supports trace-checking: you can add logging statements to your implementation and feed the logs to a trace-checking utility called PObserve. Ankush mentioned this utility in his 2022 HPTS talk (&lt;a href=&#34;https://hpts.ws/papers/2022/HPTS2022.pdf&#34;&gt;slides&lt;/a&gt;, p. 20), and it gets a paragraph in Ankush and Marc Brooker&amp;rsquo;s &lt;a href=&#34;https://dl.acm.org/doi/10.1145/3712057&#34;&gt;Systems Correctness Practices at AWS: Leveraging Formal and Semi-formal Methods&lt;/a&gt;, and he mentioned it again at BugBash. But PObserve doesn&amp;rsquo;t seem open source or described in detail anywhere.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image011.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Lightning talks&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Felicitas Pojtinger: Commoditize your CI compute.&lt;/strong&gt; She started with a nice idea from &lt;a href=&#34;https://www.joelonsoftware.com/2002/06/12/strategy-letter-v/&#34;&gt;Spolsky&lt;/a&gt;: &amp;ldquo;Smart companies try to commoditize their products’ complements.&amp;rdquo; She showed a technology from startup &lt;a href=&#34;https://loopholelabs.io/&#34;&gt;Loophole&lt;/a&gt; that makes cloud providers&amp;rsquo; spot instances an interchangeable commodity, by live-migrating onto and off of spot instances without losing state. You can have a long-running job jump from one spot instance to the next, across cloud providers, while maintaining TCP connections etc.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image012.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;strong&gt;William Schultz: Interactive formal specifications.&lt;/strong&gt; Will (MongoDB research) has renamed his tla-web tool to &lt;a href=&#34;https://github.com/will62794/spectacle&#34;&gt;Spectacle&lt;/a&gt;, it&amp;rsquo;s a partial implementation of TLA+ in JavaScript. You can interactively run a spec&amp;rsquo;s actions forward and backward, choosing the next state at each transition and inspecting variables and visualizations, and share traces with your colleagues using a URL.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image013.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;strong&gt;Jayaprabhakar Kadarkarai: Modeling distributed systems.&lt;/strong&gt; &amp;ldquo;JP&amp;rdquo; has a new modeling language FizzBee. His language focuses on ease-of-use for engineers, which I agree is lacking in most other languages. He&amp;rsquo;s made impressive progress developing solo for the last 2-3 years. FizzBee has a Pythonic syntax and a ton of features, like several builtin visualizations and interactivity, and a prototype of &lt;a href=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/&#34;&gt;probabilistic performance modeling&lt;/a&gt;.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image014.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;strong&gt;Marco Primi: The traveling anteater.&lt;/strong&gt; Entertaining talk about using Antithesis to &amp;ldquo;solve&amp;rdquo; the traveling salesman problem, just demonstrates that Antithesis is a heuristic tree-searcher.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image015.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;State of Reliability panel discussion&lt;/h1&gt;

&lt;p&gt;Five men with war stories, jokes, and conflicting opinions. One interesting idea: a goal of quality and testing is to reduce nondeterminism, but AI and ML are shifting us to a world where good software is legit nondeterministic. We&amp;rsquo;ll have to develop a new testing philosophy.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image016.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Evaluation&lt;/h1&gt;

&lt;p&gt;The conference organizers had an all-male speaker lineup when they first announced BugBash. They corrected this, but they still had no women on their panel. I complained about this, and I hope they do better next year.&lt;/p&gt;
&lt;p&gt;I knew BugBash would be a relevant conference for me and the &lt;a href=&#34;https://www.mongodb.com/company/research/distributed-systems-research-group&#34;&gt;MongoDB Distributed Systems Research Group&lt;/a&gt;, but it was far better and more relevant than I expected. Lots of people I know from distributed systems research and formal methods were there. I made useful connections with people, I learned valuable things, and I had some moments of inspiration that could become research projects.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/image017.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Images by the sublime &lt;a href=&#34;https://www.scaruffi.com/museums/artzybas/index.html&#34;&gt;Boris Artzybasheff&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Are We Serious About Using TLA&#43; For Statistical Properties?</title>
      <link>https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/</link>
      <pubDate>Sat, 10 May 2025 10:59:24 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/</guid>
      
      
      
      
      <description>&lt;p&gt;At this year&amp;rsquo;s &lt;a href=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/&#34;&gt;TLA&lt;sup&gt;+&lt;/sup&gt; community event&lt;/a&gt;, I tentatively proposed adding features to make the language useful for performance modeling. Here&amp;rsquo;s the video, and a written version is below.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/Wekywox2Ghk?si=p19oHE_uIDtcZwb5&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;hr&gt;


&lt;h1&gt;Half Our Problems&lt;/h1&gt;

&lt;p&gt;In 2022, I saw Marc Brooker give a talk called &amp;ldquo;Formal Methods Only Solve Half My Problems&amp;rdquo; at &lt;a href=&#34;https://emptysqua.re/blog/2022-hpts-notes/&#34;&gt;HPTS&lt;/a&gt;. &lt;a href=&#34;https://brooker.co.za/blog/2022/06/02/formal.html&#34;&gt;He published a blog post about this too&lt;/a&gt;. He said that TLA&lt;sup&gt;+&lt;/sup&gt; can check correctness (safety and liveness), but not performance characteristics. &amp;ldquo;What I want is tools that do both: tools that allow development of formal models &amp;hellip; and then allow us to ask those models questions about design performance.&amp;rdquo; He acknowledges that correctness is important. But it&amp;rsquo;s not enough to say nothing bad ever happens, and something good eventually happens—we want to know how &lt;em&gt;quickly&lt;/em&gt; something good happens!&lt;/p&gt;
&lt;p&gt;How would we actually create such a tool? Marc said that queueing theory was the kind of math that was obviously useful for this.&lt;/p&gt;
&lt;h1&gt;Learn Queueing Theory?&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/queueing-book.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;So I decided to learn queueing theory. I asked Marc what book I should read, and he recommended &lt;em&gt;Performance Modeling and Design of Computer Systems, Queuing Theory in Action&lt;/em&gt;. The title sounds like it&amp;rsquo;s the perfect book for our purposes, so I recruited Andrew Helwer and Murat Demirbas to read it with me. &lt;a href=&#34;https://emptysqua.re/blog/review-queue-theory-book/&#34;&gt;We spent 8 months reading most of the book and doing a lot of the problem sets&lt;/a&gt;. It was definitely interesting, but mostly irrelevant to the actual problem of, you know, &lt;em&gt;performance modeling and design of computer systems&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As an example of a queueing theory problem, here&amp;rsquo;s a diagram from Chapter 2 of the book.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/queueing-network.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;There are three servers, each with a queue where tasks arrive. A server processes tasks from its queue, and then the task either leaves the network, or it gets sent to another server&amp;rsquo;s queue. There are probability distributions for all these events: the delays between task arrivals, the time it takes for a server to process a task, the chance a task leaves the network or goes to another server.&lt;/p&gt;
&lt;p&gt;What kinds of questions does queueing theory answer about this system?&lt;/p&gt;
&lt;p&gt;In the book, one of the exercises asks a classic queueing theory question, which is also a practical question: what&amp;rsquo;s the maximum arrival rate at Server 1&amp;rsquo;s queue that the system can handle?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/queueing-exercise.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I did my homework with paper and pencil. I solved some equations using the techniques in the book and came up with an answer: the max arrival rate is 5.2.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/pencil.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I don&amp;rsquo;t think we should try to answer performance questions by writing equations and solving for a variable! It&amp;rsquo;s hard to get right, easy to get wrong, and you have to recalculate every time the inputs change. I think we should just use simulation—run an experiment thousands of times and take the average. Call it &amp;ldquo;Monte Carlo&amp;rdquo; simulation if that makes you feel better.&lt;/p&gt;
&lt;h1&gt;Java Modelling Tools&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/jmt.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;There&amp;rsquo;s an open source toolkit that will do this for you, an academic project called &lt;a href=&#34;https://emptysqua.re/blog/java-modelling-tools/&#34;&gt;the Java Modelling Tools&lt;/a&gt;. They&amp;rsquo;re a bunch of tools for modeling queue networks and then answering questions about them, through some combination of equation-solving and simulation. I started from the exercise in the book, and I used JMT to draw this very ugly version of the same queue network:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/jmt-diagram.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I configured a bunch of things and ran a simulation where I varied the arrival rate at Server 1:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/jmt-simulation.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;JMT lets me &lt;em&gt;sweep&lt;/em&gt; the value of the arrival rate from 0 to 10 by increments of 0.1—that&amp;rsquo;s the &lt;em&gt;independent&lt;/em&gt; variable—and I can simulate the system and measure the server utilization that results—that&amp;rsquo;s the &lt;em&gt;dependent&lt;/em&gt; variable. You can see that when the arrival rate reaches 5.2, server utilization reaches 1.0, meaning it&amp;rsquo;s completely utilized and the system has reached its capacity. That&amp;rsquo;s the answer I got with my pencil, too. This kind of simulation works even for complex queue networks where the math is hard or impossible.&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;ve learned from the book and the tool is that queueing theory has super-useful concepts: arrival rate, service rate, utilization, ergodicity, Little&amp;rsquo;s Law, service discipline, open vs. closed loop, and many more. But the math is heinous. There are some simple equations to start you off, like Little&amp;rsquo;s Law, and then all hell breaks loose. So, don&amp;rsquo;t try to learn the math. No matter how good you are at math, queueing theory math won&amp;rsquo;t get you what you want. Specifically, you&amp;rsquo;ll never be able to estimate system performance by solving equations. You can only solve the equations for extremely simple systems. If you want to model a real-world system, the math will be an open research question, or literally impossible.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://brooker.co.za/blog/2022/04/11/simulation.html&#34;&gt;Just run simulations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If we use simulated queue networks to model performance, have we solved both Marc&amp;rsquo;s problems? No, Marc wants one tool that &lt;strong&gt;both&lt;/strong&gt; checks an algorithm&amp;rsquo;s correctness and models its performance, with a single specification.&lt;/p&gt;
&lt;h1&gt;TLA+ for Performance Modeling&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/jack-markus.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The same year Marc talked about this, Jack Vanlightly and Markus Kuppe presented &amp;ldquo;Obtaining Statistical Properties via TLC Simulation.&amp;rdquo; They described how to write a spec and use TLC to measure statistics. (&lt;a href=&#34;https://www.youtube.com/watch?v=cYenTPD7740&#34;&gt;Video&lt;/a&gt;, &lt;a href=&#34;https://conf.tlapl.us/2022/JackMarkusTLA+Statistics.pdf&#34;&gt;slides&lt;/a&gt;, &lt;a href=&#34;https://jack-vanlightly.com/blog/2024/11/19/obtaining-statistical-properties-through-modeling-and-simulation&#34;&gt;Jack&amp;rsquo;s article&lt;/a&gt;, &lt;a href=&#34;https://muratbuffalo.blogspot.com/2022/10/checking-statistical-properties-of.html&#34;&gt;Murat&amp;rsquo;s review&lt;/a&gt;.) I&amp;rsquo;ll show some code examples from &lt;a href=&#34;https://github.com/Vanlightly/formal-methods-playground/blob/master/tla/tlaplus-conf/swim/swim_stats.tla&#34;&gt;Jack&amp;rsquo;s spec for a gossip protocol&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\* Increment the updates counter by the number of incoming peer states.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TLCSet(updates_ctr_id, TLCGet(updates_ctr_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                       + Cardinality(DOMAIN incoming_peer_states))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The spec uses TLCSet to record each statistic. Here it&amp;rsquo;s counting the number of times nodes get new information. TLCSet and TLCGet access a global scratchpad, so you can record things outside of the state space.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CSVWrite(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;%1$s,%2$s,%3$s,%4$s,%5$s,%6$s,%7$s,%8$s,%9$s,&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \o &amp;#34;%10$s,%11$s,%12$s,%13$s,%14$s,%15$s,%16$s,&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \o &amp;#34;%17$s,%18$s,%19$s,%20$s,%21$s,%22$s,%23$s,&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \o &amp;#34;%24$s,%25$s&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&amp;lt;behaviour_id, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r, RoundMessageLoad(r), DirectProbeDeadMessageLoad(r),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IndirectProbeDeadMessageLoad(r), TLCGet(updates_pr_ctr(r)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    TLCGet(eff_updates_pr_ctr(r)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    alive_count, suspect_count, dead_count, alive_states_count,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    suspect_states_count, dead_states_count,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    infective_states_count, infectivity, cfg_num_members,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg_dead_members, cfg_new_members, SuspectTimeout,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    DisseminationLimit, cfg_max_updates, cfg_lose_nth,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg_peer_group_size, cfg_initial_contacts, MaxRound&amp;gt;&amp;gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  RoundStatsCSV)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code appends a line to the CSV file, recording the results of the execution.
Jack had to hand-code this in TLA&lt;sup&gt;+&lt;/sup&gt;, and hand-code the logic for when to run this during TLC&amp;rsquo;s execution, and so on. It&amp;rsquo;s impressive that it works at all, but it&amp;rsquo;s kind of a hack.&lt;/p&gt;
&lt;p&gt;So my &lt;strong&gt;Complaint Number 1&lt;/strong&gt; is the syntax. What we have now in TLA&lt;sup&gt;+&lt;/sup&gt; is hard to write, it attracts bugs, and it clutters up the specification.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\*  &amp;#39;probabilistic&amp;#39; is a random chance of losing the message
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\*  &amp;#39;exhaustive&amp;#39; is for model checking where both options are explored
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GetDeliveredCount() ==
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  CASE MessageLossMode = &amp;#34;probabilistic&amp;#34; -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      IF RandomElement(1..cfg_lose_nth) = cfg_lose_nth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      THEN {0}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ELSE {1}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [] MessageLossMode = &amp;#34;exhaustive&amp;#34; -&amp;gt; {0,1}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SendMessage(msg) ==
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \E delivered_count \in GetDeliveredCount() :
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    \* ... send the message if delivered_count is 1 ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s some code to determine whether a message is delivered or lost. Jack has a config option called MessageLossMode, he has to set it to &amp;ldquo;probabilistic&amp;rdquo; to make statistics work, or set it to &amp;ldquo;exhaustive&amp;rdquo; to make model-checking work. My &lt;strong&gt;Complaint Number 2&lt;/strong&gt; is that randomization is incompatible with model-checking and you have to code workarounds. (&lt;strong&gt;Correction&lt;/strong&gt;: Markus says this is fixed now!)&lt;/p&gt;
&lt;p&gt;Jack has another a constant called cfg_lose_nth, let&amp;rsquo;s say it&amp;rsquo;s 4, then in probabilistic mode there&amp;rsquo;s a one-in-four chance that a message is lost. You could extend this to any &lt;em&gt;rational&lt;/em&gt; number, like two-out-of-three, but you can&amp;rsquo;t work with irrational numbers. TLC doesn&amp;rsquo;t have floating-point numbers at all. So my &lt;strong&gt;Complaint Number 3&lt;/strong&gt; is that randomization is very limited. It&amp;rsquo;s just Dungeons &amp;amp; Dragons dice throws.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/dice.png&#34; alt=&#34;dice&#34; style=&#34;max-width: 50%; margin-bottom: 1em&#34; /&gt;
&lt;/div&gt;
&lt;p&gt;If you want to measure performance, you need some sort of &lt;em&gt;cost function&lt;/em&gt;. E.g., sending a message to Tokyo might cost 2.5 times as much as sending it to London. Or its cost might be exponentially distributed. Here&amp;rsquo;s some imaginary TLA&lt;sup&gt;+&lt;/sup&gt; for accumulating the cost of an algorithm:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\* In your dreams
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TLCSet(cost, TLCGet(cost) + 1.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TLCSet(cost, TLCGet(cost) + 2.5)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TLCSet(cost, TLCGet(cost) + Exponential(3))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All the examples I&amp;rsquo;ve seen of statistical properties in TLA&lt;sup&gt;+&lt;/sup&gt; are counting the number of times some discrete event occurs, like counting the number of messages. But if we want to measure the performance of a system as a whole, we need to measure a variety of events, and they have to have &lt;em&gt;relative&lt;/em&gt; costs! &lt;strong&gt;Complaint Number 4&lt;/strong&gt; is we have no floats and no probability distributions besides &amp;ldquo;uniform&amp;rdquo;.&lt;/p&gt;
&lt;h1&gt;Statistical Properties in TLA&lt;sup&gt;+&lt;/sup&gt;, So Far&lt;/h1&gt;

&lt;p&gt;I think it&amp;rsquo;s just a proof of concept. Jack and Markus&amp;rsquo;s technique works and it&amp;rsquo;s useful. What they accomplished is impressive and it started the conversation. But it&amp;rsquo;s insufficient if we&amp;rsquo;re serious about using TLA&lt;sup&gt;+&lt;/sup&gt; for statistics. TLCSet and the CSV module provide a manual, low-level API to save statistics. Compared to safety and liveness checking, which are first-class features of the language and tools, statistical modeling really clutters up the specification.&lt;/p&gt;
&lt;p&gt;As we saw with Jack&amp;rsquo;s gossip protocol, it&amp;rsquo;s not only a large amount of code, but it&amp;rsquo;s error-prone, you have to write it into each spec, and you don&amp;rsquo;t get the benefit of a tested and documented library. People need a concise and easy API for saving statistics.&lt;/p&gt;
&lt;p&gt;The Randomization module only gives us uniform distributions with integer parameters. Performance modeling requires lots of distributions, such as Poisson, exponential, and Zipf. Limiting probabilities to ratios of integers is an artifact of our unfinished tooling; we should give TLA&lt;sup&gt;+&lt;/sup&gt; authors the freedom to choose whatever probabilities they want.&lt;/p&gt;
&lt;p&gt;If you want to model the performance of a whole system, you need to combine the costs of different actions. You can do that manually with TLCSet, but there&amp;rsquo;s no standard convenient way to do it.&lt;/p&gt;
&lt;p&gt;And there are no floats. This makes sense for model-checking, but it&amp;rsquo;s a silly limitation for performance modeling. I&amp;rsquo;ll stop talking about that before I get too annoying.&lt;/p&gt;
&lt;h1&gt;Statistical Properties: Are We Serious?&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/clock.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;So, what we have right now in  TLA&lt;sup&gt;+&lt;/sup&gt; isn&amp;rsquo;t really solving both of Marc&amp;rsquo;s problems. Should we try? Should  TLA&lt;sup&gt;+&lt;/sup&gt; be the tool that solves both, or should we stay focused on safety?&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know. I haven&amp;rsquo;t implemented anything, I&amp;rsquo;m just offering ideas. And I want to admit I&amp;rsquo;m not offering any time or money to this project, so I can&amp;rsquo;t make any demands. I&amp;rsquo;m just brainstorming with you.&lt;/p&gt;
&lt;h1&gt;State of the Art&lt;/h1&gt;

&lt;p&gt;As a start, let&amp;rsquo;s look at some existing tools: JMT, PRISM, Runway, and FizzBee.&lt;/p&gt;
&lt;h2&gt;State of the Art #1 of 4: Java Modelling Tools&lt;/h2&gt;

&lt;p&gt;We saw JMT above; it&amp;rsquo;s the tool where you can draw queue networks. It&amp;rsquo;s made for statistical modeling and answering performance questions. It has a point-and-click interface, which is good for small models and bad for big ones. There&amp;rsquo;s also a Python API for generating large models. It comes with lots of probability distributions built-in, it can model cost functions, and you can use real-world data sets as inputs! E.g., if you have some data about the distribution of network latencies, you can import that into a JMT model.&lt;/p&gt;
&lt;h2&gt;State of the Art #2 of 4: PRISM&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://www.hillelwayne.com/tags/prism/&#34;&gt;PRISM&lt;/a&gt; is built for exactly this problem. It&amp;rsquo;s a &amp;ldquo;probabilistic model checker&amp;rdquo;: you write a spec of an algorithm and ask statistical questions, like the probability of something happening, or the average cost.&lt;/p&gt;
&lt;p&gt;Actions in PRISM look like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[my_action] x=0 -&amp;gt; 0.8:(x&amp;#39;=1) + 0.2:(x&amp;#39;=2);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You have an action name (&amp;ldquo;my_action&amp;rdquo;), a guard, which is some predicate (this action is enabled if &lt;em&gt;x&lt;/em&gt; is zero), and some state updates with probabilities. There&amp;rsquo;s a 0.8 probability that it sets &lt;em&gt;x&lt;/em&gt; to 1 and a 0.2 probability that it sets &lt;em&gt;x&lt;/em&gt; to 2. These numbers are either &lt;em&gt;probabilities&lt;/em&gt; that something happens, or the &lt;em&gt;rates&lt;/em&gt; at which things happen, depending on whether you&amp;rsquo;re writing a discrete-time or continuous-time model.&lt;/p&gt;
&lt;p&gt;PRISM has built-in support for cost functions. A cost is just anything you want to measure, any way of expressing performance. PRISM calls costs &amp;ldquo;rewards,&amp;rdquo; but if you&amp;rsquo;re thinking of reinforcement learning, stop. This is not related to reinforcement learning.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;rewards
    x=0 : 100;
    x&amp;gt;0 &amp;amp; x&amp;lt;10 : 2*x;
endrewards
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code says that every state where &lt;em&gt;x&lt;/em&gt; equals zero has a reward of 100, and if &lt;em&gt;x&lt;/em&gt; is between 0 and 10 then the reward is twice &lt;em&gt;x&lt;/em&gt;. You can imagine writing a reward function that measures revenue, or a reward function that&amp;rsquo;s actually a cost function that measures something bad, like latency.&lt;/p&gt;
&lt;p&gt;PRISM has remarkably powerful property expressions. Here&amp;rsquo;s an example from their website, it&amp;rsquo;s &amp;ldquo;the probability that more than 5 errors occur within the first 100 time units is less than 0.1.&amp;rdquo; This is an &lt;em&gt;assertion&lt;/em&gt; that PRISM checks.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;P&amp;lt;0.1 [ F&amp;lt;=100 num_errors &amp;gt; 5 ]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;rsquo;s another, &amp;ldquo;the probability that process 1 terminates before process 2 does.&amp;rdquo; This is a question—what &lt;em&gt;is&lt;/em&gt; the probability?—and PRISM will give you the answer.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;P=? [ !proc2_terminate U proc1_terminate ]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You could express safety as an assertion: the probability of something bad happening is zero. Liveness is an assertion that the long-run probability of something good happening is 1. But there are much more complex and powerful probabilistic properties you can express and check in PRISM, like saying that the 95th-percentile latency of an algorithm is less than some acceptable amount.&lt;/p&gt;
&lt;p&gt;The PRISM website has a bunch of great examples. Here are statistics from a &lt;a href=&#34;https://www.prismmodelchecker.org/casestudies/gossip.php&#34;&gt;PRISM model of a gossip protocol&lt;/a&gt;. As nodes discover each other over time, they find shorter paths from node to node:&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/prism-chart.png&#34; style=&#34;max-width: 50%;&#34; /&gt;
&lt;/div&gt;
&lt;p&gt;This kind of analysis and graphing is built into PRISM, it was born for this.&lt;/p&gt;
&lt;p&gt;PRISM can only express models that are simple enough to analyze by solving equations. So the PRISM language has no arrays, no hashtables, no sets. It&amp;rsquo;s just scalar variables with float values, pretty much. That makes PRISM specs kind of awful.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the PRISM gossip protocol. This model has 4 nodes. Each node&amp;rsquo;s view of each other node is a separate variable, so that&amp;rsquo;s 16 variables:&lt;/p&gt;
&lt;pre class=&#34;prism-code&#34;&gt;
&lt;span class=&#34;prismcomment&#34;&gt;// initial view of node 1 (can see 2 one hop away)&lt;/span&gt;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv1_1_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;2&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv1_2_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv1_1_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv1_2_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;;

&lt;span class=&#34;prismcomment&#34;&gt;// initial view of node 2 (empty)&lt;/span&gt;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv2_1_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv2_2_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv2_1_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv2_2_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;;

&lt;span class=&#34;prismcomment&#34;&gt;// initial view of node 3 (can see 2 one hop away)&lt;/span&gt;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv3_1_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;2&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv3_2_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv3_1_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv3_2_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;;

&lt;span class=&#34;prismcomment&#34;&gt;// initial view of node 4 (can see 2 one hop away)&lt;/span&gt;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv4_1_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;2&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv4_2_a&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv4_1_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;;
&lt;span class=&#34;prismkeyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;prismkeyword&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;prismident&#34;&gt;iv4_2_h&lt;/span&gt; = &lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;;
&lt;/pre&gt;
&lt;p&gt;If you had 5 nodes, you&amp;rsquo;d need 25 variables. You can&amp;rsquo;t write any generic code. As the number of nodes grows, the code in this spec grows. This is Node 1&amp;rsquo;s code for talking to Nodes 2, 3, and 4:&lt;/p&gt;
&lt;pre class=&#34;prism-code&#34;&gt;
&lt;span class=&#34;prismcomment&#34;&gt;// send to node 2&lt;/span&gt;
[&lt;span class=&#34;prismident&#34;&gt;push1_2_0&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id2&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt; -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;+&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;);
[&lt;span class=&#34;prismident&#34;&gt;push1_2_1&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id2&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;v1_1_h&lt;/span&gt;&amp;lt;&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt; -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;s1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;send1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;);
[&lt;span class=&#34;prismident&#34;&gt;push1_2_end&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id2&lt;/span&gt; &amp;amp; ((&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;&amp;amp;&lt;span class=&#34;prismident&#34;&gt;v1_1_h&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;) | (&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;2&lt;/span&gt;&amp;amp;&lt;span class=&#34;prismident&#34;&gt;v1_2_h&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;)) -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;s1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;send1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;);
&lt;span class=&#34;prismcomment&#34;&gt;// send to node 3&lt;/span&gt;
[&lt;span class=&#34;prismident&#34;&gt;push1_3_0&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt; -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;+&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;);
[&lt;span class=&#34;prismident&#34;&gt;push1_3_1&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;v1_1_h&lt;/span&gt;&amp;lt;&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt; -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;s1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;send1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;);
[&lt;span class=&#34;prismident&#34;&gt;push1_3_end&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id3&lt;/span&gt; &amp;amp; ((&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;&amp;amp;&lt;span class=&#34;prismident&#34;&gt;v1_1_h&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;) | (&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;2&lt;/span&gt;&amp;amp;&lt;span class=&#34;prismident&#34;&gt;v1_2_h&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;)) -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;s1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;send1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;);
&lt;span class=&#34;prismcomment&#34;&gt;// send to node 4&lt;/span&gt;
[&lt;span class=&#34;prismident&#34;&gt;push1_4_0&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id4&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt; -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;+&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;);
[&lt;span class=&#34;prismident&#34;&gt;push1_4_1&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id4&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;v1_1_h&lt;/span&gt;&amp;lt;&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt; -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;s1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;send1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;);
[&lt;span class=&#34;prismident&#34;&gt;push1_4_end&lt;/span&gt;] &lt;span class=&#34;prismident&#34;&gt;s1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;3&lt;/span&gt; &amp;amp; &lt;span class=&#34;prismident&#34;&gt;send1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;id4&lt;/span&gt; &amp;amp; ((&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;1&lt;/span&gt;&amp;amp;&lt;span class=&#34;prismident&#34;&gt;v1_1_h&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;) | (&lt;span class=&#34;prismident&#34;&gt;i1&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;2&lt;/span&gt;&amp;amp;&lt;span class=&#34;prismident&#34;&gt;v1_2_h&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;4&lt;/span&gt;)) -&amp;gt; (&lt;span class=&#34;prismident&#34;&gt;s1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;i1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;) &amp;amp; (&lt;span class=&#34;prismident&#34;&gt;send1&#39;&lt;/span&gt;=&lt;span class=&#34;prismnum&#34;&gt;0&lt;/span&gt;);
&lt;/pre&gt;
&lt;p&gt;Node 2 borrows Node 1&amp;rsquo;s code by horribly renaming all its functions:&lt;/p&gt;
&lt;pre class=&#34;prism-code&#34;&gt;
&lt;span class=&#34;prismident&#34;&gt;push1_2_0&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_1_0&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_2_1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_1_1&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_2_2&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_1_2&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_2_3&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_1_3&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_2_end&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_1_end&lt;/span&gt;,
&lt;span class=&#34;prismident&#34;&gt;push1_3_0&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_3_0&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_3_1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_3_1&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_3_2&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_3_2&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_3_3&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_3_3&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_3_end&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_3_end&lt;/span&gt;,
&lt;span class=&#34;prismident&#34;&gt;push1_4_0&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_4_0&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_4_1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_4_1&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_4_2&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_4_2&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_4_3&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_4_3&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push1_4_end&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push2_4_end&lt;/span&gt;,
&lt;span class=&#34;prismident&#34;&gt;push2_1_0&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push1_2_0&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push2_1_1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push1_2_1&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push2_1_2&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push1_2_2&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push2_1_3&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push1_2_3&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push2_1_end&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push1_2_end&lt;/span&gt;,
&lt;span class=&#34;prismident&#34;&gt;push3_1_0&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push3_2_0&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push3_1_1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push3_2_1&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push3_1_2&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push3_2_2&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push3_1_3&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push3_2_3&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push3_1_end&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push3_2_end&lt;/span&gt;,
&lt;span class=&#34;prismident&#34;&gt;push4_1_0&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push4_2_0&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push4_1_1&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push4_2_1&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push4_1_2&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push4_2_2&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push4_1_3&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push4_2_3&lt;/span&gt;, &lt;span class=&#34;prismident&#34;&gt;push4_1_end&lt;/span&gt;=&lt;span class=&#34;prismident&#34;&gt;push4_2_end&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I&amp;rsquo;m only showing you a fraction of this code to preserve your sanity. Nodes 2 and 3 have the same kind of thing. This is an abomination. This code is banned by the Geneva Convention. The grad student who wrote this code was found lying on the floor of the computer science lab, drooling.&lt;/p&gt;
&lt;p&gt;Actually it was probably generated by a program in another language. PRISM is like an assembly code for expressing Markov chains. This makes it tractable for PRISM&amp;rsquo;s equation solver to come up with exact solutions, but it&amp;rsquo;s obviously not a good language.&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/stopwatch.png&#34; style=&#34;width: 50%; margin-bottom: 1em&#34;&gt;
&lt;/div&gt;
&lt;h2&gt;Start of the Art #3 of 4: Runway&lt;/h2&gt;

&lt;p&gt;In 2016, Diego Ongaro announced a formal modeling system called Runway (&lt;a href=&#34;https://runway.systems/&#34;&gt;website&lt;/a&gt;, &lt;a href=&#34;https://www.usenix.org/system/files/login/articles/login_fall16_06_ongaro.pdf&#34;&gt;article&lt;/a&gt;, &lt;a href=&#34;https://groups.google.com/g/tlaplus/c/5Xd8kv288jE/m/k4xOYydpAAAJ&#34;&gt;Andrew&amp;rsquo;s summary&lt;/a&gt;.) Ongaro had published Raft a couple years before, then he announced this very promising-looking thing but stopped working on it the same year. This is a snippet from a Runway specification of Raft.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;quorum&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;serverSet&lt;/span&gt;: &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;Set&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;ServerId&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;&amp;gt;[&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ServerId&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;])&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;Boolean&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;size&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;serverSet&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;size&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;servers&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;sendMessage&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;message&lt;/span&gt;: &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;Message&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;message&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;sentAt&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;later&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;message&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;deliverAt&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;later&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;urandomRange&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;10000&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;20000&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#000&#34;&gt;push&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;network&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;message&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Runway&amp;rsquo;s syntax is familiar for programmers. And Runway has randomization built in. If Runway is in model-checking mode, it somehow tries all possibilities; I don&amp;rsquo;t know how. If Runway is in simulation mode, randomization works the way you&amp;rsquo;d expect, and Runway can gather statistics.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an elevator simulation written in Runway:&lt;/p&gt;
&lt;video controls style=&#34;width: 100%; margin-bottom: 1em&#34;&gt;
  &lt;source src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/elevator.mp4&#34; type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;
&lt;p&gt;The visualization on the right needs lots of custom code, but the graph on the left is I think easy to create with Runway&amp;rsquo;s built-in tools. If we want to develop TLA&lt;sup&gt;+&lt;/sup&gt; tools in this direction, we should make this kind of graph convenient for spec authors to generate.&lt;/p&gt;
&lt;h2&gt;State of the Art #4 of 4: FizzBee&lt;/h2&gt;

&lt;div style=&#34;text-align: center&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/fizzbee-logo.png&#34; style=&#34;width: 40%; margin-bottom: 1em&#34;&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://fizzbee.io/&#34;&gt;FizzBee&lt;/a&gt; is a new modeling language developed by a solo author, Jayaprabhakar &amp;ldquo;JP&amp;rdquo; Kadarkarai. Here&amp;rsquo;s an example FizzBee spec &lt;a href=&#34;https://fizzbee.io/design/tutorials/performance-modeling/&#34;&gt;from the FizzBee docs&lt;/a&gt;:&lt;/p&gt;
&lt;table style=&#34;width: 100%; border-spacing: 0 1em;&#34;&gt;
  &lt;tr style=&#34;text-align: center; font-weight: bold; font-family: monospace; text-decoration: underline&#34;&gt;
    &lt;td&gt;cache.fizz&lt;/td&gt;
    &lt;td&gt;perf_model.yaml&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&#34;vertical-align: top;&#34;&gt;
    &lt;td &gt;&lt;pre style=&#34;margin-top: 1em&#34;&gt;atomic action Lookup:
  cached = LookupCache()
  if cached == &#34;hit&#34;:
      return cached
  found = LookupDB()
  return found
 
func LookupCache():
  oneof:
    `hit` return &#34;hit&#34;
    `miss` return &#34;miss&#34;&lt;/pre&gt;&lt;/td&gt;
    &lt;td&gt;&lt;pre style=&#34;margin-top: 1em&#34;&gt;configs:
  LookupCache.call:
    counters:
      latency_ms:
        numeric: 10
  LookupCache.hit:
    probability: 0.2
  LookupCache.miss:
    probability: 0.8&lt;/pre&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;The spec describes a service that tries to look up data in a cache, then falls back to a database query. The algorithm is in cache.fizz on the left; Lookup and LookupCache are defined there. (I omitted LookupDB.) On the right is a config file that&amp;rsquo;s &lt;em&gt;only&lt;/em&gt; for probabilistic modeling! It defines the cost of a call to the LookupCache function: 10 milliseconds of latency. PRISM avoids the valenced &amp;ldquo;cost&amp;rdquo; / &amp;ldquo;reward&amp;rdquo; terms and calls this value a &amp;ldquo;counter&amp;rdquo;, but it can be a float. Counters and probabilities are kept in this separate config file. They don&amp;rsquo;t clutter the spec, and they&amp;rsquo;re not needed for ordinary model-checking (i.e. checking safety properties).&lt;/p&gt;
&lt;p&gt;The LookupCache function is nondeterministic, it either finds the data or doesn&amp;rsquo;t. The transitions are labeled as `hit` or `miss` in the spec, and their probabilities are defined as 0.2 and 0.8 in the config file. Again, the algorithm is kept separate from the probabilistic config.&lt;/p&gt;
&lt;p&gt;FizzBee&amp;rsquo;s probabilistic modeler, a.k.a. the performance model checker, is a separate program from the safety model-checker. The former produces the state graph and saves it to disk; the latter reads the state graph and produces statistics. If you run the performance modeler on the files above, it prints:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Metrics(mean={&amp;#39;latency_ms&amp;#39;: 84.4}, histogram=[(0.2, {&amp;#39;latency_ms&amp;#39;: 10.0}), (1.0000000000000002, {&amp;#39;latency_ms&amp;#39;: 103.0})])
   2: 0.20000000 state: {} / returns: {&amp;#34;Lookup&amp;#34;:&amp;#34;\&amp;#34;hit\&amp;#34;&amp;#34;}
   4: 0.72000000 state: {} / returns: {&amp;#34;Lookup&amp;#34;:&amp;#34;\&amp;#34;found\&amp;#34;&amp;#34;}
   5: 0.08000000 state: {} / returns: {&amp;#34;Lookup&amp;#34;:&amp;#34;\&amp;#34;notfound\&amp;#34;&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The mean latency to look up one value is 84.4, and the return value of Lookup is &amp;ldquo;hit&amp;rdquo; 20% of the time, &amp;ldquo;found&amp;rdquo; 72% of the time, and &amp;ldquo;notfound&amp;rdquo; 8% of the time, as we&amp;rsquo;d expect from the defined probabilities.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the config file again, but with a probability &lt;em&gt;distribution&lt;/em&gt; for the cost function.&lt;/p&gt;
&lt;p&gt;&lt;span style=&#34;text-align: center; font-weight: bold; font-family: monospace; text-decoration: underline; font-size: smaller&#34;&gt;perf_model.yaml&lt;/span&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;configs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  LookupCache.call:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    counters:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      latency_ms:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;        distribution: lognorm(s=0.3, loc=2)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;FizzBee lets you choose any probability distribution supported by SciPy, &lt;a href=&#34;https://docs.scipy.org/doc/scipy/tutorial/stats/probability_distributions.html&#34;&gt;which is basically all of them&lt;/a&gt;. Or you can bring your own histogram of costs: if you have experimental data, you can use that, the same as with the Java Modelling Tools. Marc Brooker said he wants a tool that uses &amp;ldquo;real-world data on network performance, packet loss, and user workloads,&amp;rdquo; and FizzBee allows this.&lt;/p&gt;
&lt;p&gt;I think FizzBee&amp;rsquo;s design is smart. FizzBee has built in some important ideas: cost functions, probability distributions, and probabilities of state transitions. We don&amp;rsquo;t have to write a bunch of error-prone code for each spec, the batteries are included. What&amp;rsquo;s really nice is that the performance stuff is all in a separate file, so it doesn&amp;rsquo;t clutter the main spec. In TLA&lt;sup&gt;+&lt;/sup&gt; &lt;a href=&#34;https://lamport.azurewebsites.net/pubs/yuanyu-model-checking.pdf&#34;&gt;we try to keep model-checking details out of the main spec&lt;/a&gt;; FizzBee lets you do the same with performance modeling.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know how much of this is implemented. FizzBee is new and JP has been developing it solo. But that&amp;rsquo;s not important because we&amp;rsquo;re just gathering design ideas for what TLA&lt;sup&gt;+&lt;/sup&gt; might do.&lt;/p&gt;
&lt;h1&gt;A Menu for TLA&lt;sup&gt;+&lt;/sup&gt;&lt;/h1&gt;

&lt;p&gt;I&amp;rsquo;ve grouped the features we&amp;rsquo;ve seen so far into three categories:&lt;/p&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/venn.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/venn.png&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;Some of the state-of-the-art tools can annotate state transitions with probabilities. Some support cost / reward functions, which FizzBee calls &amp;ldquo;counters&amp;rdquo;. PRISM has powerful statistical property expressions.
In Runway and FizzBee, model-checking is compatible with performance modeling (and now TLA&lt;sup&gt;+&lt;/sup&gt; too), and FizzBee nicely separates performance modeling config. Some include convenient charting. They all support floating-point numbers. Some of them ship with common probability distributions for rates and cost functions. Some can use experimental data as a probability distribution (&amp;ldquo;bring your own histogram&amp;rdquo;). Some of them come with solvers that calculate answers precisely, but this limits the models to those that can be solved in closed form. Others use statistical sampling, which is imprecise but much more powerful.&lt;/p&gt;
&lt;h1&gt;Possible Syntax??&lt;/h1&gt;

&lt;p&gt;Here&amp;rsquo;s the moment I dread, when I suggest a syntax. Everywhere a spec has nondeterminism for model-checking, we need to replace that with some sort of probability distribution. E.g., this formula picks an element nondeterministically from a set:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\* MySpec.tla
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SendMessage(m) ==
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \E messageIsDropped \in {FALSE, TRUE}:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want a distribution &lt;em&gt;besides&lt;/em&gt; uniform, you could wrap the set (or any collection) in an operator like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\* MySpec.tla
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SendMessage(m) ==
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \E messageIsDropped \in MessageLossProbability(FALSE, TRUE):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The operator does nothing in model-checking mode, but it&amp;rsquo;s like a label for a probability distribution.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\* MySpec.cfg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DISTRIBUTION
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  MessageLossProbability = BooleanChoice(0.23)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the config file, labeling this operator as a DISTRIBUTION means the model-checker treats it as either true or false, and it explores both possibilities nondeterministically. The performance-modeler knows that this is a particular probability, perhaps BooleanChoice is the name of a built-in utility to return its first argument with probability 0.23, otherwise its second argument. Like FizzBee, we use labels to tie the spec to the configuration.&lt;/p&gt;
&lt;p&gt;We also need cost functions in TLA&lt;sup&gt;+&lt;/sup&gt;. We could set the cost of each &lt;em&gt;action&lt;/em&gt; to be a constant or a probability distribution. The distribution could come from real-world data, and it could be parameterized if the action is parameterized.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;\* MySpec.cfg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;COST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  SendMessage = Exponential(3.17)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the part of my talk I&amp;rsquo;m least sure about. If you hate this syntax, that&amp;rsquo;s fine, don&amp;rsquo;t get angry at the whole idea because you hate my syntax.&lt;/p&gt;
&lt;h1&gt;TLA&lt;sup&gt;+&lt;/sup&gt; with Probabilistic Solvers&lt;/h1&gt;

&lt;p&gt;If we implement any of this in TLA&lt;sup&gt;+&lt;/sup&gt;, we&amp;rsquo;ll need some way to actually &lt;em&gt;answer&lt;/em&gt; these probabilistic questions. In order of ambitiousness:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Just use &lt;a href=&#34;https://groups.google.com/g/tlaplus/c/2lpTFq6uFsE/m/Dy5Ezw29AgAJ&#34;&gt;TLC&amp;rsquo;s -generate option&lt;/a&gt;, generate thousands of behaviors, average the stats.&lt;/li&gt;
&lt;li&gt;Use -generate, run until stats stabilize within some precision. Perhaps prune branches of the state graph as they stabilize. JMT does similar smart exploration of simulations.&lt;/li&gt;
&lt;li&gt;Use PRISM&amp;rsquo;s solvers, by translating the state graph to PRISM code, or otherwise borrowing PRISM&amp;rsquo;s implementation.&lt;/li&gt;
&lt;li&gt;Write a solver or solvers from scratch: translate the state graph to a Markov chain and find its steady-state probability distribution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The latter two options will find closed-form solutions with precise answers, but severely limit our options for what models we can use for probabilistic questions. I think we should just use simulation, period.&lt;/p&gt;
&lt;h1&gt;TLA&lt;sup&gt;+&lt;/sup&gt; with Performance Modeling&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/flying.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;If we actually had TLA&lt;sup&gt;+&lt;/sup&gt; with performance modeling, it could be really cool! One model could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Express the algorithm.&lt;/li&gt;
&lt;li&gt;Check correctness.&lt;/li&gt;
&lt;li&gt;Evaluate performance.&lt;/li&gt;
&lt;li&gt;Simulate &amp;ldquo;what-if&amp;rdquo; experiments using real-world inputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We could confidently explore optimizations: we&amp;rsquo;d know &lt;em&gt;both&lt;/em&gt; whether a change to an algorithm is safe, &lt;em&gt;and&lt;/em&gt; if it wins us anything.&lt;/p&gt;
&lt;h1&gt;Questions&lt;/h1&gt;

&lt;p&gt;I didn&amp;rsquo;t take questions during my talk, because I didn&amp;rsquo;t have any answers, so I asked the audience questions instead.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What syntax should  TLA&lt;sup&gt;+&lt;/sup&gt; use for annotating state transitions with probabilities?&lt;/li&gt;
&lt;li&gt;What syntax for cost functions?&lt;/li&gt;
&lt;li&gt;How do we separate performance-modeling config from the spec and model-checking config?&lt;/li&gt;
&lt;li&gt;Should TLC do the probabilistic checking, or another tool?&lt;/li&gt;
&lt;li&gt;Could the  TLA&lt;sup&gt;+&lt;/sup&gt; Foundation get new funding for this work?&lt;/li&gt;
&lt;li&gt;Is any of this a good idea or should  TLA&lt;sup&gt;+&lt;/sup&gt; stick to correctness?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Further Reading&lt;/h1&gt;

&lt;p&gt;Andrew Helwer, &lt;a href=&#34;https://ahelwer.ca/post/2020-09-11-probabilistic-distsys/&#34;&gt;How do you reason about a probabilistic distributed system?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Joost-Pieter Katoen &lt;a href=&#34;https://i-cav.org/2015/wp-content/uploads/2015/07/mod12_katoen.pdf&#34;&gt;Model Checking Meets Probability: A Gentle Introduction&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Acks: Andrew Helwer, Jayaprabhakar Kadarkarai, Murat Demirbas, and Will Schultz generously helped me.
The good ideas are theirs and the bad ideas are mine.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Jesse&#39;s 2025 TLA&#43; Community Event Notes</title>
      <link>https://emptysqua.re/blog/2025-tlaplus-community-event/</link>
      <pubDate>Thu, 08 May 2025 21:47:33 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/2025-tlaplus-community-event/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-1.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Here are my notes from the 2025 TLA&lt;sup&gt;+&lt;/sup&gt; Community Event. See also &lt;a href=&#34;https://muratbuffalo.blogspot.com/2025/05/notes-from-tla-community-event.html&#34;&gt;my colleague Murat Demirbas&amp;rsquo;s notes&lt;/a&gt; (he was one of the event&amp;rsquo;s organizers). The talks were all recorded, and the videos, slides, and abstracts &lt;a href=&#34;https://conf.tlapl.us/2025-etaps/&#34;&gt;are posted&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some years &lt;a href=&#34;https://conf.tlapl.us/home/&#34;&gt;there are TLA&lt;sup&gt;+&lt;/sup&gt; &amp;ldquo;conferences&amp;rdquo; co-located with &lt;em&gt;industry&lt;/em&gt; conferences&lt;/a&gt;, and some years there are &amp;ldquo;community events&amp;rdquo; co-located with &lt;em&gt;academic&lt;/em&gt; conferences. This year was a community event, the first TLA&lt;sup&gt;+&lt;/sup&gt; community event in North America. There were about 20 participants, evenly divided between academia and industry. I knew most of them already. If I recall correctly, the last TLA&lt;sup&gt;+&lt;/sup&gt; thing I attended was the 2021 &lt;em&gt;conference&lt;/em&gt;; it had more participants, perhaps because it was co-located with the huge StrangeLoop industry conference.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-2.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;ModelFuzz: Model guided fuzzing of distributed systems&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://www.srinidhin.com/&#34;&gt;Srinidhi Nagendra&lt;/a&gt;, Max Planck Institute for Software Systems.&lt;/p&gt;
&lt;p&gt;Srini summarized his paper, &lt;a href=&#34;http://arxiv.org/abs/2410.02307&#34;&gt;Model-guided Fuzzing of Distributed Systems&lt;/a&gt;. Srini&amp;rsquo;s ModelFuzz is coverage-guided fuzzing of the implementation, where &amp;ldquo;coverage&amp;rdquo; is defined by the model. ModelFuzz executes an implementation test (which executes a workload and occasionally crashes or restarts a server), captures an execution trace, maps the trace back to a sequence of TLA&lt;sup&gt;+&lt;/sup&gt; model states, and guides the next round of tests to maximize coverage of the TLA&lt;sup&gt;+&lt;/sup&gt; model states. For distributed systems, maximizing this sort of coverage is a more efficient way to discover bugs than e.g. &lt;a href=&#34;https://afl-1.readthedocs.io/en/latest/about_afl.html#coverage-measurements&#34;&gt;branch coverage like AFL uses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Srini&amp;rsquo;s example is a Raft spec and several Raft implementations. He records all messages among Raft nodes, as well as events like node crashes, and replays this execution on the model. The spec had to be slightly restructured to make it easier to map implementation actions to model actions. He also needed to &amp;ldquo;abstract states&amp;rdquo; (something like a symmetry set in TLA&lt;sup&gt;+&lt;/sup&gt; config) so that states which differ in uninteresting ways are not considered distinct states that improve coverage. E.g., if you crash the leader 1000 times, you&amp;rsquo;ll get 1000 terms without truly exploring new states. Choosing the right model abstraction is an art. I don&amp;rsquo;t understand why he reinvented this idea instead of just using TLA&lt;sup&gt;+&lt;/sup&gt;&amp;rsquo;s existing symmetry sets or views.&lt;/p&gt;
&lt;p&gt;ModelFuzz generates implementation tests in a really surprising way. Srini created a central proxy through which all Raft messages pass. ModelFuzz counts the number of messages that are delivered to each server, and then &amp;ldquo;mutates&amp;rdquo; a test by buffering messages in the proxy and changing the order in which the proxy delivers messages, as well as perturbing the schedule of server crashes. There is a lot of nondeterminism in the system already and ModelFuzz is jiggling it even more. Srini shows that mutating tests strategically will moderately improve model coverage, compared to just rerunning the test many times and relying on inherent nondeterminism to improve model coverage, and compared to purely random mutation of tests. The charts he showed us seemed to show a rather small improvement, but he found a lot of new bugs!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s interesting that Srini was able to explore more of the state space with an intervention that&amp;rsquo;s reasonably easy to implement. But I think &lt;a href=&#34;https://antithesis.com/&#34;&gt;Antithesis&lt;/a&gt; is better. Model-guided fuzzing is still useful, but I predict that Antithesis or other deterministic hypervisors will soon be the overwhelmingly dominant means of actually doing the fuzzing.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-3.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Automating Trace Validation with PGo&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://fhackett.com/&#34;&gt;Finn Hackett&lt;/a&gt; (presenting) and &lt;a href=&#34;https://www.cs.ubc.ca/~bestchai/&#34;&gt;Ivan Beschastnikh&lt;/a&gt;, University of British Columbia.&lt;/p&gt;
&lt;p&gt;Finn is my &lt;a href=&#34;https://www.mongodb.com/academia/phd-fellowship&#34;&gt;PhD fellow&lt;/a&gt; and will be my PhD intern starting in June. &lt;a href=&#34;https://www.cs.ubc.ca/~bestchai/papers/asplos23-pgo.pdf&#34;&gt;His PGo project&lt;/a&gt; translates a variant of PlusCal into Go. He wants to use trace-validation to check that the Go really matches the PlusCal; his trace-validator is called TraceLink. It uses log statements that are autogenerated along with the rest of the Go code, and it introspects the PlusCal to see what variables must be matched per trace line, and generates some TLA&lt;sup&gt;+&lt;/sup&gt; to validate a trace against the implementation. If validation fails, TraceLink provides some debug info, as TLA&lt;sup&gt;+&lt;/sup&gt; &lt;a href=&#34;https://learntla.com/topics/cli.html&#34;&gt;aliases&lt;/a&gt; that appear in TLC&amp;rsquo;s counterexample trace. This includes a list of enabled next-actions at the moment the trace diverged from the spec.&lt;/p&gt;
&lt;p&gt;For each PlusCal label block (equivalent to a TLA&lt;sup&gt;+&lt;/sup&gt; action), PGo outputs critical section of Go code, which ends in commit/abort. If it commits, it updates the program&amp;rsquo;s state and logs the state transition for trace-checking&amp;rsquo;s sake. If it aborts it changes no state, but it still logs the state it &lt;em&gt;observed&lt;/em&gt;, so even aborted critical sections can be trace-checked.&lt;/p&gt;
&lt;p&gt;Finn&amp;rsquo;s system uses vector clocks to order log messages. This means there are many possible total orderings, and &lt;em&gt;all&lt;/em&gt; of them should be valid behaviors according to the spec! (I keep forgetting and misunderstanding this.) A PGo-generated system should log all modeled variables on each state transition, and always increment the vector clock during every interaction between processes, so every one of the total orders is valid according to the spec. That gives TraceLink some options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;check every possible log ordering against the spec&lt;/li&gt;
&lt;li&gt;check one arbitrarily-chosen ordering&lt;/li&gt;
&lt;li&gt;check a set of orderings that satisfies some coverage metric&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finn has chosen a hybrid: TraceLink arbitrarily chooses one ordering of the log and checks it against the spec, but for each log message it asserts that each &lt;em&gt;possible&lt;/em&gt; next state is valid according to the spec, before it advances to a &lt;em&gt;single&lt;/em&gt; next state and repeats. He says this gives him a nice compromise between performance and completeness.&lt;/p&gt;
&lt;p&gt;TraceLink generates from the log a TLA&lt;sup&gt;+&lt;/sup&gt; spec, which validates that the trace conforms with the &lt;em&gt;original&lt;/em&gt; PlusCal spec of the program. This trace-checking spec could be huge, but TraceLink compacts it by factoring out common &amp;ldquo;shapes&amp;rdquo; of validation and storing the values in a binary file that TLC can read.&lt;/p&gt;
&lt;p&gt;Finn gets diverse traces from the implementation by adding an exponentially-distributed random sleep after each critical section.&lt;/p&gt;
&lt;p&gt;This work is specific to implementations generated by PGo, could be adapted with some manual effort (add PGo-style instrumentation to existing implementation) or by building a PlusCal-to-another-language compiler that automatically adds this instrumentation.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-4.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Translating C to PlusCal for Model Checking of Safety Properties on Source Code&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://www.linkedin.com/in/guillaume-di-fatta-0441a3253/&#34;&gt;Guillaume Di Fatta&lt;/a&gt; (presenting), &lt;a href=&#34;https://scholar.google.fr/citations?user=nYVf510AAAAJ&amp;amp;hl=fr&#34;&gt;Emmanuel Ohayon&lt;/a&gt;, and &lt;a href=&#34;https://dblp.org/pid/167/5040.html&#34;&gt;Amira Methni&lt;/a&gt;, Asterios Technologies.&lt;/p&gt;
&lt;p&gt;Asterios is a defense contractor, they make a realtime microkernel for embedded systems. Guillaume and his colleagues wrote a tool that auto-translates the microkernel scheduler from C to PlusCal. The C is simple and self-contained, which helps. The translation progresses through a series of intermediate representations. He wrote a tiny stack-based VM in PlusCal to interpret the final IR. This is cool but outside my interests. An intriguing suggestion for future work: use model-checking to show the auto-translated PlusCal refines a more abstract spec.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-5.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;TLA&lt;sup&gt;+&lt;/sup&gt; for All: Model Checking in a Python Notebook&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://laufer.cs.luc.edu/&#34;&gt;Konstantin Läufer&lt;/a&gt; (presenting) and &lt;a href=&#34;https://gkt.sh/&#34;&gt;George K. Thiruvathukal&lt;/a&gt;, Loyola University Chicago.&lt;/p&gt;
&lt;p&gt;Konstantin talked about teaching formal methods to undergrads at his school and &lt;a href=&#34;https://fme-teaching.github.io/&#34;&gt;globally&lt;/a&gt;. He described where FM fits in a curriculum for CS students, particularly among other classes about software testing, which increase in power as they work through the curriculum. (He mentions the useful &lt;a href=&#34;https://www.agilealliance.org/resources/sessions/test-driven-development-guided-by-zombies/&#34;&gt;ZOMBIES mnemonic for unittesting&lt;/a&gt;!) He wants to integrate FM into existing classes, e.g. a data structures class could use Alloy or LEAN. Finally, he shows that he&amp;rsquo;s created a browser-based zero-install environment for running TLC.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-6.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Formal models for monotonic pipeline architectures&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://dblp.org/pid/97/1837.html&#34;&gt;J.-P. Bodeveix&lt;/a&gt;, &lt;a href=&#34;https://scholar.google.fr/citations?user=8k2MVLYAAAAJ&amp;amp;hl=fr&#34;&gt;A. Bonenfant&lt;/a&gt;, &lt;a href=&#34;https://scholar.google.fr/citations?user=3iKALIoAAAAJ&amp;amp;hl=sr&#34;&gt;T. Carle&lt;/a&gt;, &lt;a href=&#34;https://dblp.org/pid/60/599.html&#34;&gt;Mamoun Filali&lt;/a&gt; (presenting), &lt;a href=&#34;https://www.irit.fr/~Christine.Rochange/&#34;&gt;C. Rochange&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They want to analyze the Worst-Case Execution Time (WCET) for pipelined hardware architectures. They&amp;rsquo;re concerned about &amp;ldquo;timing anomalies&amp;rdquo; e.g. where a cache hit makes execution &lt;em&gt;slower&lt;/em&gt;. Mamoun didn&amp;rsquo;t discuss performance analysis in TLA&lt;sup&gt;+&lt;/sup&gt;, though, he instead walked through a TLA&lt;sup&gt;+&lt;/sup&gt; spec and safety properties in detail.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-7.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;TLA&lt;sup&gt;+&lt;/sup&gt; Modeling of MongoDB Transactions&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://www.linkedin.com/in/murat-demirbas-distributolog-a2233b176/&#34;&gt;Murat Demirbas&lt;/a&gt; (presenting) and &lt;a href=&#34;https://www.linkedin.com/in/william-schultz-a22714a2/&#34;&gt;Will Schultz&lt;/a&gt; MongoDB&lt;/p&gt;
&lt;p&gt;MongoDB introduced single-document transactions in version 3.2 (2015), multi-document transactions in version 4.0, (2018), and sharded transactions in version 4.2 (2019). Murat described the layers of the transaction algorithm: storage, replica set, sharded cluster. The algorithm is hard to reason about and verify: it was developed incrementally, its use of &lt;a href=&#34;https://muratbuffalo.blogspot.com/2024/04/implementation-of-cluster-wide-logical.html&#34;&gt;ClusterTime&lt;/a&gt; and &lt;a href=&#34;https://muratbuffalo.blogspot.com/2024/02/tunable-consistency-in-mongodb.html#:~:text=Speculative%20majority%20and%20snapshot%20isolation,back%2Dto%2Dback%20transactions.&#34;&gt;speculative majority&lt;/a&gt; adds complexity, and some of the hard bugs are in fault-tolerance and the interactions of transactions with DDL and chunk migration between shards. For now, Murat&amp;rsquo;s and Will&amp;rsquo;s TLA&lt;sup&gt;+&lt;/sup&gt; spec disables chunk migration and ignores DDL.&lt;/p&gt;
&lt;p&gt;To check isolation, our spec maintains a history variable &amp;ldquo;ops&amp;rdquo; where it stores the history of transactions. We use &lt;a href=&#34;https://ir.cwi.nl/pub/30580/30580.pdf&#34;&gt;Crooks&amp;rsquo;s client-centric isolation definitions, modeled in TLA&lt;sup&gt;+&lt;/sup&gt; by Soethout&lt;/a&gt;. The interactions between transactions and timestamps are key to isolation guarantees.&lt;/p&gt;
&lt;p&gt;Interesting audience question: Crooks&amp;rsquo;s formalism doesn&amp;rsquo;t catch phantoms due to predicate reads, how do we ensure we&amp;rsquo;re preventing phantoms? Murat: that&amp;rsquo;s only a problem with serializable isolation, but the strongest we provide is snapshot isolation.&lt;/p&gt;
&lt;p&gt;We use model-based test case generation to check conformance of the &lt;a href=&#34;https://github.com/muratdem/MDBTLA/blob/main/MultiShardTxn/Storage.tla&#34;&gt;storage layer spec&lt;/a&gt; and implementation. Since the spec is modular, the storage spec is separate from the other aspects of the transactions spec, which helps.&lt;/p&gt;
&lt;p&gt;Murat and Will analyzed the &lt;em&gt;permissiveness&lt;/em&gt; of our transaction protocols compared to the behaviors allowed by Crooks&amp;rsquo;s formalism. We allow 70-80% of the behaviors that are theoretically allowed for snapshot isolation and read-committed—this suggests if we were more permissive we could allow more parallelism. I have a draft of the paper that I haven&amp;rsquo;t read yet, now I&amp;rsquo;m really curious how they measured this.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-8.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Are We Serious About Using TLA&lt;sup&gt;+&lt;/sup&gt; For Statistical Properties?&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://emptysqua.re/blog/&#34;&gt;A. Jesse Jiryu Davis&lt;/a&gt; MongoDB&lt;/p&gt;
&lt;p&gt;This was my presentation. &lt;a href=&#34;https://emptysqua.re/blog/are-we-serious-about-statistical-properties-tlaplus/&#34;&gt;Here&amp;rsquo;s the video and a detailed writeup&lt;/a&gt;. In summary: &lt;a href=&#34;https://brooker.co.za/blog/2022/06/02/formal.html&#34;&gt;Marc Brooker in 2022&lt;/a&gt; asked for tools that model performance &lt;strong&gt;and&lt;/strong&gt; check safety, with a single spec. Markus Kuppe and Jack Vanlightly that year presented a way to do it in TLA&lt;sup&gt;+&lt;/sup&gt;, but I think it&amp;rsquo;s an impractical prototype because it&amp;rsquo;s missing essential features. I asked, are we serious about making TLA&lt;sup&gt;+&lt;/sup&gt; do performance modeling? I reviewed some existing tools for ideas to inspire us: &lt;a href=&#34;https://emptysqua.re/blog/java-modelling-tools/&#34;&gt;Java Modelling Tools&lt;/a&gt;, &lt;a href=&#34;https://www.prismmodelchecker.org/&#34;&gt;PRISM&lt;/a&gt;, &lt;a href=&#34;https://fizzbee.io/&#34;&gt;Fizzbee&lt;/a&gt;, &lt;a href=&#34;https://runway.systems/&#34;&gt;Runway&lt;/a&gt;. I proposed how we might do performance modeling in TLA&lt;sup&gt;+&lt;/sup&gt;, and asked the audience whether we should proceed and how.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-9.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;It’s never been easier to write TLA⁺ tooling!&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://ahelwer.ca/&#34;&gt;Andrew Helwer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Andrew described the existing infrastructure on which you can build your own TLA&lt;sup&gt;+&lt;/sup&gt; tools. There are a bunch of TLA&lt;sup&gt;+&lt;/sup&gt; parsers, SANY is the only full-featured one. Andrew&amp;rsquo;s Tree-Sitter grammar is good for prototyping new TLA&lt;sup&gt;+&lt;/sup&gt; notation because it tells you how much ambiguity you&amp;rsquo;d introduce. For model-checking there&amp;rsquo;s TLC, Apalache, and now &lt;a href=&#34;https://github.com/will62794/spectacle&#34;&gt;Will Schultz&amp;rsquo;s Spectacle&lt;/a&gt; can do some model-checking. There&amp;rsquo;s Snowcat (a type-checker), two auto-formatters (tlaplus-formatter and tlafmt), LSP (language server protocol), Andrew&amp;rsquo;s TLAUC (Unicode converter), and the TLA&lt;sup&gt;+&lt;/sup&gt; VS Code extension.&lt;/p&gt;
&lt;p&gt;The main tools have a lot of legacy code, defined as &amp;ldquo;code without tests&amp;rdquo; or, according to Andrew, &amp;ldquo;code that no one understands.&amp;rdquo; SANY &amp;amp; TLC were started 25 years ago, overhauled 10 years ago for version 2, those authors are gone now. Markus knows more than anyone, but there are large sections of the code he doesn&amp;rsquo;t know / no one knows. The model-checking code is straightforward, but TLAPM (the proof system) is esoteric. TLAPM was started with a grant at INRIA but is not vigorously developed now. Apalache was spun off by Informal Systems but it hasn&amp;rsquo;t been adopted by the TLA&lt;sup&gt;+&lt;/sup&gt; Foundation; it&amp;rsquo;s quiescent.&lt;/p&gt;
&lt;p&gt;Andrew wants a world where we&amp;rsquo;re unafraid to make large changes to core TLA&lt;sup&gt;+&lt;/sup&gt; tools. He&amp;rsquo;s written a lot of parser tests with his TLA&lt;sup&gt;+&lt;/sup&gt; Foundation funding, but we need more tests. We should onboard new core contributors: Andrew is writing &lt;a href=&#34;https://docs.tlapl.us/codebase:start&#34;&gt;onboarding docs&lt;/a&gt; and tutorials for contributors. He&amp;rsquo;s also written &lt;a href=&#34;https://docs.tlapl.us/creating:start&#34;&gt;how to create a new TLA&lt;sup&gt;+&lt;/sup&gt; tool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;He presented some possible future directions, like simplifying and modernizing TLA&lt;sup&gt;+&lt;/sup&gt; syntax, and improving model-checking performance 1000x.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-10.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Announcements&lt;/h1&gt;

&lt;p&gt;Markus says &lt;a href=&#34;https://github.com/tlaplus/tlaplus/issues&#34;&gt;the TLA&lt;sup&gt;+&lt;/sup&gt; issue tracker in GitHub&lt;/a&gt; now labels some issues as TLA&lt;sup&gt;+&lt;/sup&gt; Foundation funding candidates. He describes the grant proposal process.&lt;/p&gt;
&lt;p&gt;Markus demonstrated, with a sped-up video, that GitHub Copilot can solve the Diehard problem with TLA&lt;sup&gt;+&lt;/sup&gt; in 4 minutes of reasoning, with some human intervention. We should explore TLA&lt;sup&gt;+&lt;/sup&gt; plus LLMs more. The Foundation and NVidia came up with &lt;a href=&#34;https://foundation.tlapl.us/challenge/index.html&#34;&gt;the &amp;ldquo;TLAI&amp;rdquo; challenge&lt;/a&gt;, for novel combos of TLA&lt;sup&gt;+&lt;/sup&gt; and AI.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll have a TLA&lt;sup&gt;+&lt;/sup&gt; conference / community event next year, but we don&amp;rsquo;t yet know where or when.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-11.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;h1&gt;Extracurriculars&lt;/h1&gt;

&lt;p&gt;Andrew Helwer and I visited Niagara Falls the day after the conference. He&amp;rsquo;d never seen them. I saw the falls when I was a kid and hardly remember, aside from a memory of a helicopter ride I took over the falls with my mother. She&amp;rsquo;d said, &amp;ldquo;This is so expensive, promise you won&amp;rsquo;t blink for the whole ride.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I give the falls 3 out of 5 stars: lots of water, but not very tall, and you can&amp;rsquo;t see much because of all the mist.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/niagara.png&#34; style=&#34;display:inline;width:auto;height:auto;vertical-align:middle&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;a href=&#34;https://www.instagram.com/p/Ck_WGIJL4Jc/&#34;&gt;From the delightful &amp;ldquo;Subpar Parks&amp;rdquo;.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We had a great time in the vast and rusty old powerplant, a total steampunk playground, with a half-mile tunnel where you walk deep underground from the subterranean turbines to the base of the falls. There&amp;rsquo;s a hagiographic exhibit about Nikola Tesla, the wronged genius who single-handedly invented electricity, radios, and VTOL aircraft? The show turned out to be a propaganda operation by the Serbian government—Tesla was born in Serbia.&lt;/p&gt;
&lt;p&gt;Andrew and I hiked around the Niagara Glen Nature Reserve, where we saw &lt;a href=&#34;https://www.instagram.com/p/DJUHXxyRLtM/&#34;&gt;some lovely mossy boulders&lt;/a&gt;, and &lt;a href=&#34;https://www.instagram.com/p/DJSri9CunOe/&#34;&gt;Canada goose families with floppy goslings&lt;/a&gt;.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/2025-tlaplus-community-event/Futuristic-visions-cards-germany-12.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;The futuristic images are from &lt;a href=&#34;https://rarehistoricalphotos.com/futuristic-visions-cards-germany/&#34;&gt;trading cards published by the Echte Wagner margarine company&lt;/a&gt; in early 20th Century Germany.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Holy Week at Christ Episcopal Church</title>
      <link>https://emptysqua.re/blog/christ-episcopal-holy-week/</link>
      <pubDate>Sun, 27 Apr 2025 08:04:34 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/christ-episcopal-holy-week/</guid>
      
      
      
      
      <description>&lt;p&gt;Holy Week services (Maundy Thursday, Good Friday) at &lt;a href=&#34;https://www.christchurchpok.org/history-christ-church/&#34;&gt;Christ Episcopal Church&lt;/a&gt; in Poughkeepsie. The church has operated since the 1760s; the current building was erected in 1889. My friend Emily is the church&amp;rsquo;s assistant rector, she was &lt;a href=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/&#34;&gt;ordained there&lt;/a&gt; last year. She invited me to photograph the services this month.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-011.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-012.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-013.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-014.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-015.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-016.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-017.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-018.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-019.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-020.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-021.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-022.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-023.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-024.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-025.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-026.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-027.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-028.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-holy-week/christ-church-029.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;Shot with a Nikon F3 and a Nikkor 50mm f/1.2 on Portra 800 35mm film.&lt;/p&gt;
&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mohonk Preserve Rangers, part 2</title>
      <link>https://emptysqua.re/blog/mohonk-rangers-2/</link>
      <pubDate>Fri, 11 Apr 2025 19:36:29 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/mohonk-rangers-2/</guid>
      
      
      
      
      <description>&lt;p&gt;April 9, 2025. Mohonk Preserve rangers training for rescue operations. &lt;a href=&#34;https://emptysqua.re/blog/mohonk-rangers/&#34;&gt;I spent a day with them earlier, in March&lt;/a&gt;.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers-2/rangers-2-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How Long Must I Test?</title>
      <link>https://emptysqua.re/blog/how-long-must-i-test/</link>
      <pubDate>Mon, 07 Apr 2025 22:36:05 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/how-long-must-i-test/</guid>
      
      
      
      
      <description>&lt;p&gt;When you&amp;rsquo;re hunting for bugs with a nondeterministic test, how many times should you run the test? The &lt;em&gt;program&lt;/em&gt; might be deterministic: the same input produces the same behavior. Presuming you&amp;rsquo;re confident this is the case, you could fuzz the input for a while, hoping to catch bugs. But if your program is nondeterministic, you must not only give it random inputs, you must re-test it on the same input many times, hoping to achieve high branch coverage and catch rare bugs.&lt;/p&gt;
&lt;p&gt;I was at &lt;a href=&#34;https://emptysqua.re/blog/notes-from-antithesis-bugbash/&#34;&gt;the first annual Antithesis conference&lt;/a&gt; this week. Antithesis has built a &lt;a href=&#34;https://antithesis.com/blog/deterministic_hypervisor/&#34;&gt;hypervisor&lt;/a&gt; that makes nondeterministic tests into deterministic ones, and they built a coverage-guided fuzzer to search for deep bugs. (I evaluated an early version of their product in 2020 for MongoDB, &lt;a href=&#34;https://antithesis.com/case_studies/mongodb_productivity/&#34;&gt;we&amp;rsquo;ve been a customer ever since&lt;/a&gt;.) So I&amp;rsquo;ve been wondering about Antithesis and other testing methods: how efficient are they, and how long do we have to run them to be confident we&amp;rsquo;ve found most of the bugs?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/sacred-beetle.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Painting of a dung beetle pushing a ball of dung while another pulls it&#34; title=&#34;&#34;&gt;&lt;h1&gt;Deterministic Programs&lt;/h1&gt;

&lt;p&gt;You&amp;rsquo;re in luck, inputs and program behaviors are 1:1. But some bugs are triggered by only a subset of inputs, and there are too many possible inputs for you to test them all. How many should you test?&lt;/p&gt;
&lt;p&gt;My colleague Max Hirschhorn told me about a 2020 paper, &lt;a href=&#34;https://mboehme.github.io/paper/FSE20.EmpiricalLaw.pdf&#34;&gt;Fuzzing: On the Exponential Cost of Vulnerability Discovery&lt;/a&gt;. The paper itself is confusing and contradictory, so I&amp;rsquo;ll try to summarize: If you choose inputs at random (with replacement), the number of inputs required to find each additional bug rises exponentially. This applies to stupid totally-random fuzzers and &lt;em&gt;also&lt;/em&gt;, depressingly, to smart coverage-guided fuzzers. I assume the coverage-guided fuzzers have a much smaller exponent, though.&lt;/p&gt;
&lt;p&gt;The first half of the paper shows charts from experiments with fuzzers (AFL and LibFuzzer) and hundreds of programs. The programs must all be deterministic, because the authors don&amp;rsquo;t consider that one input could produce multiple outcomes. They discover an &amp;ldquo;empirical law&amp;rdquo; that the marginal number of inputs to discover a bug increases exponentially in nearly all cases.&lt;/p&gt;
&lt;p&gt;The authors go on to mathematically model a dumb blackbox fuzzer that chooses inputs uniformly randomly from a domain &lt;em&gt;D&lt;/em&gt;, which can be subdivided into &lt;em&gt;S&lt;/em&gt; subdomains called &lt;em&gt;species&lt;/em&gt;. One species is one kind of outcome recognized by the fuzzer, e.g. a crash at a particular instruction, or a particular path through the program&amp;rsquo;s branches. Some of the species are the bugs we&amp;rsquo;re hunting. Each input has one species, and the probability of a random input belonging to a particular species &lt;em&gt;i&lt;/em&gt; is &lt;em&gt;p&lt;sub&gt;i&lt;/sub&gt;&lt;/em&gt;, which is the proportion of inputs in &lt;em&gt;D&lt;/em&gt; that lead to &lt;em&gt;i&lt;/em&gt;. The fuzzer chooses a subset of &lt;em&gt;n&lt;/em&gt; inputs from &lt;em&gt;D&lt;/em&gt;, and the expected number of species it discovers is:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/how-long-must-i-test/formula-360235c453d38f50fe4b1f29fc3b6e089ad7de8531aa8cbd4de7e9856e33a2af.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/formula-360235c453d38f50fe4b1f29fc3b6e089ad7de8531aa8cbd4de7e9856e33a2af.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\sum_{i=1}^{S} \left[ 1 - (1 - p_i)^n \right] = S - \sum_{i=1}^{S} (1 - p_i)^n&#34;&gt;
&lt;/picture&gt;&lt;p&gt;This is intuitive: the probability of discovering a single species is the inverse chance of failing to discover it &lt;em&gt;n&lt;/em&gt; times, and the expected number of discoveries is the sum of those probabilities over all the species.&lt;/p&gt;
&lt;p&gt;So far this is straightforward, but why does it lead to an exponential rise in the marginal number of inputs to discover another species, i.e. an exponential decline in the rate of species discovery over the course of a fuzzing campaign? Alas the authors give two contradictory explanations. First, perhaps real programs&amp;rsquo; &lt;em&gt;p&lt;sub&gt;i&lt;/sub&gt;&lt;/em&gt; probabilities are power-law distributed, i.e. the most probable species is much more likely than the next most probable and so on, flattening out towards the tail where rare species are about equally improbable. This theory matches the authors&amp;rsquo; experiments in the first half of the paper, but it&amp;rsquo;s not an explanation for &lt;em&gt;why&lt;/em&gt; species should be distributed this way.&lt;/p&gt;
&lt;p&gt;Immediately after explaining this theory, the authors blithely contradict themselves with a different theory:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When collecting baseball cards, the first couple of cards are always easy to find, but adding a new card to your collection will get progressively more difficult—even if all baseball cards were equally likely. This is related to the coupon collector&amp;rsquo;s problem &amp;hellip; covering one more branch or discovering one more bug will get progressively more difficult—so difficult, in fact, that each new branch covered and each new vulnerability exposed comes at an exponential cost.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/Coupon_collector%27s_problem&#34;&gt;coupon collector&amp;rsquo;s problem&lt;/a&gt; applies when species are all equally likely. As &lt;em&gt;n&lt;/em&gt; grows, the number of trials to discover &lt;em&gt;n&lt;/em&gt; species (&amp;ldquo;collect &lt;em&gt;n&lt;/em&gt; coupons&amp;rdquo;) grows like &lt;em&gt;n&lt;/em&gt; log(&lt;em&gt;n&lt;/em&gt;), which is much slower than exponential. But if there is a fixed number of species &lt;em&gt;n&lt;/em&gt; and you&amp;rsquo;ve discovered &lt;em&gt;k&lt;/em&gt; of them, what&amp;rsquo;s the marginal cost to discover one more? How does it compare to exponential growth?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/coupon.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;!-- 
import numpy as np
import matplotlib.pyplot as plt

n = 100
k_max = 10  # Using first 10 coupons
k_values = np.arange(1, k_max + 1)
expected_trials = []

# Calculate the expected number of trials for collecting k unique coupons:
# E[T_k] = n * sum_{i=n-k+1}^{n} (1/i)
for k in k_values:
    sum_val = sum(1 / i for i in range(n - k + 1, n + 1))
    expected_trials.append(n * sum_val)

# Fit the exponential curve using the first two points:
# f(1) = A = expected_trials[0] and f(2) = A * exp(B) = expected_trials[1]
A = expected_trials[0]  # should be 1
B = np.log(expected_trials[1] / A)

# Define the exponential function f(k) = A * exp(B*(k-1))
exp_vals = A * np.exp(B * (k_values - 1))

# Plotting both curves on the same figure
plt.figure(figsize=(10, 6))
plt.plot(k_values, expected_trials, marker=&#39;o&#39;, linestyle=&#39;-&#39;, markersize=6, label=&#34;Expected Trials&#34;)
plt.plot(k_values, exp_vals, marker=&#39;s&#39;, linestyle=&#39;--&#39;, markersize=6, label=&#34;Exponential Fit (first 2 points)&#34;)

plt.xlabel(&#34;Number of distinct coupons collected (k)&#34;)
plt.ylabel(&#34;Expected number of trials (log scale)&#34;)
plt.title(&#34;Coupon Collector&#39;s Problem for first 10 coupons (n = 100)&#34;)
plt.grid(True)

# Set y-axis to log scale and ensure x-axis has a tick at each integer
plt.yscale(&#34;log&#34;)
plt.xticks(k_values)

plt.legend()
plt.show()
!--&gt;
&lt;p&gt;It&amp;rsquo;s a poor fit. The exponentially increasing difficulty of finding a new species is not well-explained by the coupon collector&amp;rsquo;s problem, the power-law distribution must be the real reason. But that just poses a new question: why are the probabilities of species power-law distributed?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s another question: why aren&amp;rsquo;t coverage-guided fuzzers asymptotically better than this? Indeed, at the Antithesis conference, Zac Hatfield-Dodds suggested that they are. He gave a talk about his property-based testing library &lt;a href=&#34;https://hypothesis.readthedocs.io/&#34;&gt;Hypothesis&lt;/a&gt;, and threw out a thought-provoking aside. If I heard him correctly, he said &amp;ldquo;coverage-guided fuzzing reduces the time to find a bug from exponential to polynomial.&amp;rdquo; This paper seems to contradict him, but perhaps I don&amp;rsquo;t understand his meaning. I need to think or investigate more.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/mantis.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Painting of a praying mantis, wings unfurled and arms raised&#34; title=&#34;&#34;&gt;&lt;h1&gt;Nondeterministic Programs&lt;/h1&gt;

&lt;p&gt;Now things get really hard. Multithreaded programs interleave instructions nondeterministically, and distributed systems suffer unpredictable network delays, message loss, and clock skew. When you test these programs, not only must you try lots of inputs, but you must try each input many times.&lt;/p&gt;
&lt;p&gt;If you run the same test of a nondeterministic program repeatedly, you&amp;rsquo;ll probably get different outcomes, but not efficiently: mostly the program will just follow the same path over and over without discovering new bugs. You can explicitly increase randomness by introducing random network delays or various faults, as &lt;a href=&#34;https://jepsen.io/&#34;&gt;Jepsen&lt;/a&gt; does.&lt;/p&gt;
&lt;p&gt;You can find concurrency bugs in multithreaded programs much more efficiently if you control thread scheduling. &lt;a href=&#34;https://fhackett.com/&#34;&gt;Finn Hackett&lt;/a&gt;, a MongoDB PhD fellow and intern, showed me the paper &lt;a href=&#34;https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/asplos277-pct.pdf&#34;&gt;A Randomized Scheduler with Probabilistic Guarantees of Finding Bugs&lt;/a&gt;, in ASPLOS 2010. The authors point out that even a simple multithreaded program has an overwhelming number of possible schedules: &lt;em&gt;n&lt;/em&gt; threads that collectively execute &lt;em&gt;k&lt;/em&gt; instructions have on the order of &lt;em&gt;n&lt;sup&gt;k&lt;/sup&gt;&lt;/em&gt; schedules. But only some of the scheduling choices matter for reproducing any of the program&amp;rsquo;s concurrency bugs. This paper defines a nice term, bug &lt;em&gt;depth&lt;/em&gt;, &amp;ldquo;the minimum number of scheduling constraints that are sufficient to find a bug.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/figure-1-burckhardt.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;For example, in the paper&amp;rsquo;s Figure 1(a), a bug is reproduced if Thread 2 accesses a variable &lt;em&gt;before&lt;/em&gt; Thread 1 initializes it. This bug has one scheduling constraint, i.e. depth 1. In Figure 1(b), the bug requires Thread 1 to run an assignment &lt;em&gt;after&lt;/em&gt; Thread 2 runs an if-statement and &lt;em&gt;before&lt;/em&gt; it runs a method-call, so it has depth 2. The authors describe a thread-scheduling algorithm that finds any single bug of depth &lt;em&gt;d&lt;/em&gt; with probability 1/&lt;em&gt;nk&lt;sup&gt;d-1&lt;/sup&gt;&lt;/em&gt;, much better than the theoretical lower bound of 1/&lt;em&gt;n&lt;sup&gt;k&lt;/sup&gt;&lt;/em&gt;, i.e. the probability of picking one schedule out of all possible schedules. You can expect to find a depth-1 bug in an &lt;em&gt;n&lt;/em&gt;-threaded program in &lt;em&gt;n&lt;/em&gt; tries and a depth-2 bug in &lt;em&gt;nk&lt;/em&gt; tries.&lt;/p&gt;
&lt;p&gt;Their algorithm is called Probabilistic Concurrency Testing (PCT). You provide PCT with a program that executes up to &lt;em&gt;k&lt;/em&gt; instructions on &lt;em&gt;n&lt;/em&gt; threads. You provide a desired bug depth &lt;em&gt;d&lt;/em&gt;, and an input that &lt;em&gt;could&lt;/em&gt; trigger a bug of depth &lt;em&gt;d&lt;/em&gt;. PCT&amp;rsquo;s job is to find a schedule that &lt;em&gt;does&lt;/em&gt; trigger it. PCT assigns random initial priorities &lt;em&gt;d&lt;/em&gt;, &lt;em&gt;d&lt;/em&gt;+1, &amp;hellip;, &lt;em&gt;d&lt;/em&gt;+&lt;em&gt;n&lt;/em&gt; to the &lt;em&gt;n&lt;/em&gt; threads. PCT chooses &lt;em&gt;d&lt;/em&gt;-1 random &amp;ldquo;change points&amp;rdquo; in the program. It allows the program to execute one instruction at a time on one thread: the highest-priority runnable thread, i.e. the thread with the largest priority number that isn&amp;rsquo;t waiting for anything. When a thread stumbles upon the &lt;em&gt;i&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt; change point, PCT changes the thread&amp;rsquo;s priority to &lt;em&gt;i&lt;/em&gt; and continues. PCT uses higher numbers for threads&amp;rsquo; initial priorities (≥&lt;em&gt;d&lt;/em&gt;) than it uses for change points (&amp;lt;&lt;em&gt;d&lt;/em&gt;), so that a thread tends to be unscheduled when it hits its first change point, perturbing the system and increasing the chance of bugs.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/figure-6-burckhardt.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Here&amp;rsquo;s Figure 6(b) from the paper. It shows the same depth-2 bug as before. PCT reproduces it by assigning priority 3 to Thread 2, and priority 2 to Thread 1 (white circles), and inserting a change point (black circle) that changes Thread 2&amp;rsquo;s priority to 1. The probability of PCT assigning these initial priorities is 1/&lt;em&gt;n&lt;/em&gt;, and its probability of choosing this change point is 1/&lt;em&gt;k&lt;/em&gt;. Thus there&amp;rsquo;s a 1/&lt;em&gt;nk&lt;/em&gt; chance of finding this bug in one try, which matches the authors&amp;rsquo; promise of finding a depth 2 bug in &lt;em&gt;nk&lt;/em&gt; tries on average.&lt;/p&gt;
&lt;p&gt;In practice PCT is much more efficient than its worst-case bound, since there are usually many ways to reproduce a given bug.&lt;/p&gt;
&lt;p&gt;The authors implemented PCT for real:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For fine-grained priority control, we implemented PCT as a user-mode scheduler. PCT works on unmodified x86 binaries. It employs binary instrumentation to insert calls to the scheduler after every instruction that accesses shared memory or makes a system call. The scheduler gains control of a thread the first time the thread calls into the scheduler. From there on, the scheduler ensures that the thread makes progress only when all threads with higher priorities are blocked.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think by &amp;ldquo;shared memory&amp;rdquo; they mean the heap, which is shared among threads. In the abstract description above we called the total number of instructions &lt;em&gt;k&lt;/em&gt;, but the authors&amp;rsquo; implementation of PCT only places change points before synchronization points, like &lt;code&gt;pthread&lt;/code&gt; calls or atomics, and &lt;em&gt;k&lt;/em&gt; is the number of these synchronization points. Surprisingly, PCT can run huge programs like Mozilla at one-third normal speed. The paper&amp;rsquo;s evaluation section describes the bugs they found and how quickly, and the various optimizations and heuristics they added to the algorithm. This part interested me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Final Wait:&lt;/strong&gt; Some concurrency bugs might manifest much later than when they occur. We found that PCT missed some of the manifestations as the main thread exits prematurely at the end of the program. Thus, we artificially insert a priority change point for the main thread before it exits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There must be hordes of rarely-manifesting use-after-free bugs where the main thread deletes global variables as it exits, and background threads &lt;em&gt;sometimes&lt;/em&gt; wake up just before the program terminates.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/cricket.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Painting of field crickets, looking rather cute, one is emerging from a burrow&#34; title=&#34;&#34;&gt;&lt;h1&gt;Deterministic Simulation Testing&lt;/h1&gt;

&lt;p&gt;At MongoDB we run certain concurrency tests, which we know are highly nondeterministic, many times each night. There are rare bugs that pop up once every few months, or few &lt;em&gt;years&lt;/em&gt;. This is bad, because we don&amp;rsquo;t know which change introduced the bug, and once we diagnose it, we can&amp;rsquo;t be confident we fixed it.&lt;/p&gt;
&lt;p&gt;Some companies like FoundationDB built &lt;a href=&#34;https://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html&#34;&gt;deterministic simulation testing&lt;/a&gt; into their code from the start. When FoundationDB was acquired by Apple, some of the FoundationDB team left to create Antithesis and bring deterministic testing to projects that &lt;em&gt;didn&amp;rsquo;t&lt;/em&gt; build it in. Running MongoDB&amp;rsquo;s nondeterministic tests in Antithesis has been helpful, and I expect we&amp;rsquo;ll use them more.&lt;/p&gt;
&lt;h1&gt;Inconclusion&lt;/h1&gt;

&lt;p&gt;Distributed systems engineers (myself included) typically aren&amp;rsquo;t knowledgeable enough about randomized testing and we don&amp;rsquo;t deploy it thoughtfully. We should ask rigorously, exactly how much confidence do I gain with each trial, and how can I test nondeterministic programs efficiently? And the most vexing question of all: when can I stop? I don&amp;rsquo;t know the answers yet.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/how-long-must-i-test/wasps.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Painting of common wasps on a papery nest, which has been cut away to reveal the brood cells&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Images: &lt;a href=&#34;https://publicdomainreview.org/collection/fabres-book-of-insects-1921/&#34;&gt;Fabre’s Book of Insects&lt;/a&gt; (1921)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Zen priest Kenzen in New York City</title>
      <link>https://emptysqua.re/blog/kenzen/</link>
      <pubDate>Fri, 04 Apr 2025 17:39:42 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/kenzen/</guid>
      
      
      
      
      <description>&lt;p&gt;April 1, 2025. Zen priest Kenzen, formerly named Gessho, who was recently ordained at the Village Zendo in New York.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/kenzen/ajdavis_20250401_9395-Edit.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/kenzen/ajdavis_20250401_9435.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/kenzen/ajdavis_20250401_9457-Edit.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mohonk Preserve Rangers</title>
      <link>https://emptysqua.re/blog/mohonk-rangers/</link>
      <pubDate>Wed, 26 Mar 2025 11:43:51 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/mohonk-rangers/</guid>
      
      
      
      
      <description>&lt;p&gt;March 25, 2025. Mohonk Preserve rangers training for rescue operations. They practiced building anchors, ascending and descending ropes, rescuing an unconscious climber hanging on a rope, and securing a patient in a litter. (&lt;a href=&#34;https://emptysqua.re/blog/mohonk-rangers-2/&#34;&gt;More photos here&lt;/a&gt;.)&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-011.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-012.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-013.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-rangers/rangers-014.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gravity Vault Climbing Team</title>
      <link>https://emptysqua.re/blog/gravity-vault-climbing-team/</link>
      <pubDate>Wed, 19 Mar 2025 17:27:54 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/gravity-vault-climbing-team/</guid>
      
      
      
      
      <description>&lt;p&gt;February 18, 2025. The local youth climbing team training at the gym.&lt;/p&gt;
&lt;hr&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gravity-vault-climbing-team/climbing-team-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Red Rocks</title>
      <link>https://emptysqua.re/blog/red-rocks-2025/</link>
      <pubDate>Wed, 19 Mar 2025 10:58:13 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/red-rocks-2025/</guid>
      
      
      
      
      <description>&lt;p&gt;I climbed with five friends in Red Rocks, outside Las Vegas. We had a medium-disappointing week. The sandstone there is fragile for a day or two after it rains, and it rained twice, so we were relegated to mediocre limestone climbing instead of world-class sandstone for most of the trip. Even on the limestone days our time was cut short by cold winds and occasional hail. My major objectives like &lt;a href=&#34;https://www.mountainproject.com/route/105732254/the-fox&#34;&gt;The Fox&lt;/a&gt; and &lt;a href=&#34;https://www.mountainproject.com/route/114115286/cactus-massacre&#34;&gt;Cactus Massacre&lt;/a&gt; will have to wait. I never took out my Canon, but I had some fun with my phone camera.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/red-rocks-2025/rr-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Review: SwiftPaxos: Fast Geo-Replicated State Machines</title>
      <link>https://emptysqua.re/blog/swiftpaxos/</link>
      <pubDate>Thu, 06 Feb 2025 21:32:59 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/swiftpaxos/</guid>
      
      
      
      
      <description>&lt;p&gt;&lt;a href=&#34;https://www.usenix.org/conference/nsdi24/presentation/ryabinin&#34;&gt;SwiftPaxos: Fast Geo-Replicated State Machines&lt;/a&gt;, in NSDI 2024, proposes a Paxos variant for networks with high latency, and different latencies between different pairs of nodes. Here&amp;rsquo;s a video of my presentation to &lt;a href=&#34;https://charap.co/summer-2024-reading-group-papers-papers-171-180/&#34;&gt;the DistSys Reading Group&lt;/a&gt;, and a written review of the paper below.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/rYxD4Wfa4x8?si=gpukEhj45fmGERES&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;hr&gt;


&lt;h1&gt;Previous Paxi&lt;/h1&gt;

&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Paxos_(computer_science)&#34;&gt;Paxos&lt;/a&gt;, as you know, is Leslie Lamport&amp;rsquo;s solution to the problem of consensus among unreliable nodes. The original Paxos achieved consensus on a &lt;em&gt;single&lt;/em&gt; value (or &amp;ldquo;decree&amp;rdquo;), and it takes 5 one-way network delays in the happy path, counting client-server delays as well as inter-server. The standard analysis of Paxos&amp;rsquo;s performance assumes that each one-way network delay is the &lt;em&gt;same&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Multi-Paxos&lt;/h2&gt;

&lt;p&gt;Multi-Paxos is more practical. It handles the real-world scenario where a group of nodes must agree on a sequence of commands that modify a shared state machine. There&amp;rsquo;s a long lived leader, and the client sends its request to the leader. The leader broadcasts the client&amp;rsquo;s command to all the nodes. It commits the command once it hears replies from a majority of nodes, at which point it can execute the command and send the result back to the client. In the happy case, Multi-Paxos takes 4 one-way network delays per value (again counting client-server and inter-server chatter).&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/multi-paxos.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Diagram from Wenbing Zhao, “FastPaxos Made Easy”, 2015&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h2&gt;FastPaxos&lt;/h2&gt;

&lt;p&gt;FastPaxos reduces network delays in the happy case. The client sends its request &lt;em&gt;directly&lt;/em&gt; to all nodes at the same time. When the leader hears from three quarters of the nodes, who all agree to put the same command in the same slot, the leader can commit and execute the command and reply to the client. This saves a single, one way network latency, for a total of 3 per value.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/fastpaxos.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Diagram from Wenbing Zhao, “FastPaxos Made Easy”, 2015, plus my scribbles&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Why is the &amp;ldquo;fast quorum&amp;rdquo; three quarters of the nodes? When I &lt;a href=&#34;https://emptysqua.re/blog/review-nezha/&#34;&gt;reviewed the Nezha paper&lt;/a&gt; a few months ago, I tried hard to understand this, and I think I succeeded. Then I forgot. While I read SwiftPaxos, I tried to remember. I failed. So let&amp;rsquo;s assume that three quarters is the size of a fast quorum, and that avoids certain problems that a mere majority would cause.&lt;/p&gt;
&lt;p&gt;In FastPaxos, different clients are all broadcasting simultaneously to all of the nodes, so there can be conflicts: different nodes can receive different commands, or in different orders. Some of the nodes could tell the leader, &amp;ldquo;I want command &lt;em&gt;c&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; in slot &lt;em&gt;i&lt;/em&gt;&amp;rdquo;, others could say, &amp;ldquo;I want command &lt;em&gt;c&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt; in slot &lt;em&gt;i&lt;/em&gt;.&amp;rdquo; The leader resolves a conflict by starting a new round using classic Multi-Paxos. Thus conflicts makes &amp;ldquo;Fast&amp;rdquo; Paxos slower than Multi-Paxos.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/earth.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;If you use a geo-distributed deployment with clients in many regions, conflicts are more likely, because their broadcasts reach different nodes with different delays. And in a geo-distributed deployment, restarting consensus after a conflict is very expensive!&lt;/p&gt;
&lt;h2&gt;SwiftPaxos&lt;/h2&gt;

&lt;p&gt;This paper introduces SwiftPaxos. It claims to improve on FastPaxos in the happy case, with only 2 one-way message delays—just from the client to the leader and back! Even in the slow path, there are only 3 one-way delays. I&amp;rsquo;ll compare all these paxi and describe SwiftPaxos in more detail, and we&amp;rsquo;ll see if we agree.&lt;/p&gt;
&lt;h1&gt;Conflict Avoidance&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/conflict-avoidance.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.flickr.com/photos/zetson/3241975525&#34;&gt;(image source)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Sequels to FastPaxos attempt to avoid conflicts with various strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://charap.co/reading-group-EPaxos-revisited/&#34;&gt;Egalitarian Paxos&lt;/a&gt; (EPaxos) is leaderless. Clients broadcast all commands to all nodes. Each node tracks each command&amp;rsquo;s dependencies and waits until a strongly-connected component (SCC) of the dependency graph appears. Then all nodes execute commands in the SCC in the same deterministic order, even when some nodes think they observe dependency cycles. The size of the SCCs each node must track, and the delay before a command is executed, can be large (theoretically unbounded).&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://emptysqua.re/blog/review-nezha/&#34;&gt;Nezha&lt;/a&gt; is similar, except it introduces deadline ordered multicast (DOM), which relies on reasonably synchronized clocks. DOM schedules each command to run sometime in the near future, and hopes that on three quarters of the nodes, the same set of commands will arrive before their deadline, so that will reduce conflicts. Nezha also falls back to a slow path when DOM makes a mistake. But Nezha recognizes that different orders are okay among non-conflicting commands (&amp;ldquo;commuting&amp;rdquo; commands) because their order doesn&amp;rsquo;t matter relative to each other.&lt;/li&gt;
&lt;li&gt;SwiftPaxos. Once again, nodes track dependencies among commands. SwiftPaxos never has dependency cycles (stay tuned). Like Nezha, SwiftPaxos ensures all nodes run conflicting commands in the same order, but they can run commuting commands in any order. SwiftPaxos has a low-key leader who&amp;rsquo;s mostly needed for accelerating the slow path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;What Are Nodes Agreeing To?&lt;/h1&gt;

&lt;p&gt;To understand all these systems, I found it helpful to ask what &lt;em&gt;value&lt;/em&gt; nodes are agreeing to.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paxos, Multi-Paxos: nodes agree that command &lt;em&gt;c&lt;/em&gt; is in slot &lt;em&gt;i&lt;/em&gt;, so they agree about the total order of commands.&lt;/li&gt;
&lt;li&gt;EPaxos, Nezha, SwiftPaxos: nodes agree that command &lt;em&gt;c&lt;/em&gt; has dependencies &lt;em&gt;s&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In EPaxos there&amp;rsquo;s &lt;em&gt;no&lt;/em&gt; consensus on ordering! Only &lt;em&gt;after&lt;/em&gt; consensus do the nodes look at the dependency graph that they agreed to, and then resolve cycles and run the commands in the same &lt;em&gt;partial&lt;/em&gt; order, where conflicting commands are ordered the same on all nodes. So order is part of the consensus protocol for Paxos and Multi-Paxos, but not for EPaxos. Nezha and SwiftPaxos are similar to EPaxos, but their consensus protocols prevent cycles.&lt;/p&gt;
&lt;h1&gt;When Can a Node Run a Command?&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Paxos, Multi-Paxos: as soon as it&amp;rsquo;s committed.&lt;/li&gt;
&lt;li&gt;EPaxos: after its dependencies (special rules for cycles).&lt;/li&gt;
&lt;li&gt;Nezha, SwiftPaxos: after its dependencies (never any cycles).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Paxos and Multi-Paxos, as soon as command &lt;em&gt;c&lt;/em&gt; is committed, all the commands that &lt;em&gt;c&lt;/em&gt; can depend on are already committed, because &lt;em&gt;c&lt;/em&gt; is already placed into a total order. In EPaxos, a command can run after the strongly-connected component to which it belongs has been identified, and its dependencies have run. If there&amp;rsquo;s a cycle, EPaxos breaks it by running the command with the lowest id number first. In EPaxos a command might wait for an unbounded amount of time before it can run: an adversarial workload can blow up the size of these strongly connected graph components. Since Nezha and SwiftPaxos prevent cycles, there&amp;rsquo;s a bounded delay between nodes agreeing about a command and executing it.&lt;/p&gt;
&lt;h1&gt;Semi-Strong Leader&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/augustus.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The role of the leader is where SwiftPaxos sharply differs from its predecessors. Every SwiftPaxos quorum must include the leader, but clients can propose commands to any node. The SwiftPaxos leader is sort of &lt;a href=&#34;https://en.wikipedia.org/wiki/Primus_inter_pares&#34;&gt;first among equals&lt;/a&gt;: it&amp;rsquo;s not as strong as in Multi-Paxos, but stronger than in Egalitarian Paxos (which of course has no leader).&lt;/p&gt;
&lt;p&gt;In both Multi-Paxos and SwiftPaxos, there are &lt;em&gt;N&lt;/em&gt; nodes with ids 0 through &lt;em&gt;N&lt;/em&gt; - 1, the leader of ballot &lt;em&gt;b&lt;/em&gt; is the node with id = &lt;em&gt;b&lt;/em&gt; mod &lt;em&gt;N&lt;/em&gt;, and this leader keeps its job until failure.&lt;/p&gt;
&lt;p&gt;As in FastPaxos, SwiftPaxos has slow quorums and fast quorums. In SwiftPaxos, a slow quorum is a majority of replicas, &lt;em&gt;including&lt;/em&gt; the leader. This is a departure from other protocols! A majority is not enough to commit a command, the majority must include the leader.&lt;/p&gt;
&lt;p&gt;Each fast quorum is &lt;em&gt;either&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More than ¾ of nodes, including the leader, or&lt;/li&gt;
&lt;li&gt;A specific set of nodes which is a majority including the leader.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can configure the system to use one kind of fast quorum or the other. The first is more fault-tolerant, the second is faster. Read Section 5.5 where the authors explore the tradeoffs; it&amp;rsquo;s subtle.&lt;/p&gt;
&lt;h1&gt;The SwiftPaxos Algorithm&lt;/h1&gt;

&lt;p&gt;Let&amp;rsquo;s talk about the algorithm already. How does it actually work?&lt;/p&gt;
&lt;p&gt;First, a client sends command &lt;em&gt;c&lt;/em&gt; to all the nodes, the same as in FastPaxos. Each node &lt;em&gt;N&lt;/em&gt; does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compute &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependency set: prior uncommitted conflicting commands. For example, &lt;em&gt;c&lt;/em&gt; reads some key &lt;em&gt;x&lt;/em&gt;, and node &lt;em&gt;N&lt;/em&gt; already knows of an uncommitted command that modifies &lt;em&gt;x&lt;/em&gt;, so that uncommitted command is in the dependency set of &lt;em&gt;x&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Broadcast &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependency set to the other nodes.&lt;/li&gt;
&lt;li&gt;Wait for messages agreeing about &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependencies from a fast quorum.&lt;/li&gt;
&lt;li&gt;If a fast quorum agrees about &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependencies, follow the fast path:
&lt;ul&gt;
&lt;li&gt;Wait until all of &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependencies are committed.&lt;/li&gt;
&lt;li&gt;Commit &lt;em&gt;c&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Wait until all of &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependencies have executed on &lt;em&gt;N&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Execute &lt;em&gt;c&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Otherwise, if no fast quorum agrees about &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependencies, follow the slow path:
&lt;ul&gt;
&lt;li&gt;The leader proposes its value for &lt;em&gt;c&lt;/em&gt;&amp;rsquo;s dependencies.&lt;/li&gt;
&lt;li&gt;Do normal Multi-Paxos agreement.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this figure from the paper, there are three clients. The circled nodes p1-3 are a fast quorum. In this case we&amp;rsquo;re using SwiftPaxos&amp;rsquo;s quirky configuration, where the fast quorum is a specific set of nodes that forms a majority and includes the leader.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/fast-path.001.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 1 from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The three clients submit commands &lt;em&gt;y&lt;/em&gt;, &lt;em&gt;x&lt;/em&gt;, then &lt;em&gt;z&lt;/em&gt;, in that order. But due to geo-distribution, the clients have different latencies to different nodes, and each node gets these commands in a different order:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/fast-path.004.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 1 plus my scribbles.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Some of these commands commute and some of them don&amp;rsquo;t.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/fast-path.005.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;All nodes agree about &lt;em&gt;y&lt;/em&gt;&amp;rsquo;s dependencies. Node p1 decided that &lt;em&gt;y&lt;/em&gt; does not conflict with &lt;em&gt;x&lt;/em&gt;, and so &lt;em&gt;y&lt;/em&gt; has an empty set as its dependencies on p1. Nodes p2 and p3 didn&amp;rsquo;t have to think at all: they had no prior uncommitted commands when &lt;em&gt;y&lt;/em&gt; arrived, so they also decided that &lt;em&gt;y&lt;/em&gt;&amp;rsquo;s dependency set is empty.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/fast-path.006.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;There is some disagreement about command &lt;em&gt;x&lt;/em&gt;. It arrived before the other commands on p1, so its dependency set is empty there. On p2, &lt;em&gt;x&lt;/em&gt; arrived after &lt;em&gt;y&lt;/em&gt;, but p2 decided it doesn&amp;rsquo;t conflict, so its dependency set is empty there too. But on p3, &lt;em&gt;x&lt;/em&gt; arrived after &lt;em&gt;z&lt;/em&gt;, and these commands &lt;em&gt;do&lt;/em&gt; conflict, so &lt;em&gt;x&lt;/em&gt; depends on &lt;em&gt;z&lt;/em&gt;. Thus p3 has a different opinion about &lt;em&gt;x&lt;/em&gt;&amp;rsquo;s dependency set than the other nodes. Since we&amp;rsquo;ve configured SwiftPaxos with a special fast quorum consisting of these three nodes, any disagreement among them aborts the fast path and forces SwiftPaxos to fall back to the slow path.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/fast-path.007.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Command &lt;em&gt;z&lt;/em&gt; has the same outcome: it arrived before conflicting commands on some nodes and after conflicting commands on other nodes, so &lt;em&gt;z&lt;/em&gt; has to fall back to the slow path too, i.e. normal Multi-Paxos consensus. A unique feature of SwiftPaxos&amp;rsquo;s fallback is that a node can vote &lt;em&gt;twice&lt;/em&gt; in the same ballot: once for a fast-path proposal and once for a slow-path proposal. Since the leader must be a member of both the fast and the slow quorum, it can ensure only one proposal wins.&lt;/p&gt;
&lt;p&gt;Apart from the consensus protocol, there are also optimizations in the client-server protocol. The leader optimistically executes any command immediately, before it&amp;rsquo;s committed, and sends the result to the client. If the leader and client learn the command was committed they can trust this result. Otherwise, they discard it. This saves some network latency compared to the normal pessimistic execution. On that fast path, a client submits its command to all nodes, receives agreeing replies directly from a fast quorum of nodes, and learns that its command was committed in just 2 one-way delays. The slow path takes only 3.&lt;/p&gt;
&lt;h1&gt;Why Is This Simpler Than EPaxos?&lt;/h1&gt;

&lt;p&gt;SwiftPaxos is simpler than EPaxos, believe it or not. Why? I think it&amp;rsquo;s because EPaxos commits commands in any order: a command c&amp;rsquo;s dependencies must all be known, but not necessarily committed yet, before c is committed. That&amp;rsquo;s how EPaxos ends up with cycles, and why it must build up a certain kind of subgraph (a strongly connected component) before it can start executing commands. SwiftPaxos waits to commit a command until after all of its dependencies are committed. This is why there are no dependency cycles, and why SwiftPaxos doesn&amp;rsquo;t suffer unbounded delays like EPaxos. (I didn&amp;rsquo;t understand this until I read the proof in the appendix.)&lt;/p&gt;
&lt;p&gt;After a leader change, there&amp;rsquo;s a recovery protocol in SwiftPaxos which is more complicated than what we&amp;rsquo;ve seen so far. The recovery protocol &lt;em&gt;does&lt;/em&gt; have to deal with cycles. I wonder: if SwiftPaxos used Raft elections, which choose a member from the majority with the longest log, might that prevent cycles during recovery?&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;

&lt;p&gt;The authors have a nifty approach to evaluation. They have sites scattered around the earth. Eight sites run clients, 3 sites run a server each, and 2 sites run both clients and a server. At the best client site, in the middle row of the chart, clients are near the nodes, and at the worst site at the bottom clients are far from them, so we can see the effect of getting a fast quorum agreement more or less often. The top row shows the average across all client sites. They compare how various protocols perform given different geo-locality and different rates of conflicts among commands.&lt;/p&gt;
&lt;p&gt;SwiftPaxos permits two ways of defining a fast quorum, and for this experiment they choose the second option: a specific set of nodes, a majority including the leader, constitute the only fast quorum.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/evaluation.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 7a from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;y&lt;/em&gt; axis is each protocol&amp;rsquo;s latency speedup relative to Paxos, and the &lt;em&gt;x&lt;/em&gt; axis is how often commands conflict. SwiftPaxos is the gold line. SwiftPaxos almost always beats the other protocols, of course, because this is the SwiftPaxos paper. But notice how some of the other protocols, particularly CURP+, are a little better at low conflict rates at the worst site. I don&amp;rsquo;t know the CURP+ protocol, so I don&amp;rsquo;t know why it&amp;rsquo;s better in this scenario.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/evaluation-2.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 7b from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the same average, best, and worst sites, evaluated by the cumulative distribution function of latency. The conflict rate is fixed at 2%. Again, SwiftPaxos beats all the others at the best site and the average site. But a couple of the other protocols are actually better, at this 2% conflict rate, at the worst site where the client is very far from a fast quorum.&lt;/p&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;The paper is well-written, but the protocol is complex and hard for me to understand, probably because I don&amp;rsquo;t know EPaxos well. I had to read the appendix to understand why it avoids dependency cycles, unlike EPaxos.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve heard people say EPaxos is impractical, mainly because of the unbounded execution delay and accumulation of state at the nodes. EPaxos is interesting research and often cited, but not actually used. Perhaps SwiftPaxos is the practical sequel to EPaxos? It&amp;rsquo;s a new paper, we&amp;rsquo;ll have to see how the community responds and builds upon it. The savings in one-way network delays seems significant.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/swiftpaxos/plate-158-american-swift.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Audubon, the American swift.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mixed-Media Climbing Art</title>
      <link>https://emptysqua.re/blog/gunks-climbing-mixed-media/</link>
      <pubDate>Wed, 05 Feb 2025 17:22:58 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/gunks-climbing-mixed-media/</guid>
      
      
      
      
      <description>&lt;p&gt;Some of my photographer friends hand-make books, sculptures, etc. from their photos. They inspire me to be more inventive.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://emptysqua.re/blog/dustin/&#34;&gt;Dustin&lt;/a&gt; holding his own mixed-media climbing art, framed with old climbing slings.&lt;/li&gt;
&lt;li&gt;Dustin training on his hangboard, framed in climbing chalk.&lt;/li&gt;
&lt;li&gt;Chris climbing &lt;a href=&#34;https://emptysqua.re/blog/bonticou-climbing/&#34;&gt;Bonticou Crag&lt;/a&gt;, framed with local rocks.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-climbing-mixed-media/gunks-framed-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-climbing-mixed-media/gunks-framed-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-climbing-mixed-media/gunks-framed-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>New Paltz in the Fall</title>
      <link>https://emptysqua.re/blog/new-paltz-fall-2023/</link>
      <pubDate>Tue, 28 Jan 2025 19:23:43 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/new-paltz-fall-2023/</guid>
      
      
      
      
      <description>&lt;p&gt;October 22, 2023. There was stupidly dramatic light and clouds over New Paltz in the late afternoon that day. I forgot to publish this until now.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-fall-2023/ajdavis_20231102_000161040001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-fall-2023/ajdavis_20231102_000161040003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-fall-2023/ajdavis_20231102_000161040008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/new-paltz-fall-2023/ajdavis_20231102_000161050007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Dustin Portzline</title>
      <link>https://emptysqua.re/blog/dustin/</link>
      <pubDate>Tue, 28 Jan 2025 08:28:37 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/dustin/</guid>
      
      
      
      
      <description>&lt;p&gt;January 27, 2025. My climbing mentor, rock guide and artist &lt;a href=&#34;https://www.advancedrockcraft.com/&#34;&gt;Dustin Portzline&lt;/a&gt;, at home.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dustin/dustin-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Which Beings Are Sentient?</title>
      <link>https://emptysqua.re/blog/which-beings-are-sentient/</link>
      <pubDate>Sat, 25 Jan 2025 16:40:05 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/which-beings-are-sentient/</guid>
      
      
      
      
      <description>&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/Pm0Ni1nHfgs?si=GK70CPydvhUaEb2E&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Which animals are sentient, and how can we liberate them from suffering? This is my January 23, 2025 dharma talk at the &lt;a href=&#34;https://villagezendo.org&#34;&gt;Village Zendo&lt;/a&gt;. I reviewed the philosopher Jonathan Birch&amp;rsquo;s book &amp;ldquo;The Edge of Sentience&amp;rdquo; and shared good news about humanity&amp;rsquo;s moral progress regarding animal welfare. Watch the video above, read the transcript below, or &lt;a href=&#34;https://emptysquare.libsyn.com/podcast/&#34;&gt;subscribe to my podcast&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;TRANSCRIPT&lt;/h1&gt;

&lt;p&gt;I would like to introduce you to somebody. This is Sojourner Truth Armstrong Davis, she is a Chinese dwarf hamster, and she is a little over a year old. We got her at a pet store across the river in Poughkeepsie, so her life is now more than half completed. She was in a cage with some sisters when we got her and we brought her home, and she&amp;rsquo;s been alone ever since. I mean, &lt;em&gt;we&amp;rsquo;re&lt;/em&gt; her friends, but it&amp;rsquo;s unclear if she knows that we exist.&lt;/p&gt;
&lt;p&gt;Actually, it&amp;rsquo;s unclear if she knows &lt;em&gt;she&lt;/em&gt; exists.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/53452915067_1b9c85e821_4k.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Sojourner Truth Armstrong Davis&#34; title=&#34;&#34;&gt;&lt;p&gt;This &amp;ldquo;problem of other minds&amp;rdquo; is a classic problem in Western philosophy. This question—what do we know about other beings&amp;rsquo; internal experience, based on what we can observe about their behavior or their anatomy or by measuring their brains with EEGs or whatever? What do we know about other minds, and specifically, do we know whether they&amp;rsquo;re conscious or not? It seems obvious that I&amp;rsquo;m conscious—I know that I exist. I experience something, as Descartes said, &amp;ldquo;I think therefore I am.&amp;rdquo; Buddha said that this is kind of overstated, that based on our experiences, we tend to conclude that our consciousness is a lasting, separate &lt;em&gt;thing&lt;/em&gt; that we need to protect and promote, whereas, in fact, my consciousness is more like a candle flame or a wave on an ocean or a bubble on a stream, as it says in the Diamond Sutra, I think. Temporary and not separate. But it doesn&amp;rsquo;t mean that I don&amp;rsquo;t exist! My consciousness is happening now, and other humans mostly behave like they&amp;rsquo;re conscious too. So it seems reasonable to guess that they are, but I can&amp;rsquo;t prove it. And with nonhuman animals, it&amp;rsquo;s much harder.&lt;/p&gt;
&lt;p&gt;In what ways is Sojourner&amp;rsquo;s mind like or unlike mine? I&amp;rsquo;m pretty sure she has no language, although she makes a lot of noise. Does she think? She has some behaviors that sort of seem like planning. We feed her a variety of seeds and she doesn&amp;rsquo;t eat them all at once. She caches them around her cage. Well, if we give her &lt;em&gt;fresh&lt;/em&gt; food, she eats right away. But if she gets food that seems like it&amp;rsquo;s going to last, she hides it. So this all seems pretty sophisticated. Mostly she saves her food in her little house, inside her cage, which is her nest, but then she also stores it in a few other places. Is she calculating that she wants backups, in case the main cache of food in her nest is lost somehow? I don&amp;rsquo;t know. She often shells a seed before she hides it. Is that to save space? I don&amp;rsquo;t know. When it&amp;rsquo;s cold in our house, she&amp;rsquo;ll stuff her little house with fluff for insulation. And one time, when we&amp;rsquo;d been gone for about a week and the house was quite cold, we set the thermostat down to 50 degrees, she had stuffed fluff around the outside of the house and piled it on top of her house to increase the insulation, which we hadn&amp;rsquo;t seen before.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/53453842061_e479ab43ee_o.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Sojourner Truth Armstrong Davis&#34; title=&#34;&#34;&gt;&lt;p&gt;Is that because she understands heat transfer and insulation in the way that a human does, or is it just an instinct? Her ancestors, who did that kind of thing at random, survived and passed on that instinct to her. I don&amp;rsquo;t know. Instinct seems more likely.&lt;/p&gt;
&lt;p&gt;My own cognition isn&amp;rsquo;t as clear cut either! When it&amp;rsquo;s cold in the house, I get an extra blanket from the closet. Is that because I understand heat transfer and insulation, or is that an instinct? Or is that operant conditioning: I tried it in the past, and it felt warmer, so now I&amp;rsquo;ve trained myself to do that. Or is it imitation: I&amp;rsquo;m mimicking a behavior I saw my mother do when I was a child. As both Buddha and Freud have observed, we&amp;rsquo;re not very good at observing the operations of our own minds.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The world of her senses is very different from mine. Her hearing is about as sensitive as mine. Her ears are much cuter, but they&amp;rsquo;re not necessarily better. I can hear low tones better than she can, whereas she can hear much, much higher tones. So the world would sound different to her than to me. I couldn&amp;rsquo;t find any studies on dwarf hamster olfaction, but if she&amp;rsquo;s anything like a mouse, then she has three or four times as many olfactory genes as I do. So she can smell a much wider variety of scents, and she&amp;rsquo;s much more sensitive to subtle smells than I am.&lt;/p&gt;
&lt;p&gt;Her vision is not very good. It&amp;rsquo;s blurry. She can&amp;rsquo;t focus on things unless they&amp;rsquo;re right in front of her, in the middle of her vision. She can barely see green and blue. She can&amp;rsquo;t see red at all. But her night vision is fairly good, and she has a very wide peripheral angle. So she seems to be optimized for detecting an owl swooping down on her in the dark, and for detecting a dark corner that she can run to to escape it. She also has very long whiskers. They&amp;rsquo;re half the length of her body, and she uses her whiskers and her smell to navigate. I think that the sensations of her whiskers must be very vivid to her.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/53454156954_263d29ab91_o.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Sojourner Truth Armstrong Davis&#34; title=&#34;&#34;&gt;&lt;p&gt;I have no idea what any of this is &lt;em&gt;like&lt;/em&gt;. What is it like to have her mind and body? The philosopher Thomas Nagel, in 1974 wrote an essay, &amp;ldquo;What Is It Like To Be A Bat?&amp;rdquo;, where he talked about a bat&amp;rsquo;s echolocation. He argued that with the bat&amp;rsquo;s echolocation, even if you understood everything about the physics of its ear and its neurons and its nerves, even if you could model this all with equations, you still wouldn&amp;rsquo;t understand what it&amp;rsquo;s &lt;em&gt;like&lt;/em&gt; to be a bat. And with Soji as well, her sensorium and her consciousness are just irreducibly different from ours.&lt;/p&gt;
&lt;p&gt;Is Soji sentient? She seems quite emotional. When I pick her up, she usually clicks her teeth together. This is called bruxing, and people agree that rodents make this sound when they&amp;rsquo;re happy. And normally, when I pick her up, she might also chatter, and I have no idea what that means. That might be happy too, because she makes this noise when I put her in her plastic ball, and she seems to enjoy running around the house, banging her plastic ball into things, so maybe that means she&amp;rsquo;s happy. But then she also makes that chattering sound when I put her back in her house. And she also tends to make it just when she&amp;rsquo;s sitting and there&amp;rsquo;s absolutely nothing going on, that&amp;rsquo;s actually when she&amp;rsquo;s the loudest. So maybe she&amp;rsquo;s announcing that this is her territory, wherever she happens to be. Maybe she&amp;rsquo;s horny. Maybe she&amp;rsquo;s expressing something that&amp;rsquo;s beyond human comprehension. I don&amp;rsquo;t know.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/Edge-of-Sentience-cover.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I&amp;rsquo;m in the middle of this fascinating book called The Edge of Sentience, which was published last year. It&amp;rsquo;s by the philosopher Jonathan Birch (who has nothing to do with the John Birch Society). It was published last year. It&amp;rsquo;s a free download from &lt;a href=&#34;https://www.edgeofsentience.com/&#34;&gt;edgeofsentience.com&lt;/a&gt; or you can buy it. Birch is proposing an ethical framework for deciding which beings might be sentient or not, because if they are sentient, then we have an ethical duty not to cause them undue suffering. Birch starts out by talking about consciousness. He distinguishes some aspects of the mind that we might call consciousness. And he breaks it down into three meanings.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/consciousness.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The first is selfhood, what we were talking about, this experience of being some sort of lasting single &lt;em&gt;thing&lt;/em&gt;. And then there&amp;rsquo;s also sapience, which is intelligence: learning, problem solving, analyzing. And then finally, there is what he calls the &amp;ldquo;broad&amp;rdquo; sense of sentience, which is phenomenal consciousness, or &amp;ldquo;raw feels&amp;rdquo;, just bits of experience in time. And he says that if you combine this with valence, which is having experiences that feel good or feel bad, then these two things together, having consciousness and feeling good or feeling bad, this is the &amp;ldquo;narrow&amp;rdquo; sense of sentience, which is having valenced conscious experience. Any being, any &lt;em&gt;system&lt;/em&gt; of neurons or software or whatever, that can have valenced conscious experiences, this is a sentient being, and we have a duty not to make it suffer.&lt;/p&gt;
&lt;p&gt;So he&amp;rsquo;s trying to build this ethical framework for dealing with creatures where we&amp;rsquo;re not sure if they&amp;rsquo;re sentient or not. So he says,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A sentient being (in the sense relevant to the present framework) is a system with the capacity to have valenced experiences, such as experiences of pain and pleasure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And if a being &lt;em&gt;might&lt;/em&gt; be sentient, then he says, we have a duty to avoid causing gratuitous suffering to this being:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Framework Principle 1. A duty to avoid causing gratuitous suffering. We ought, at minimum, to avoid causing gratuitous suffering to sentient beings either intentionally or through recklessness/negligence. Suffering is not gratuitous if it occurs in the course of a defensible activity despite proportionate attempts to prevent it. Suffering is gratuitous if the activity is indefensible or the precautions taken fall short of what is proportionate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So he&amp;rsquo;s not proposing to outlaw all meat or all animal research, just causing &lt;em&gt;gratuitous&lt;/em&gt; suffering. This seems reasonable to me, and what I think is really interesting about Birch&amp;rsquo;s book, and what has been the most newsworthy aspect of it, is that he is trying to minimize ethical &lt;em&gt;risk&lt;/em&gt;, moral risk, and this is the risk that we could be causing suffering to a sentient being without knowing it.&lt;/p&gt;
&lt;p&gt;In a way we&amp;rsquo;re already familiar with moral risk. If I drop a thumbtack on the ground in a public place where people might walk barefoot, I clearly have a moral duty to pick it up, even though I might never know that somebody steps on it after I leave. They might not be permanently damaged, but it would be painful, and I have a duty to avoid the risk of causing suffering to that sentient being in the future.&lt;/p&gt;
&lt;p&gt;Birch is talking about a different sort of moral risk. Think about dropping a live lobster into boiling water. There&amp;rsquo;s no question about what&amp;rsquo;s going to happen to the lobster, but there is a question about whether it&amp;rsquo;s a sentient being who feels pain, experiences it, in some way that we are morally obliged not to cause. If so, boiling it alive is immoral. So there&amp;rsquo;s some doubt whether it&amp;rsquo;s a moral or not, and it hinges on the lobster&amp;rsquo;s sentience. We may never know for sure, but it&amp;rsquo;s easy enough to avoid boiling lobsters alive. So in Birch&amp;rsquo;s opinion, we should do that to avoid the risk of causing suffering without knowing. Birch cause calls this the &amp;ldquo;precautionary principle&amp;rdquo;, and I think it&amp;rsquo;s a really interesting way to think about ethics.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/764ba2e8-dc4d-4822-88f0-488625ec43ac_5357.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;So Birch divides all systems into three categories, and &lt;em&gt;system&lt;/em&gt; is a deliberately vague and maybe strange word for a being, but it encompasses animals, plants, maybe it encompasses ant colonies as a whole, and it encompasses artificial intelligence, if such a thing already exists. So bring in large language models like GPT, just because it&amp;rsquo;s such a weird outlier, so that it helps us think about the whole range of possibilities. Here are Birch&amp;rsquo;s categories:&lt;/p&gt;
&lt;p&gt;Number one, &lt;strong&gt;sentience candidate&lt;/strong&gt;. This is the highest level. We&amp;rsquo;re not sure it&amp;rsquo;s sentient, but we&amp;rsquo;re pretty sure, and sentience candidates are anything where there&amp;rsquo;s evidence that these beings could experience pain and pleasure. We have some idea of what might cause pain and pleasure to them. So obviously, we can guess that all mammals feel pain, they react the same way to the same painful stimuli, and they have the same brain structures for experiencing the pain as we do, so mammals almost certainly sentient. Birch thinks that all adult vertebrates—mammals, fish, reptiles, amphibians—are also sentience candidates, but not necessarily vertebrate embryos. So, fish &lt;em&gt;eggs&lt;/em&gt; probably are not sentient. Probably don&amp;rsquo;t have to worry about causing suffering to them.&lt;/p&gt;
&lt;p&gt;When it comes to invertebrates like insects, insects don&amp;rsquo;t seem to feel pain if their exoskeleton is damaged, interestingly, maybe because there&amp;rsquo;s no point evolving that, since their exoskeletons can&amp;rsquo;t heal. But it&amp;rsquo;s possible that they do feel pain if they&amp;rsquo;re poisoned or heated. Birch analyzes their brain structures and behavior and says that insects are sentience candidates too, and he says that we should take proportionate steps to avoid gratuitous suffering to them. We don&amp;rsquo;t need to be as careful about causing pain to bugs as to hamsters, but we should take proportionate steps.&lt;/p&gt;
&lt;p&gt;The second category for Birch is an &lt;strong&gt;investigation priority&lt;/strong&gt;, and Birch puts some snails and worms here. Interestingly, he thinks spiders are less likely to be sentient than bugs, and there&amp;rsquo;s a whole chapter of brain structure analysis to explain why. Since the risk that these are sentient beings is lower, we don&amp;rsquo;t have to be as cautious about causing them suffering, but we should hurry to figure out whether they should be upgraded or not. So these are priorities for us to investigate further.&lt;/p&gt;
&lt;p&gt;Finally, the third category is &lt;strong&gt;not sentient&lt;/strong&gt;. Birch doesn&amp;rsquo;t think that plants, single celled organisms like bacteria, rocks, oceans, the earth, are sentient. These beings or things, can have very complex behaviors, but they don&amp;rsquo;t have neurons or anything like them, and so Birch doesn&amp;rsquo;t think that they experience anything.&lt;/p&gt;
&lt;p&gt;What about a large language model like GPT? We don&amp;rsquo;t know what it might find pleasurable or painful. It&amp;rsquo;s possible that asking it for a recipe is the equivalent of giving it an orgasm, and that asking it to write a poem is the equivalent of boiling it alive. But there doesn&amp;rsquo;t seem to be any way to know. There&amp;rsquo;s no behavior it evinces that resembles pleasure or pain, and it doesn&amp;rsquo;t have any structures that are anything like those with which animals experience pleasure and pain. So GPT is, for the moment, considered not sentient. My personal opinion here is that large language models are going to top out at high intelligence, but not sentience. They can think, but they can&amp;rsquo;t feel. Artificial intelligence will eventually be sentient, but it will be some other architecture, some other method of simulating intelligence, that will also feel. Birch thinks that we need to be very careful not to accidentally create a sentient artificial intelligence that suffers without our knowing it, because that would be a moral risk.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s this very interesting example called &lt;a href=&#34;https://openworm.org/downloads.html&#34;&gt;OpenWorm&lt;/a&gt;. It&amp;rsquo;s a research project to simulate in software every neuron of the tiny worm called &lt;em&gt;C. elegans&lt;/em&gt;. So &lt;em&gt;C. elegans&lt;/em&gt;, the real worm, has exactly 302 neurons. Every adult &lt;em&gt;C. elegans&lt;/em&gt; has the same neurons, connected in the same way. Researchers have just mapped them out and simulated them in software. And this project is called OpenWorm, and it has many contributors.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/worm.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Recently, a researcher &lt;a href=&#34;http://www.connectomeengine.com/download/Papers/Connectome.pdf&#34;&gt;connected up the worm neuron simulator to a physical robot&lt;/a&gt; made out of Legos, and it sort of maybe a little bit behaves like a worm; it navigates the space using some of the same methods that a worm would use.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/lego.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;So this is an interesting example, because Birch thinks that a &lt;em&gt;C. elegans&lt;/em&gt;, a real one, is an investigation priority. What about a simulation of a &lt;em&gt;C. elegans&lt;/em&gt; worm? I think that&amp;rsquo;s an investigation priority too. And if the worm were upgraded to a sentience candidate, something that we really have to avoid causing suffering to, what about the simulation of it? I see no difference. I don&amp;rsquo;t think that the hardware matters at all. I think it&amp;rsquo;s just software. I think that a simulation of a sentience being is sentient, and we must avoid making such a thing suffer.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/intro-to-buddhist-ethics.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Okay, so this is a dharma talk. What are Buddhists supposed to think about all of this? I&amp;rsquo;m reading this excellent book, &lt;a href=&#34;https://www.cambridge.org/core/books/an-introduction-to-buddhist-ethics/9813D1B3D65333E686B3ED528C70B972&#34;&gt;An Introduction to Buddhist Ethics&lt;/a&gt; by Peter Harvey, published in 2000. It&amp;rsquo;s got a lot of early Buddhist teachings, from the Pali canon, which I didn&amp;rsquo;t know until I read it, because I&amp;rsquo;ve only practiced Zen. In Zen, when someone asks a question, the teacher answers with riddles and quotes ancient Chinese poetry and says, you know, &amp;ldquo;The butterfly carries the precious jewel over the mountain.&amp;rdquo; So whenever I encounter early Buddhism and Shakyamuni Buddha, I really enjoy them. The old man was an intellectual. He thought everything through, and when somebody asked him a question, he would typically answer it in detail and tell you what he thought. And it&amp;rsquo;s clear that Buddha was very concerned about nonhuman animals. Indian religions in general are very concerned with animal welfare, in a way that might be unique among world religion families. Maybe because they say that humans are reincarnated as animals and vice versa. Or maybe the causation goes the other way: maybe they respect animals so much that they see no reason why humans couldn&amp;rsquo;t be reincarnated as animals.&lt;/p&gt;
&lt;p&gt;Both Hinduism and Buddhism teach that each of us has been, in some past life, a worm, a ghost, a God, a hamster, and will be again. Any being we ever meet at any time has at some point been our parent or our child in some past life that we both had, and we will have this relationship again in the future. According to the Pali canon, &lt;a href=&#34;https://obo.genaud.net/dhamma-vinaya/wp/sn/02_nv/sn02.15.011.bodh.wp.htm#p4&#34;&gt;Buddha said&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This samsara is without discoverable beginning. A first point is not discerned of beings roaming and wandering on, hindered by ignorance and fettered by craving.&lt;br&gt;
Whenever you see anyone in misfortune, in misery, you can conclude: &amp;ldquo;We too have experienced the same thing in this long course.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I love Buddha&amp;rsquo;s sort of scientific worldview—he doesn&amp;rsquo;t &lt;em&gt;know&lt;/em&gt;. He says samsara&amp;rsquo;s without &lt;em&gt;discoverable&lt;/em&gt; beginning.&lt;/p&gt;
&lt;p&gt;Or more concisely, in another sutra he says,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The bones you’ve left behind in transmigration are greater than a mountain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All these bodies you&amp;rsquo;ve died as, are greater than a mountain. But you don&amp;rsquo;t have to believe in reincarnation in this way, to see that we all experience misery. We all yearn to be liberated from suffering, and so it&amp;rsquo;s natural to feel sympathy for anybody who&amp;rsquo;s suffering.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/29e2ea65-2e58-46f9-bce7-9f08eb7fe0a2_6450.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Early Buddhist ethics teaches that harming a big, complex animal is worse than harming a small or simple one, and this is similar to Jonathan Birch&amp;rsquo;s framework. But on the other hand, Buddha didn&amp;rsquo;t seem to spend actually all that much time distinguishing between sentient and non sentient beings, like asking, is this or is this not? In Pali, he just talked about &lt;em&gt;sattas&lt;/em&gt;, which is equivalent to the Sanskrit &lt;em&gt;sattvas&lt;/em&gt;. In general, when we vow, as part of the Four Bodhisattva Vows, &amp;ldquo;to save all sentient beings&amp;rdquo;, in English, this, I think, comes from the Avatamsaka Sutra, the Bodhisattva vows of Samantabadhra. Or at least that was maybe an influence on it, via Chinese and so on. And Samantabadhra just says &amp;ldquo;saving sattvas&amp;rdquo; in Sanskrit, &amp;ldquo;saving beings&amp;rdquo;, maybe with the implication that sattvas are sentient. Certainly, if they&amp;rsquo;re worth saving, they must be. But it&amp;rsquo;s not a &lt;em&gt;division&lt;/em&gt; between sentient and insentient that Buddhism really emphasizes.&lt;/p&gt;
&lt;p&gt;Buddha, his goal was to free beings from suffering, by teaching them about the causes of suffering and the methods of being liberated from suffering. As we learn about suffering and we see that we&amp;rsquo;re trapped in it, and so is everybody else, it&amp;rsquo;s natural for us to be sympathetic for each other&amp;rsquo;s misery. Everybody similarly hates pain, yearns for peace.&lt;/p&gt;
&lt;p&gt;Buddha taught that we shouldn&amp;rsquo;t cause suffering for any sentient beings, nor kill them. In the Dhammapada, which is the oldest Buddhist scripture, and maybe the most plausibly related to what the old guy actually said, he&amp;rsquo;s quoted as saying,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All tremble at punishment,&lt;br&gt;
Life is dear to all.&lt;br&gt;
Comparing others with oneself,&lt;br&gt;
One should neither kill nor cause others to kill.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The early sutras say that we should help other beings and not cause them suffering, because that&amp;rsquo;s &lt;em&gt;essentially&lt;/em&gt; good, but also acting this way is good for me. It accumulates good karma for me, and that good karma kind of pushes me along, gives me momentum to achieve escape velocity from suffering and achieve nirvana. But doing good because it&amp;rsquo;s going to be good for me, is only &lt;em&gt;sort of&lt;/em&gt; good. Doing good for goodness&amp;rsquo; sake is best, and your intention when you act is really key. Buddha said that if you harm unintentionally, it won&amp;rsquo;t cause you to suffer in the future. And if you cause good by accident, it won&amp;rsquo;t help you in the future either. This is in contrast to Jainism, another Indian religion that was arising around the same time in India. In Jainism, unintentionally harming a bug is quite bad, not as bad as intentionally, but still something that you work very hard to avoid. Even today, you&amp;rsquo;ll see Jain monks often wearing a mask to avoid accidentally inhaling a bug, sweeping the ground to avoid accidentally stepping on one. They even are so concerned about killing plants that they won&amp;rsquo;t eat root vegetables, like digging up a carrot kills the carrot, whereas plucking a fruit leaves the tree intact, and so they don&amp;rsquo;t eat root vegetables. Buddhists are a lot more relaxed about this stuff. In Theravada, they&amp;rsquo;re vegetarian, and they certainly try to avoid killing bugs, but they don&amp;rsquo;t go quite so far, because as long as your intentions are good, that&amp;rsquo;s probably good enough.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/a14c36ac-e281-4a39-ac93-5a3adea4bc1c_5294.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;But Buddha said to think carefully about the consequences of your actions. So if you&amp;rsquo;re just like a well meaning idiot, obliviously causing carnage everywhere, that&amp;rsquo;s not the path to nirvana either. So I think Jonathan Birch&amp;rsquo;s framework is largely generally compatible with Buddha&amp;rsquo;s teaching here too. If we &lt;em&gt;suspect&lt;/em&gt; that insects experience pain, for example, we shouldn&amp;rsquo;t ignore the possibility of that inconvenient truth because it would, it would be inconvenient for us. We should be curious. We should investigate whether they&amp;rsquo;re sentient. We should be willing to change our behavior if it turns out that they might be.&lt;/p&gt;
&lt;p&gt;A difference between Jonathan birch and Shakyamuni Buddha is that Birch is not very concerned about &lt;em&gt;killing&lt;/em&gt; animals. He&amp;rsquo;s worried about how farm animals, for example, would experience being raised and slaughtered, but he&amp;rsquo;s not against eating meat if it could be raised and slaughtered ethically. If it can&amp;rsquo;t be ethically farmed, he&amp;rsquo;s against it. For example, he thinks that octopuses cannot be raised ethically as livestock. So he&amp;rsquo;s for a ban of that. But he&amp;rsquo;s not against eating wild caught octopus, I think, if that could be done without pain. And honestly, this is a relief to me, because I do eat meat, I do kill mosquitos. Not causing undue suffering when I do that is something that I can aspire to. This is obviously incompatible with the First Precept. I took jukai in 2006 and I promised not to kill beings. I recognize the contradiction here, and I don&amp;rsquo;t have an answer to you for that. But avoiding causing suffering to sentient beings is something that I could aspire to.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;A couple of years ago, due to Brexit, the United Kingdom was revising its animal welfare laws. It had withdrawn from the EU. And the EU had a treaty that said that animals are sentient beings, and member states must pay full regard to the welfare of animals. But the EU didn&amp;rsquo;t specify which animals are sentient or not. So I mean, technically, a brine shrimp is sentient. Do we have to worry about whether it experiences suffering or not? Unclear. When the UK replaced the European treaty with its own law, the first draft specified all invertebrates, so mammals, fish, reptiles, amphibians. Some activists argued for the inclusion of two specific groups of invertebrates. These are cephalopods like octopuses, squids and cuttlefish, or decapods, like lobsters and crabs. So in comes our hero, Jonathan Birch. The UK hires him to lead a commission to study the possibility that cephalopods and decapods are sentient beings. And I don&amp;rsquo;t know how long it took them, years? They wrote a hundred-page document with 300 citations to research studies. And the upshot is that the big, smart cephalopods, like octopuses, are likely enough to be sentient that British legislature has to keep their welfare in mind, and the same for lobsters and crabs. Of course, the news reports are overblown. They say, UK declares lobsters are sentient beings, but it doesn&amp;rsquo;t mean that you&amp;rsquo;re going to be tried for murder if you eat a lobster in London. It just means that the UK legislature must consider their welfare when it passes future laws. That&amp;rsquo;s it.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/2448c70f-cf89-4031-ba6f-c67a963dd6a7_6441.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;But still, even though the impact of the sentience act of 2022 might be small, it&amp;rsquo;s an example of the widening moral circle, widening it to include more beings. The philosopher Peter Singer wrote about this. He said that the &lt;a href=&#34;https://en.wikipedia.org/wiki/The_Expanding_Circle&#34;&gt;Expanding Circle&lt;/a&gt; is an example of &lt;em&gt;moral progress&lt;/em&gt;, of society&amp;rsquo;s moral beliefs improving over time. He said,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The circle of altruism has broadened from the family and tribe to the nation and race, and we are beginning to recognize that our obligations extend to all human beings. The only justifiable stopping place for the expansion of altruism is the point at which all whose welfare can be affected by our actions are included within the circle of altruism.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Progress. Progress is a very modern concept, maybe &lt;em&gt;the&lt;/em&gt; modern concept. Only once our technology and social change was happening rapidly enough that you could actually observe that change in one lifetime, did we start to think that progress, continuous improvement, was a trend that we could observe and expect to continue. And &lt;em&gt;moral&lt;/em&gt; progress is this astonishingly modern idea, that there is some better morality. We don&amp;rsquo;t know yet what it is. We can only guess. There might be principles that are better, but we don&amp;rsquo;t know what they are, but we or our descendants in the future, will believe them and behave accordingly.&lt;/p&gt;
&lt;p&gt;The idea makes me very uncomfortable, because it seems to indicate moral realism, or objective morality, that there is some moral truth that&amp;rsquo;s out there outside of our current beliefs, and that society can either progress toward it or move away from it. Otherwise how is it possible to define whether a change in morality is moral progress or just moral change? I&amp;rsquo;m pretty sure that there is no moral truth, that societies just choose their axioms and try to reason logically from them, and that every philosopher who does this either comes up with axioms that logically lead to &lt;a href=&#34;https://en.wikipedia.org/wiki/Mere_addition_paradox&#34;&gt;repugnant conclusions&lt;/a&gt; that offend our instincts, or that they lead to logical contradictions, generally both, and then we just try again and again, and it&amp;rsquo;s doomed.&lt;/p&gt;
&lt;p&gt;Buddha taught, I think, that there &lt;em&gt;is&lt;/em&gt; moral truth. That behaving in accord with the truth that we&amp;rsquo;re all interdependent, and in accord with the truth that all sentient beings want to be liberated from suffering, behaving in this way is essentially good. I mean, now that I say that, it sort of sounds reasonable. That as long as there are sentient beings in the universe, and it&amp;rsquo;s true that we&amp;rsquo;re not separate, that it makes sense for us to try to help each other all achieve what we want. And what we want naturally is not to suffer, kind of by the definition of suffering. So maybe, if there is a moral truth, he was right. Nagarjuna wrote that the precepts are empty, that they have no essential nature, they&amp;rsquo;re skillful means, and that they&amp;rsquo;re useful so long as they match our circumstances. That also sounds right to me. But then how do you define useful or not useful? Skillful or not skillful? This is all very ambiguous to me.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/f5937e50-09a4-4e1a-893b-4a0bbf8023e9_6316.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;But if moral progress is real and it involves less suffering for sentient beings, then &lt;a href=&#34;https://farmanimalwelfare.substack.com/p/ten-big-wins-in-2024-for-farmed-animals&#34;&gt;moral progress is happening now&lt;/a&gt; and and it&amp;rsquo;s happening extremely rapidly. Over 1000 companies have pledged not to use meat or eggs from caged chickens in the US. Forty percent of hens are now cage free, and that was only 6% a decade ago. Starting last year, McDonald&amp;rsquo;s, all the chicken meat and eggs that it uses come from cage free hens, and that&amp;rsquo;s two years sooner than McDonald&amp;rsquo;s had promised.&lt;/p&gt;
&lt;p&gt;Male chicks are generally useless to agriculture, and so in the past, they were generally handed sexed and then ground up alive. Now it&amp;rsquo;s technologically possible to sex them &lt;em&gt;in ovo&lt;/em&gt; and destroy them before they&amp;rsquo;re hatched. So in my opinion, this is less suffering. This is morally superior, and this affects billions of chicks every year.&lt;/p&gt;
&lt;p&gt;The UK&amp;rsquo;s largest grocery chains will stop selling live lobsters and crabs in their stores, so you don&amp;rsquo;t see those miserable lobsters in a tank there anymore. France&amp;rsquo;s largest producer of trout will now stun them unconscious before killing them, before slaughtering them. Germany&amp;rsquo;s largest grocery stores have cut the price of fake plant-based meat so that it&amp;rsquo;s equal to the price of animal meat. And this price cut led to a 30% increase in the sales of fake meat.&lt;/p&gt;
&lt;p&gt;Billions of sentient beings every year are affected by these changes, and there&amp;rsquo;s just a ton of momentum happening right now to accelerate moral progress when it comes to animal welfare in agriculture. So as bodhisattvas, we should rejoice that all of these beings are suffering less. One of the vows of the Bodhisattva Samantabadhra in the Avatamsaka Sutra, was to &amp;ldquo;rejoice in the merit and virtues of others.&amp;rdquo; So let&amp;rsquo;s rejoice that people are caring about animal welfare and doing this research and making these promises and making these changes, instead of feeling guilty that we&amp;rsquo;re not doing enough, or discouraged because there&amp;rsquo;s still so much suffering in the world. A bodhisattva&amp;rsquo;s attitude is joy that we&amp;rsquo;re all moving in the right direction.&lt;/p&gt;
&lt;p&gt;Of course, we can also find ways to do our individual part and to keep pushing in the right direction. We can be selective in what animal products we buy some of the grocery store. Labels really make a difference. Free range fowl, chicken etcetera, meat and eggs really is a meaningful label. It&amp;rsquo;s regulated by the USDA. Other animals, not so much, but it makes a difference for chickens. And then there are non government organizations that have more rigorous standards, and you can look for their certifications on animal products at the grocery store too. Obviously, eating less meat is better. The &lt;a href=&#34;https://www.openphilanthropy.org/focus/farm-animal-welfare/&#34;&gt;Farm Animal Welfare Program at Open Philanthropy&lt;/a&gt; is an effective activist and research group.&lt;/p&gt;
&lt;p&gt;Mostly, I just want you to know that even though there&amp;rsquo;s a lot of grim news and a lot of ways in which humanity is going in the wrong direction, This is something where we&amp;rsquo;re seeing a lot of progress right now, and it&amp;rsquo;s something to keep an eye on and to rejoice in.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/which-beings-are-sentient/ba42cc15-b3af-4301-b109-ef722c984bef_6490.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;hr&gt;
&lt;p&gt;Image sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.flickr.com/search/?user_id=93922379%40N00&amp;amp;view_all=1&amp;amp;text=sojourner&#34;&gt;Sojourner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getty.edu/art/collection/object/107C3S&#34;&gt;Rats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getty.edu/art/collection/object/106ZG0&#34;&gt;Lobster X-ray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getty.edu/art/collection/object/107D3F&#34;&gt;Caught lobsters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getty.edu/art/collection/object/106WY5&#34;&gt;Octopus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getty.edu/art/collection/object/107C3R&#34;&gt;Cat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getty.edu/art/collection/object/106WJV&#34;&gt;Willow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Winter 2024 Meditation Retreat</title>
      <link>https://emptysqua.re/blog/winter-ango-2024/</link>
      <pubDate>Mon, 13 Jan 2025 13:11:35 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/winter-ango-2024/</guid>
      
      
      
      
      <description>&lt;p&gt;December 2024. The Village Zendo&amp;rsquo;s winter meditation retreat in Garrison, NY, led by head student Ryugin.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-ango-2024/winter-sesshin-2024-011.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Is an Enlightened Person Afraid of Illness and Death?</title>
      <link>https://emptysqua.re/blog/is-an-enlightened-person-afraid/</link>
      <pubDate>Sat, 21 Dec 2024 18:53:54 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/is-an-enlightened-person-afraid/</guid>
      
      
      
      
      <description>&lt;p&gt;This is a dharma talk I gave at the Village Zendo December 12, 2024, about my mother&amp;rsquo;s brain surgery and end-of-life decisions. Here&amp;rsquo;s the video and a transcript.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/hdFXpozrIn0?si=9TBLR0hAhCo6zXAg&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen style=&#34;margin-bottom: 1em&#34;&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;p&gt;Two months ago, my mom had emergency brain surgery. She had an artery in her brain that was basically completely blocked. This is the artery that feeds Broca&amp;rsquo;s area and the temporal lobes. These are the language centers, and language is my mother&amp;rsquo;s whole life. &lt;a href=&#34;https://hms.cas.lehigh.edu/content/dena-davis-phd&#34;&gt;She&amp;rsquo;s a scholar of philosophy and law and religion&lt;/a&gt;. Reading and writing are her whole career. So this was especially scary for her. She had surgery on a Monday.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/lesnouvellesid00ram_0176.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Keishin and I went into Mount Sinai and it was supposed to be, I don&amp;rsquo;t know, six to eight hours. It took 10 hours. We were just waiting and waiting and waiting in the big waiting area that they have there. Finally, we found out that she was done and in the next building over. We went and sat with her until she woke up. When she woke up, she couldn&amp;rsquo;t speak at all, which nobody had warned us about. When she had gone into surgery, she was fine. She&amp;rsquo;d had a few episodes of difficulty speaking, but she was totally fine. And then after the surgery, she woke up, and as she was coming out of the anesthesia, she looked over at me. We made eye contact. It seemed like she recognized me. She smiled at me, and then she said &amp;ldquo;dub dub dub dub dah,&amp;rdquo; as if she expected me to understand.&lt;/p&gt;
&lt;p&gt;And then maybe she seemed scared. There were some tears coming out of her eyes, and she was—she was in the bed spasming. Grabbing the edges of the bed and shaking it, and her legs were kicking. I had no idea what was going on with her, and I was so scared. I said, &amp;ldquo;Can you point to some part of you that&amp;rsquo;s not comfortable,&amp;rdquo; and she couldn&amp;rsquo;t seem to understand what I was saying. She would look at me and she would nod, but then she wouldn&amp;rsquo;t comprehend. Or she couldn&amp;rsquo;t express anything.&lt;/p&gt;
&lt;p&gt;She had this huge bandage around her head, and she kept reaching up to scratch behind the bandage, and pull at the bandage. And I kept grabbing her hand and putting it down and saying, &amp;ldquo;It&amp;rsquo;s okay, just be patient. Leave that bandage alone. I don&amp;rsquo;t know why you can&amp;rsquo;t talk. I think it&amp;rsquo;s probably just the anesthesia. You&amp;rsquo;ll probably be able to talk soon. Just relax. Just be patient. Just keep waiting, Mom. I think you&amp;rsquo;re going to be okay, leave the bandage alone.&amp;rdquo; We did this for hours. At some point, she finally evaded me and pulled her bandage off, and it was heavy with blood. It was just soaked with blood, and her pillow was soaked with blood.&lt;/p&gt;
&lt;p&gt;Whenever this experience got too much for me, I would still be sitting there holding her hand, but I would close my eyes, breathe into my stomach and just be there. I wasn&amp;rsquo;t &lt;em&gt;reassuring&lt;/em&gt; myself because I didn&amp;rsquo;t know anything. I was just breathing. I was aware of Keishin sitting in the room across from me and watching me, watching my mother. Knowing that was reassuring.&lt;/p&gt;
&lt;p&gt;Every once in a while, a doctor would come in and get in Mom&amp;rsquo;s face and loudly ask, &amp;ldquo;Do you know where you are?&amp;rdquo; She&amp;rsquo;d say &amp;ldquo;buh.&amp;rdquo; The doctor would say, &amp;ldquo;What year is it? Is it 2022? Is it 2024?&amp;rdquo; She would just look at them. One guy strolled out of the room and whispered to a nurse, &amp;ldquo;Word salad.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I asked, &amp;ldquo;So it&amp;rsquo;s normal? How long is this going to last? She didn&amp;rsquo;t have aphasia before the surgery.&amp;rdquo; And the doctors said either it&amp;rsquo;s normal, or we don&amp;rsquo;t know, or wait until you can see some other doctor in the morning.&lt;/p&gt;
&lt;p&gt;And finally, it was nine o&amp;rsquo;clock and we had to go home.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/lesnouvellesid00ram_0036.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Over the next few days, I did find out, okay, maybe this is at the bad end of a normal spectrum. It doesn&amp;rsquo;t mean that she&amp;rsquo;ll never speak again. We still don&amp;rsquo;t know.&lt;/p&gt;
&lt;p&gt;She&amp;rsquo;d had surgery on a Monday, and Thursday was really one of the worst days of my life. I got a call around nine-thirty in the morning from the attending physician, saying, &amp;ldquo;Your mother has taken a turn for the worse. She may have had another stroke.&amp;rdquo; So jumped on my bike and I rode over. I was staying in New York. I rode to the hospital again. Jennifer was upstate. I got her on the phone, and we talked to the doctor.&lt;/p&gt;
&lt;p&gt;What happened? &amp;ldquo;Not sure, but what little language ability and responsiveness she had is gone.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Will she get better? &amp;ldquo;Don&amp;rsquo;t know.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Before my mother&amp;rsquo;s surgery, she had said to me, &amp;ldquo;Look, if something goes wrong and I can no longer write anything worth publishing—two tickets to Switzerland.&amp;rdquo; Meaning we were going to go to Switzerland together for assisted suicide. This was on my mind the whole time. When I saw her, I would say, thinking she might understand: &amp;ldquo;Mom, I think you&amp;rsquo;re going to get better, but if not, I remember your wishes. You can trust me. I remember what I promised.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Thursday morning was the time to put this into effect.&lt;/p&gt;
&lt;p&gt;I said, is she going to get better? &amp;ldquo;We don&amp;rsquo;t know.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Okay. Is she on life-supporting treatment? &amp;ldquo;Not right now.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Is there a way that we can end her life? &amp;ldquo;We can stop feeding her.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Okay, let&amp;rsquo;s give it a few more days.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/nyt-cajal-2.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;And then she started talking, that Sunday morning, seven days after her surgery. I was home in the city, and while I was in the shower, mom called. When I got out, I picked up her voicemail, and she had said, very carefully, &amp;ldquo;Hi, Jesse, it&amp;rsquo;s Dena. Could you bring some coffee when you come?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;That was the first full sentence I&amp;rsquo;d heard from her in almost a week.&lt;/p&gt;
&lt;p&gt;So she spent five weeks in the hospital, total. She finally came home. She&amp;rsquo;s been home for, I guess, about two months now, little less. And continues to improve. She&amp;rsquo;s walking with a cane, she&amp;rsquo;s speaking, she&amp;rsquo;s reading, she&amp;rsquo;s writing. Definitely still affected by the surgery, but also definitely not the worst that we had feared.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/nyt-cajal-brain.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I&amp;rsquo;m telling you this story, because this is about Zen training. This is about, why are we meditating? How can we use this training? How can we get it in our bodies such that when the shit hits the fan, we can put it into practice without thinking about it?&lt;/p&gt;
&lt;p&gt;Sometimes the old Zen texts seem to think that there&amp;rsquo;s no point to meditation. They seem to make fun of people who think that there is a purpose to meditation. Every once in a while, there will be a story about some monk who thinks that meditation makes your mind like a perfect mirror that reflects reality with no judgment, with no opinion. And this seems great. It both seems like a very effective state of mind to be in for responding to events, and it seems very peaceful. You don&amp;rsquo;t see a mirror freak out about what it reflects. It just reflects.&lt;/p&gt;
&lt;p&gt;Sounds lovely, right? But then you can tell really the Zen attitude is making fun of this desire to become a mirror. So there&amp;rsquo;s the contest—no actually, first I&amp;rsquo;m going to talk about this koan from the Eighth Century.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nangaku went to his student Baso&amp;rsquo;s hut and asked, &amp;ldquo;What are you doing these days?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Baso the student says, &amp;ldquo;These days I just sit.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Nangaku the teacher: &amp;ldquo;What is the purpose of sitting in zazen?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;The purpose of sitting in zazen is to become a Buddha.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Nangaku the teacher, he gets a terracotta tile and rubs it on a rock.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;What are you doing, master?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Nangaku: &amp;ldquo;Polishing a tile.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;What is the purpose of polishing a tile?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I&amp;rsquo;m polishing it into a mirror.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;How can polishing a tile make it into a mirror?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;How can sitting in zazen turn you into a Buddha?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And there&amp;rsquo;s an earlier story about a poetry contest between two students of Hongren. They&amp;rsquo;re competing to become his Dharma successor. There is the learned aristocratic student Shenxiu, and there&amp;rsquo;s the working class stiff Huineng.&lt;/p&gt;
&lt;p&gt;So the learned aristocrat, he writes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The body is the tree of enlightenment,&lt;br&gt;
The mind is like a bright mirror’s stand;&lt;br&gt;
Time after time polish it diligently,&lt;br&gt;
So that no dust can collect.&lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The other student, the working class stiff Huineng writes,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Enlightenment is not a tree,&lt;br&gt;
The bright mirror has no stand;&lt;br&gt;
Originally there is not one thing—&lt;br&gt;
What place could there be for dust?&lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And he, of course, is considered the winner and our ancestor, right? The one who says there&amp;rsquo;s no mirror to polish, there&amp;rsquo;s no dust to remove.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s the point of meditation? Become a flawless mirror that perfectly reflects reality with no smudgy delusion? Or is that idea actually stupid and there is no point to meditation whatsoever. And if so, what were we doing for that last half an hour? And why?&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/nyt-Santiago-Ramon-y-Cajal.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;And even taking another step back from this, some texts really seem to criticize asking questions like this, like, &amp;ldquo;What is the purpose?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s the koan where Jizo asks Hogan, &amp;ldquo;What&amp;rsquo;s the point of going on a pilgrimage?&amp;rdquo; Hogan says, &amp;ldquo;I don&amp;rsquo;t know.&amp;rdquo; Jizo says, &amp;ldquo;Not knowing is most intimate.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So should we be asking this at all? Should we be trying to &lt;em&gt;know&lt;/em&gt; what is the purpose of meditation, or not?&lt;/p&gt;
&lt;p&gt;Now, look, I&amp;rsquo;m Jewish. I can&amp;rsquo;t help asking questions like this, analyzing texts, getting myself involved in the arguments. There&amp;rsquo;s a two-thousand-year-old Jewish joke about this: Somebody who is considering converting to Judaism goes to the Rabbi Shammai and asks: &amp;ldquo;Explain to me the whole Torah while I stand on one foot.&amp;rdquo; Shammai is offended and chases the person out.&lt;/p&gt;
&lt;p&gt;The same person goes to Rabbi Hillel and asks the same question: &amp;ldquo;Explain all of Torah to me while I stand on one foot.&amp;rdquo; Hillel says, &amp;ldquo;What you would hate if it were done to you, don&amp;rsquo;t do it to other people. The rest is commentary. Go study it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a famous story because it gets both sides: Wisdom is very simple, and it&amp;rsquo;s very complex. And the complexity is worth studying.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s do this. Let&amp;rsquo;s boldly analyze what is the point of meditation.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/781px-Golgi_1885_Plate_XIII.jpeg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Here&amp;rsquo;s one answer: We are likely to experience old age and sickness, and we are definitely going to die. And most of us experience some anxiety about this. So maybe the purpose of meditation is to resolve this anxiety. In the old stories about Buddha, yes, for him, resolving this anxiety was the thing that motivated him to meditate. After studying meditation for six or seven years, he had learned yoga and magical powers, and he had almost starved himself to death, he sat under a tree and made the vow that he would not get up again until he had resolved this anxiety about sickness and old age and death.&lt;/p&gt;
&lt;p&gt;And his insight after eight days of sitting was that suffering is caused by thirst, by &lt;em&gt;trishna&lt;/em&gt;, by the desire for things to be different from how they are. For less of what we don&amp;rsquo;t want. For more of what we do want. And that is on a foundation of delusion, that the self is this separate and lasting &lt;em&gt;thing&lt;/em&gt; that I can guard the borders of. That I can defend from the things that I don&amp;rsquo;t want. The borders behind which I can accumulate more of the things that I do want. And by seeing that this is all an illusion, we can resolve this anxiety about the end of &lt;em&gt;this&lt;/em&gt;.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/797px-Golgi_1885_Plate_XXI.jpeg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The monk Dogen had a similar story. He&amp;rsquo;s the founder of our lineage in Japan. He was born around 1200. When he was two, his father died, and when he was seven, his mother died, leaving him an orphan. At his mother&amp;rsquo;s funeral, he watched the incense rise up into the air and disappear. And he was devastated. Does this happen to everybody? Does this happen to everything? Will this happen to me? How do I understand this? How do I live with this knowledge? So that was what initially drove him to the monastery.&lt;/p&gt;
&lt;p&gt;Now, in the 1700 years between the lifetime of Buddha and of Dogen, a lot had been written down. Dogen read a lot of this stuff—he was a big scholar—and he read all of these paradoxical texts like, &amp;ldquo;There&amp;rsquo;s no mirror for dust to settle on.&amp;rdquo; Or, &amp;ldquo;Meditating to become like a Buddha is like polishing a tile to make it a mirror. It&amp;rsquo;s hopeless.&amp;rdquo; And he thought, okay, there&amp;rsquo;s something they&amp;rsquo;re not telling me: Why are we doing this?&lt;/p&gt;
&lt;p&gt;He traveled from Japan to China when he was 23, and traveled around China for four years. He found a teacher, Rujing, who seemed to him to have the real-deal original Chinese Buddhism. He studied with Rujing, and had a great insight. And his insight was: practice and enlightenment are the same thing. He wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To suppose that practice and realization are not one is a view of those outside the way; in Buddha Dharma they are one and the same. Because practice within realization occurs at the moment of practice, the practice of beginner&amp;rsquo;s mind is itself the entire original realization.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/golgi-nervous-system.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;When I first heard this slogan, I found it completely uninspiring. &amp;ldquo;Practice is enlightenment&amp;rdquo; pissed me off.&lt;/p&gt;
&lt;p&gt;At 22 I was miserable. I was depressed. I was smoking pot every day. I was failing at my job. I hated myself. I turned to meditation for help, something that would fix me. I wanted to get enlightened, because I heard that enlightenment was liberation from suffering, and I was suffering. So I checked myself into a monastery for a year. And we chanted Dogen. We read Dogen together. I kept getting this message, &amp;ldquo;practice is enlightenment.&amp;rdquo; And I thought, this is a &lt;em&gt;scam&lt;/em&gt;. Practice isn&amp;rsquo;t enlightenment. It&amp;rsquo;s a grind.&lt;/p&gt;
&lt;p&gt;So I ignored this slogan, that practice is enlightenment, for years.&lt;/p&gt;
&lt;p&gt;But after about 15 years of meditation, it started to make a little bit of sense. This is just my view of the teaching, but it&amp;rsquo;s been helpful to me, maybe it&amp;rsquo;ll be helpful to you: I am no longer trying to &lt;em&gt;become&lt;/em&gt; enlightened. I&amp;rsquo;m trying to &lt;em&gt;be&lt;/em&gt; enlightened more of the time.&lt;/p&gt;
&lt;p&gt;Like right now, I&amp;rsquo;m paying attention to you. I&amp;rsquo;m aware that I&amp;rsquo;m in this room. I am trying to speak the truth. I&amp;rsquo;m being enlightened. Then in a little bit I&amp;rsquo;ll get on the subway. I&amp;rsquo;ll shove somebody aside, I&amp;rsquo;ll get lost in thought, I&amp;rsquo;ll be deluded. And then I&amp;rsquo;ll come to, and then I&amp;rsquo;ll be enlightened again. And for sure, after spending so many years of practice, both sitting cross legged and staring at the floor and everyday practice, I am being enlightened more often than I was before.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/golgi-olfactory-bottom.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Now, I&amp;rsquo;ve read stories about people who become enlightened, and it&amp;rsquo;s a permanent change in their life. There are thousands of these stories. And I can&amp;rsquo;t say that I know for sure that this is just a folk tale. But as I get older, as I have more experience—I become more skeptical of these stories. Most of them are hundreds of years old. We don&amp;rsquo;t know these people, we don&amp;rsquo;t have a lot of independent witnesses of their behavior. And it&amp;rsquo;s a little bit suspicious that of all of the modern people who have claimed to be enlightened, almost inevitably they turned out to be alcoholics who sexually abused their students. I think we need to think very carefully about what path we are on. If that is one of the terminal subway stations, we need to figure out which station we transfer at. And I think we also just need to question whether this mindset of achieving some lasting change, as opposed to practicing being enlightened now, what effects those two things have in the long run on who we are and how we treat other people.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;m just trying to be enlightened more, and that&amp;rsquo;s what &amp;ldquo;practice is enlightenment&amp;rdquo; means to me. When I&amp;rsquo;m practicing, I&amp;rsquo;m being enlightened.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s true that being enlightened means not suffering. When I was sitting with my mom, when she came out of surgery, or when I was talking to the doctor about maybe ending her life—those were some of the most challenging moments in my life. But I wasn&amp;rsquo;t suffering. I wasn&amp;rsquo;t wishing it was different. I wasn&amp;rsquo;t thinking, &amp;ldquo;poor me.&amp;rdquo; I wasn&amp;rsquo;t thirsting for something else, because I was in the moment. I was very aware that I was being called to respond and that I needed to bring my whole self to it. And therefore I wasn&amp;rsquo;t suffering at all, because I wasn&amp;rsquo;t thinking about, &amp;ldquo;Do I prefer this or not?&amp;rdquo; I was just responding.&lt;/p&gt;
&lt;hr&gt;

&lt;img src=&#34;https://emptysqua.re/blog/is-an-enlightened-person-afraid/nyt-cajal-4.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;So Mom&amp;rsquo;s recovering slowly, as I said. This Thanksgiving, she actually was able to come up to New Paltz and have Thanksgiving dinner with Keishin and me—which, you know, was dicey. I wasn&amp;rsquo;t sure, but it worked out fine.&lt;/p&gt;
&lt;p&gt;After Thanksgiving dinner, I saw her standing at my kitchen counter, and she was picking up one foot and putting it down, picking up one foot and putting it down. She said, &amp;ldquo;I bet you can do this for a whole minute, right?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I said, do what?&lt;/p&gt;
&lt;p&gt;She said, &amp;ldquo;Stand on one foot for a minute.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I said yep.&lt;/p&gt;
&lt;p&gt;She said, &amp;ldquo;It&amp;rsquo;s a physical therapy thing. I can only do it for a few seconds.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;And I said, well, I better summarize the Torah real quick then.&lt;/p&gt;
&lt;p&gt;She laughed! She got it immediately. She said, &amp;ldquo;Very clever. Very well done.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;And I was aware, and I am aware, that my mother is just about the only person I know who would get that joke, who I can make that joke to. I didn&amp;rsquo;t know if she was gone or not, and she&amp;rsquo;s back. I have her, for now. Not forever, but for now. And that&amp;rsquo;s what being enlightened means for me, is to appreciate that.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Images: &lt;a href=&#34;https://publicdomainreview.org/collection/illustrations-of-the-nervous-system-golgi-and-cajal/&#34;&gt;Early Illustrations of the Nervous System by Camillo Golgi and Santiago Ramón y Cajal&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Peter&#39;s Kill in the Snow</title>
      <link>https://emptysqua.re/blog/peters-kill-snow/</link>
      <pubDate>Thu, 05 Dec 2024 21:33:41 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/peters-kill-snow/</guid>
      
      
      
      
      <description>&lt;p&gt;December 5, 2024. Peter&amp;rsquo;s Kill in the Gunks, during a snow shower. I ran out to get some photos of the snow falling in the morning light. While I was on the clifftop the storm returned and absolutely nuked me. It was impossible to keep my lens clean enough to take more photos, and the sunlight was blocked by clouds, so I came home.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/peters-kill-snow/ajdavis_20241205_7739.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/peters-kill-snow/ajdavis_20241205_7771.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/peters-kill-snow/ajdavis_20241205_7837.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Supper&#39;s Ready</title>
      <link>https://emptysqua.re/blog/gan-suppers-ready/</link>
      <pubDate>Sun, 24 Nov 2024 22:27:12 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/gan-suppers-ready/</guid>
      
      
      
      
      <description>&lt;p&gt;November 23 and 24, 2024. &lt;a href=&#34;https://www.instagram.com/zganee/&#34;&gt;Gan&lt;/a&gt; attempting &lt;a href=&#34;https://www.mountainproject.com/route/105877392/suppers-ready&#34;&gt;Supper&amp;rsquo;s Ready&lt;/a&gt;, a 5.12a climb in the Gunks, with her climbing partner &lt;a href=&#34;https://www.instagram.com/kenjkendel/&#34;&gt;Kenny&lt;/a&gt;.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-011.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gan-suppers-ready/suppers-ready-012.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;P.S.: On November 25, &lt;a href=&#34;https://www.instagram.com/p/DC0UTIENIcZ/&#34;&gt;she sent&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Predictive Scaling in MongoDB Atlas, an Experiment</title>
      <link>https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/</link>
      <pubDate>Thu, 31 Oct 2024 16:52:03 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/</guid>
      
      
      
      
      <description>&lt;p&gt;At MongoDB, we experimented to see if we could predict each DBaaS customer&amp;rsquo;s demand fluctuations, and auto-scale them using this foreknowledge. Senior Data Scientist Matthieu Humeau and I spoke about this experiment at &lt;a href=&#34;https://www.datacouncil.ai/austin&#34;&gt;Data Council&lt;/a&gt; and &lt;a href=&#34;https://nycsystems.xyz/october-2024.html&#34;&gt;NYC Systems&lt;/a&gt;. Here&amp;rsquo;s the video from NYC Systems, and a written version is below.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/VLi9MHnBJzQ?si=RPxsPt1SjPNX8tkM&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;h1&gt;Replica Sets&lt;/h1&gt;

&lt;p&gt;MongoDB is generally deployed as a group of servers, where one is the primary and at least two are secondaries. The client sends all writes to the primary, and the secondaries replicate those writes, usually within milliseconds. The client can read from the primary or the secondaries. So secondaries can take some of the query load, and they&amp;rsquo;re hot standbys. If the primary fails, a secondary automatically becomes primary within a few seconds, with zero data loss.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/replica-set.png&#34; alt=&#34;A drawing of three servers. One is primary, the others are secondaries. Replication goes from the primary to the secondaries. A MongoClient reads and writes at the primary and also reads from the secondaries.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;MongoDB replica set.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;Atlas&lt;/h1&gt;

&lt;p&gt;MongoDB is free and open source, you can download it and deploy a replica set yourself, and lots of people do. But these days people mostly use our cloud service, &lt;a href=&#34;https://www.mongodb.com/products/platform/atlas-database&#34;&gt;MongoDB Atlas&lt;/a&gt;. Atlas started out as a database-as-a-service. Now we call it a Developer Data Platform because it offers a lot more than a database; we have triggers and events and streaming analysis and edge computing and vector search. But this experiment focuses on the DBaaS.&lt;/p&gt;
&lt;p&gt;The DBaaS is multi-region—customers can spread their data around the world or locate it close to their users—and it&amp;rsquo;s multi-cloud, it runs on AWS, GCP, and Azure. A customer can even deploy a replica set that includes servers in multiple clouds at once.&lt;/p&gt;
&lt;p&gt;MongoDB&amp;rsquo;s cloud is actually Amazon&amp;rsquo;s, Microsoft&amp;rsquo;s, or Google&amp;rsquo;s cloud.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/business-model.png&#34; alt=&#34;A flowchart. At the top a customer sends three dollars to MongoDB, which sends two dollars to Amazon, Google, and Microsoft.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;MongoDB&amp;rsquo;s secret business model (not drawn to scale).&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Atlas customers decide how many MongoDB servers to deploy in Atlas, what clouds to deploy them in, and what size of server: how many CPUs, how much RAM, and so on. Each server in a replica set must use the same tier. (&lt;a href=&#34;https://www.mongodb.com/blog/post/introducing-ability-independently-scale-atlas-analytics-node-tiers&#34;&gt;With exceptions&lt;/a&gt;.) We charge customers according to their choices: how many servers, what size, and how many hours they&amp;rsquo;re running. Of course, most of the money we charge our customers, we then pay to the underlying cloud providers. Those providers charge &lt;strong&gt;us&lt;/strong&gt; according to the number and size of servers and how long they&amp;rsquo;re running. If we could save money by anticipating each customer&amp;rsquo;s needs and perfectly scaling their server sizes up and down, according to their changing demands, that would save our customers money and reduce our carbon emissions.&lt;/p&gt;
&lt;table class=&#34;table table-striped table-bordered&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;Storage&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;RAM&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;vCPUs&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;Base Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;M10&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;10 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;2 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;2 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$0.08/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M20&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;20 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;4 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;2 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$0.20/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M30&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;40 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;8 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;2 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$0.54/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M40&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;80 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;16 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;4 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$1.04/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M50&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;160 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;32 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;8 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$2.00/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M60&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;320 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;64 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;16 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$3.95/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M80&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;750 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;128 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;32 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$7.30/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M140&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;1000 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;192 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;48 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$10.99/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M200&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;1500 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;256 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;64 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$14.59/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M300&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;2000 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;384 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;96 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$21.85/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M400&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;3000 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;488 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;64 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$22.40/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M700&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;4000 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;768 GB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;96 vCPUs&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;$33.26/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&#34;text-align:center; margin-bottom: 1em&#34;&gt;
&lt;em&gt;MongoDB Atlas-on-AWS pricing&lt;/em&gt;
&lt;/div&gt;
&lt;p&gt;We sell MongoDB server sizes as a set of &amp;ldquo;tiers&amp;rdquo;, those are named like M10, M20, and so on, on the left. Those map to specific instance sizes in the cloud provider, so an M10 is a certain size of AWS server, and we charge &lt;a href=&#34;https://www.mongodb.com/pricing&#34;&gt;a certain price on AWS&lt;/a&gt;. If the customer chooses to deploy their M10 on Azure or GCP then the size and price will be slightly different.&lt;/p&gt;
&lt;h1&gt;Manually-Triggered Scaling&lt;/h1&gt;

&lt;p&gt;A customer can change their server size with zero downtime. Here&amp;rsquo;s the process:&lt;/p&gt;
&lt;div style=&#34;display: flex&#34;&gt;
&lt;div style=&#34;flex: 1; padding-right: 1em&#34;&gt;
&lt;ul&gt;
&lt;li&gt;The customer clicks a button or executes an API call to resize their servers to a chosen tier.&lt;/li&gt;
&lt;li&gt;Atlas chooses a secondary and takes it offline,&lt;/li&gt;
&lt;li&gt;detaches its network storage,&lt;/li&gt;
&lt;li&gt;restarts it with a different server size,&lt;/li&gt;
&lt;li&gt;reattaches the storage,&lt;/li&gt;
&lt;li&gt;and waits for it to catch up to the primary, by replaying all the writes it missed while it was down.&lt;/li&gt;
&lt;li&gt;Atlas scales the other secondary likewise.&lt;/li&gt;
&lt;li&gt;Atlas tells the primary to become a secondary and &lt;a href=&#34;https://www.mongodb.com/docs/manual/reference/method/rs.stepDown/#election-handoff&#34;&gt;hand off its responsibilities to another server&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Atlas scales the former primary.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div style=&#34;flex: 1&#34;&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/gaston-rebuffat.jpg&#34; alt=&#34;Black and white photo of 1940s-era climber ascending a vertical rock wall&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.flickr.com/photos/nordique/4889485440&#34;&gt;Gaston Rebuffat.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The whole process takes about 15 minutes, and the customer can read and write their data normally throughout. Usually the customer&amp;rsquo;s application doesn&amp;rsquo;t even notice the scaling operation, except that once scaling is complete, performance is faster or slower, and the price is different.&lt;/p&gt;
&lt;h1&gt;Atlas Autoscaling Today&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/bolton-machine.png&#34; alt=&#34;Yellowed old patent diagram of a two-man vehicle, steered by the man in front and hand-cranked by the man behind&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://publicdomainreview.org/collection/cycling-art/&#34;&gt;Today&amp;rsquo;s state of the art.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Atlas customers can opt in to autoscaling, but today&amp;rsquo;s autoscaling is &lt;strong&gt;infrequent&lt;/strong&gt; and &lt;strong&gt;reactive&lt;/strong&gt;. The rules are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scale up by one tier after 1 hour of overload,&lt;/li&gt;
&lt;li&gt;scale down by one tier after 4 hours of underload.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overload is defined as over 75% CPU or RAM utilization, and underload is less than 50% of either. (&lt;a href=&#34;https://www.mongodb.com/docs/atlas/cluster-autoscaling/&#34;&gt;Details here&lt;/a&gt;.) Atlas only scales between adjacent tiers, e.g. if an M60 replica set is underloaded, Atlas will scale it down to M50, but not directly to any tier smaller than that. If the customer&amp;rsquo;s demand changes dramatically, it takes several scaling operations to reach the optimum server size. This means servers can be overloaded or underloaded for long periods! An underloaded server is a waste of money. An overloaded server is bad for performance, and if it&amp;rsquo;s really slammed it could interfere with the scaling operation itself. So Matthieu and I envisioned&amp;hellip;&lt;/p&gt;
&lt;h1&gt;The Ideal Future&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/flying-machine.png&#34; alt=&#34;Yellowed old patent diagram of a human-powered flying machine with feathered wings and tail.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://publicdomainreview.org/collection/cycling-art/&#34;&gt;The prototype.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;In the ideal future, we would forecast each replica set&amp;rsquo;s resource needs. We could scale a replica set up just before it&amp;rsquo;s overloaded, and scale it down as soon as it&amp;rsquo;s underloaded. We would scale it directly to the right server size, skipping intermediate tiers. We&amp;rsquo;d always use the cheapest size that could meet the customer&amp;rsquo;s demand.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/predictive-scaling-drawing.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Hand-drawn chart, a smooth line labeled &amp;ldquo;demand&amp;rdquo; fluctuates up and down, a rectilinear line labeled &amp;ldquo;capacity&amp;rdquo; goes up and down in sharp steps, always a bit above the demand line&#34; title=&#34;&#34;&gt;&lt;h1&gt;Predictive Scaling Experiment&lt;/h1&gt;

&lt;p&gt;Matthieu and I performed an experiment over the last year to see if predictive scaling was possible in the MongoDB Atlas DBaaS.&lt;/p&gt;
&lt;p&gt;The experiment was possible because Atlas keeps servers&amp;rsquo; performance metrics in a data warehouse. We have a couple of years of data about all servers&amp;rsquo; CPU and memory utilization, the numbers of queries per second, inserts per second, etc., all at one-minute intervals. Atlas has about 170,000 replica sets now, each with at least three servers, so it&amp;rsquo;s a stout data set. We chose 10,000 replica sets where customers had opted in to the existing reactive auto-scaler, and we analyzed their 2023 history. We split the history into a training period and a testing period, as usual with machine learning, and trained models to forecast the clusters&amp;rsquo; demand and CPU utilization. (CPU is the simplest and most important metric; eventually we&amp;rsquo;ll forecast RAM, disk I/O, and so on.) Once we&amp;rsquo;d prototyped this predictive scaler, we estimated how it would&amp;rsquo;ve performed during the testing period, compared to the reactive scaler that was running at that time.&lt;/p&gt;
&lt;p&gt;The prototype had three components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forecaster:&lt;/strong&gt; tries to predict each cluster&amp;rsquo;s future workload.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Estimator:&lt;/strong&gt; estimates CPU% for any workload, any instance size.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Planner:&lt;/strong&gt; chooses cheapest instance that satisfies forecasted demand.&lt;/li&gt;
&lt;/ul&gt;

&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/predictive-scaler-components.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Diagram of components, explained below&#34; title=&#34;&#34;&gt;&lt;p&gt;For each replica set, its history is an input to the Short-Term and Long-Term Forecasters. (I&amp;rsquo;ll explain why we have two Forecasters soon.) The Forecasters must be retrained every few minutes, as new samples arrive.&lt;/p&gt;
&lt;p&gt;From the same data warehouse we sampled 25 million points in time from any replica set in Atlas. Each of these samples includes a count of operations per second (queries, inserts, updates, etc.), an instance size, and the CPU utilization at that moment. We used this to train the Estimator, which can predict the CPU utilization for any amount of customer demand and any instance size. This is a hard problem, since we can&amp;rsquo;t see our customers&amp;rsquo; queries or their data, but we did our best. The Estimator must be retrained rarely, when there&amp;rsquo;s new hardware available, or a more efficient version of the MongoDB software. (Eventually we plan to train an Estimator for each MongoDB version.)&lt;/p&gt;
&lt;p&gt;The Forecasters and Estimator cooperate to predict each replica set&amp;rsquo;s future CPU on any instance size available. E.g., they might predict that 20 minutes in the future, some replica set will use 90% CPU if it&amp;rsquo;s on M40 servers, and 60% CPU if it&amp;rsquo;s on more powerful M50 servers.&lt;/p&gt;
&lt;h2&gt;Predictive Scaling: Planner&lt;/h2&gt;

&lt;p&gt;Let&amp;rsquo;s look at the Planner in more detail. Here&amp;rsquo;s a forecasted workload, it&amp;rsquo;s forecasted to rise and then fall.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/forecasted-workload.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;A hand-drawn chart, CPU versus time. A line labeled &amp;ldquo;M40 estimate&amp;rdquo; rises above 75% CPU, then falls. A line labeled &amp;ldquo;M50&amp;rdquo; rises and falls but never rises above 75% CPU. A line labeled &amp;ldquo;plan&amp;rdquo; follows the M40 line except where it would cross 75% CPU. There, it follows the M50 line.&#34; title=&#34;&#34;&gt;&lt;p&gt;So the Planner&amp;rsquo;s plan is to use M40 servers until it would be overloaded, then switch to M50 during the peak, then switch back. Notice the replica set should start scaling up 15 minutes &lt;strong&gt;before&lt;/strong&gt; the overload arrives, so the scale-up is complete in time to avoid overload. It starts scaling down as soon as the risk of overload has passed.&lt;/p&gt;
&lt;h2&gt;Predictive Scaling: Long-Term Forecaster&lt;/h2&gt;

&lt;p&gt;Our goal is to forecast a customer&amp;rsquo;s CPU utilization, but we can&amp;rsquo;t just train a model based on recent fluctuations of CPU, because that would create a circular dependency: if we predict a CPU spike and scale accordingly, we eliminate the spike, invalidating the forecast. Instead we forecast metrics unaffected by scaling, which we call &amp;ldquo;customer-driven metrics&amp;rdquo;, e.g. queries per second, number of client connections, and &lt;a href=&#34;https://www.mongodb.com/docs/ops-manager/current/review-available-metrics/&#34;&gt;scanned-objects rate&lt;/a&gt;. We assume these are &lt;strong&gt;independent&lt;/strong&gt; of instance size or scaling actions. (Sometimes this is false; a saturated server exerts backpressure on the customer&amp;rsquo;s queries. But customer-driven metrics are normally exogenous.)&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/seasonal.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;A chart showing queries per second over several weeks. There are obvious weekly patterns, where weekdays have peaks and weekends don&amp;rsquo;t, and obvious daily spikes each weekday.&#34; title=&#34;&#34;&gt;&lt;p&gt;Our forecasting model, MSTL (multi-seasonal trend decomposition using LOESS), extracts components from the time series for each customer-driven metric for an individual replica set. It separates long-term trends (e.g., this replica set&amp;rsquo;s query load is steadily growing) and &amp;ldquo;seasonal&amp;rdquo; components (daily and weekly) while isolating residuals. We handle these residuals with a simple autoregressive model from the ARIMA family.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/MSTL.png&#34; alt=&#34;A chart showing an observed history of demand fluctuating over several weeks. Beneath it is a smooth line labeled &amp;#39;trend&amp;#39;, then a periodic wavy line labeled &amp;#39;daily&amp;#39;, a line with longer waves labeled &amp;#39;weekly&amp;#39;, and a semi-random-looking line labeled &amp;#39;residuals&amp;#39;.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;MSTL (multi-seasonal trend decomposition using LOESS)&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;By combining these components, we forecast each metric separately, creating a &amp;ldquo;Long-Term Forecaster&amp;rdquo; for each. Despite the name, the Long-Term Forecaster doesn&amp;rsquo;t project far into the future; it&amp;rsquo;s trained on several weeks of data to capture patterns, then predicts a few hours ahead.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/percent-seasonal.png&#34; alt=&#34;Three pie charts: 3% of servers have strong hourly seasonality and 5% have weak hourly seasonality. 24% of servers have strong daily seasonality and 32% have weak daily seasonality. 7% of servers have strong weekly seasonality and 17% have weak weekly seasonality.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;How often is demand seasonal?&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Most Atlas replica sets have daily seasonality. About 25% have weekly seasonality. Generally if a replica set has weekly seasonality it &lt;strong&gt;also&lt;/strong&gt; has daily seasonality. Hourly seasonality is rare, and anyway it isn&amp;rsquo;t helpful for planning a scaling operation that takes a quarter-hour. Replica sets with sufficient daily/weekly seasonality are predictable by the Long-Term Forecaster.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/example-forecast.png&#34; alt=&#34;A chart of queries per second over time, the same as shown earlier. In the final day of the chart is a line representing actual history, and a closely-matching green line labeled &amp;#39;forecast&amp;#39;.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Example &amp;ldquo;long-term&amp;rdquo; forecast.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;But only some replica sets have seasonality! For non-seasonal clusters, the Long-Term Forecaster&amp;rsquo;s prediction of customer-driven metrics is unusable.&lt;/p&gt;
&lt;table class=&#34;table table-striped table-bordered&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;Seasonal Clusters&lt;/th&gt;
      &lt;th&gt;Non-seasonal Clusters&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tr&gt;
    &lt;td&gt;Connections&lt;/td&gt;
    &lt;td&gt;3%&lt;/td&gt;
    &lt;td&gt;&lt;span style=&#34;color:red&#34;&gt;50%&lt;/span&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Query Rate&lt;/td&gt;
    &lt;td&gt;19%&lt;/td&gt;
    &lt;td&gt;&lt;span style=&#34;color:red&#34;&gt;71%&lt;/span&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Scanned objects Rate&lt;/td&gt;
    &lt;td&gt;27%&lt;/td&gt;
    &lt;td&gt;&lt;span style=&#34;color:red&#34;&gt;186%&lt;/span&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;span style=&#34;color:red; font-weight: bold&#34;&gt;&amp;uparrow; unusable&lt;/span&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;So we added a &amp;ldquo;self-censoring&amp;rdquo; mechanism to our prototype: the Long-Term Forecaster scores its own confidence based on its recent accuracy, and only trusts its prediction if its recent error has been small.&lt;/p&gt;
&lt;h2&gt;Predictive Scaling: Short-Term Forecaster&lt;/h2&gt;

&lt;p&gt;What can we do when the Long-Term Forecaster isn&amp;rsquo;t trustworthy? We didn&amp;rsquo;t want to fall back to purely-reactive scaling; we can still do better than that. We prototyped a &amp;ldquo;Short-Term Forecaster&amp;rdquo;: this model uses only the last hour or two of data and does trend interpolation. We compared this to a naïve baseline Forecaster, which assumes the future will look like the last observation, and found that trend interpolation beats the baseline 68% of the time (29% reduction in error).&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/short-term-forecaster.png&#34; alt=&#34;A chart with a spiky and semi-random-looking line labeled &amp;#39;query executor scanned objects per second&amp;#39;. In the final day of the chart, there are flat green lines labeled &amp;#39;baseline&amp;#39;, which show a forecast that assumes each measurement will remain the same for two hours. Angled red lines labeled &amp;#39;forecast&amp;#39; assume the current trend will continue for two hours, these are a closer match to reality than the baseline forecast.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Approximation of local trends for near future forecast.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h2&gt;Predictive Scaling: Estimator&lt;/h2&gt;

&lt;p&gt;The Forecasters predict customer demand, but we still need to know whether CPU utilization will be within the target range (50-75%). That&amp;rsquo;s the Estimator&amp;rsquo;s job. The Estimator takes the forecasted demand and an instance size (defined by CPU and memory), and outputs projected CPU. Using a regression model based on boosted decision trees trained on millions of samples, we&amp;rsquo;ve achieved fairly accurate results. For around 45% of clusters, our error rate is under 7%, allowing us to make precise scaling decisions. For another 42%, the model is somewhat less accurate but useful in extreme cases. We exclude the remaining 13% of clusters with higher error rates from predictive scaling.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/estimator-example.png&#34; alt=&#34;A chart with four inputs on the left and the output on the right. The four inputs are charts of metrics over time: connections created per second, queries per second, documents updated per second, and scanned objects per second. The output is a line labeled &amp;#39;historical CPU&amp;#39;, and a closely-matching line labeled &amp;#39;estimator prediction&amp;#39;.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Example of input and output of Estimator.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h2&gt;Predictive Scaling: Putting It All Together&lt;/h2&gt;

&lt;p&gt;With both forecasts and CPU estimates, the Planner can choose the cheapest instance size that we guess can handle the next 15 minutes of customer demand without exceeding 75% CPU. Our experiment showed that this predictive scaler, compared to the reactive scaler in use during the test period, would&amp;rsquo;ve stayed closer to the CPU target and reduced over- and under-utilization. For the average replica set it saved 9 cents an hour. That translates to millions of dollars a year if the predictive scaler were enabled for all Atlas users.&lt;/p&gt;
&lt;table class=&#34;table table-striped table-bordered&#34;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;/th&gt;
            &lt;th&gt;Predictive auto-scaler&lt;/th&gt;
            &lt;th&gt;Reactive auto-scaler&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;Average distance from 75% CPU target&lt;/td&gt;
            &lt;td style=&#34;background-color: #e0f2e0&#34;&gt;18.6%&lt;/td&gt;
            &lt;td&gt;32.1%&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;Average under-utilization&lt;/td&gt;
            &lt;td style=&#34;background-color: #e0f2e0&#34;&gt;18.3%&lt;/td&gt;
            &lt;td&gt;28.3%&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;Average over-utilization&lt;/td&gt;
            &lt;td style=&#34;background-color: #e0f2e0&#34;&gt;0.4%&lt;/td&gt;
            &lt;td&gt;3.8%&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;What&amp;rsquo;s next? Matthieu and other MongoDB people are improving the Estimator&amp;rsquo;s accuracy by adding more customer-driven metrics, and estimating more hardware metrics: not just CPU, but also memory, and perhaps co-modeling CPU and memory to capture interactions between them. We want to investigate the minority of customers with bad estimates and ensure the Estimator works for them too. We&amp;rsquo;ll try building specialized Estimators for each cloud provider and each MongoDB version. MongoDB can&amp;rsquo;t see our customers&amp;rsquo; data or queries, but we can gather statistics on &lt;a href=&#34;https://www.mongodb.com/docs/upcoming/core/query-shapes/&#34;&gt;query shapes&lt;/a&gt;—maybe we could use this to improve estimation.&lt;/p&gt;
&lt;p&gt;I can&amp;rsquo;t tell you a release date. Par for a public blog post, but still disappointing, I know. In this case we honestly need more experiments before we can plan a release. A private beta for a few customers will come soon. Before we can unleash a complex algorithm on our customers&amp;rsquo; replica sets we need a lot more confidence in its accuracy, and a lot of safeguards. We&amp;rsquo;ll always need the reactive auto-scaler to handle unexpected changes in demand. But I&amp;rsquo;m excited at the prospect of eventually saving a ton of money and electricity with precise and proactive auto-scaling.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/mongodb-predictive-scaling-experiment/propelling-carriage.png&#34; alt=&#34;A yellowed patent diagram from 1830 showing a large bicycle. One man standing upright is propelling the contraption with foot pedals and holds a two-handed crank labeled &amp;#39;steering wheel&amp;#39;. A man behind him lies prone and appears to use only his feet. Both wear tophats.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Predictive and reactive auto-scalers, cooperating.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://emptysqua.re/blog/e-store-and-p-store/&#34;&gt;This work was heavily inspired by Rebecca Taft&amp;rsquo;s PhD thesis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Also interesting: &lt;a href=&#34;https://christofidi.github.io/docs/2023_SoCC_Presentation.pdf&#34;&gt;Is Machine Learning Necessary for Cloud Resource Usage Forecasting?&lt;/a&gt;, ACM Symposium on Cloud Computing 2023.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mongodb.com/atlas&#34;&gt;MongoDB Atlas&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Cycling images from &lt;a href=&#34;https://publicdomainreview.org/collection/cycling-art/&#34;&gt;Public Domain Review&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Burial Vessel</title>
      <link>https://emptysqua.re/blog/green-burial-vessel/</link>
      <pubDate>Fri, 25 Oct 2024 20:01:42 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/green-burial-vessel/</guid>
      
      
      
      
      <description>&lt;p&gt;&lt;a href=&#34;https://www.instagram.com/p/C_nZFNPRR1x/&#34;&gt;Elizabeth Gross&lt;/a&gt; weaving a vessel for green burial.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/green-burial-vessel/elizabeth-gross-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/green-burial-vessel/elizabeth-gross-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/green-burial-vessel/elizabeth-gross-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Fossymandias</title>
      <link>https://emptysqua.re/blog/fossymandias/</link>
      <pubDate>Mon, 21 Oct 2024 09:27:10 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/fossymandias/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/fossymandias/younger-memnon.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Photo of a fragment of a statue of an ancient Egyptian pharaoah&#34; title=&#34;&#34;&gt;&lt;p&gt;I met a programmer of an antique tongue&lt;br&gt;
Who said: A vast, maintainerless pile of code&lt;br&gt;
Lies in GitHub. All around it flung&lt;br&gt;
Half-finished branches where development slowed,&lt;br&gt;
And READMEs full of bombast, heralding features&lt;br&gt;
Tell that its authors once with passion toiled&lt;br&gt;
Without release, their hearts chained to their object,&lt;br&gt;
Until their hearts gave out, their ardor spoiled:&lt;br&gt;
And in the manual these words appear:&lt;br&gt;
&amp;ldquo;My name is Fossymandias, the Greatest Project:&lt;br&gt;
Look on my works, ye Mighty, and despair!&amp;rdquo;&lt;br&gt;
No further boasts were heard from the engineers&lt;br&gt;
Of that colossal wreck. Boundless and bare&lt;br&gt;
The empty commit log stretches countless years.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Image: The Younger Memnon, © The Trustees of the British Museum. Shared under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) licence.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Climbing at Bonticou Crag</title>
      <link>https://emptysqua.re/blog/bonticou-climbing/</link>
      <pubDate>Sat, 19 Oct 2024 22:24:45 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/bonticou-climbing/</guid>
      
      
      
      
      <description>&lt;p&gt;October 19, 2024. Climbing undocumented routes in the Gunks.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-climbing/bonticou-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-climbing/bonticou-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-climbing/bonticou-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-climbing/bonticou-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-climbing/bonticou-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-climbing/bonticou-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Join Me At NYC Systems October 17</title>
      <link>https://emptysqua.re/blog/nyc-systems-october-2024/</link>
      <pubDate>Thu, 10 Oct 2024 17:36:40 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/nyc-systems-october-2024/</guid>
      
      
      
      
      <description>&lt;p&gt;New York&amp;rsquo;s hottest club is NYC Systems. I&amp;rsquo;m speaking there October 17 with my colleague Matthieu Humeau. We&amp;rsquo;ll describe our experiment with predictive auto-scaling in &lt;a href=&#34;https://www.mongodb.com/products/platform/atlas-database&#34;&gt;MongoDB Atlas&lt;/a&gt;. The other presenter that night will be Andrew Werner, talking about instrumentation of compiled code. Unfortunately for you, registrations filled up a
half hour after they opened. &lt;a href=&#34;https://nycsystems.xyz/october-2024.html&#34;&gt;Join the waitlist&lt;/a&gt;! Or &lt;a href=&#34;https://www.youtube.com/watch?v=SqyLP1CZDFk&#34;&gt;watch our presentation&lt;/a&gt; on the same topic at the Data Council conference this spring.&lt;/p&gt;
&lt;p&gt;NYC Systems is a tech talk series recently begun by &lt;a href=&#34;https://eatonphil.com/&#34;&gt;Phil Eaton&lt;/a&gt;. Phil also created a &lt;a href=&#34;https://eatonphil.com/bookclub.html&#34;&gt;giant online book club&lt;/a&gt; that reads books about software internals. He&amp;rsquo;s talented at organizing communities, I&amp;rsquo;ve been observing from the sidelines and I&amp;rsquo;m impressed.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Antlion Crag</title>
      <link>https://emptysqua.re/blog/antlion-crag/</link>
      <pubDate>Sun, 22 Sep 2024 18:11:44 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/antlion-crag/</guid>
      
      
      
      
      <description>&lt;p&gt;September 21, 2024. Chris on Refrigerator Crack 5.10a and Cloud Nine 5.9 at &lt;a href=&#34;https://gunksclimbers.org/gcc-projects/antlion/&#34;&gt;the new Ant Lion Crag in the Gunks&lt;/a&gt;.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/antlion-crag/ant-lion-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/antlion-crag/ant-lion-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/antlion-crag/ant-lion-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/antlion-crag/ant-lion-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Are You Prepared To Save A Life?</title>
      <link>https://emptysqua.re/blog/are-you-prepared-to-save-a-life/</link>
      <pubDate>Mon, 09 Sep 2024 22:55:07 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/are-you-prepared-to-save-a-life/</guid>
      
      
      
      
      <description>&lt;p&gt;You can find yourself in a critical situation without warning. Is Zen practice preparing you?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a dharma talk I gave to the &lt;a href=&#34;https://villagezendo.org/&#34;&gt;Village Zendo&lt;/a&gt; on September 5, 2024, about the classic koan, &amp;ldquo;Nansen kills the cat.&amp;rdquo; The first couple seconds of the video were cut off, sorry. There&amp;rsquo;s a written version below.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/p73Xlvc5cjE?si=ONSIDE6VfmZX87Z4&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen style=&#34;margin-bottom: 1em&#34;&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;p&gt;At the monastery on Mount Nansen, the monks of the eastern and western halls were arguing about a cat. This was late morning sometime around the year 830, in southern China. It was a cold winter, the monks didn&amp;rsquo;t have much to do. They were gathered in the kitchen, the warmest place in the monastery, and their boredom and isolation had boiled over and they started some argument about this cat. The abbot, Master Nansen, heard them. He stormed in and grabbed the cat. He picked up a big cleaver from the kitchen counter and said, &amp;ldquo;If you can say a word of Zen, I won&amp;rsquo;t kill it.&amp;rdquo; All the monks froze. He looked around. There wasn&amp;rsquo;t a word. He pinned the cat to the counter and &lt;strong&gt;THUNK&lt;/strong&gt; cut it in half. Its hind legs, disconnected, twitched. Its pink guts fell on the floor in a tangle, jiggling. The monks stared. One of them ran out the door and threw up in the snow.&lt;/p&gt;
&lt;p&gt;What were these stupid monks arguing about? Who should feed the cat? Whether a cat belongs in a Zen temple?&lt;/p&gt;
&lt;p&gt;In a commentary, Tenkei said they were arguing over whether the cat has Buddha-nature or not. If so, Master Nansen&amp;rsquo;s teaching cut through that argument. No Buddha-nature, no not-Buddha-nature. No cat. Or as Dogen said, Nansen didn&amp;rsquo;t cut through the cat, he cut through &amp;ldquo;Buddha&amp;rdquo;. I hope the stupid monks got the message. The poor cat didn&amp;rsquo;t decide to be an example in a philosophy lesson. It had sneaked into the monastery one night, following the smell of the mice who lived in its walls in the winter, eating the barley from the temple storehouse. The cat had been skittish at first but it came to trust the monks. And Nansen murdered it.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-you-prepared-to-save-a-life/nansen.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Ink brush painting of fierce monk with a sword in one hand, and a live cat in the other.&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Nanquan_Puyuan#/media/File:Shokei_Nansen_zanbyo_1495_RJP_438_detail.jpg&#34;&gt;Here&amp;rsquo;s a 15th-Century Japanese painting of Nansen and the cat&lt;/a&gt;. He has a nice sword here, but why does a monk have a sword? I think he used a kitchen knife.&lt;/p&gt;
&lt;p&gt;Did Nansen actually do this? Or did he maybe pantomime it? I&amp;rsquo;ve read essays on this koan that say this ancestor we revere wouldn&amp;rsquo;t actually murder a cat to teach his students. Wansong wrote this 250 years after the incident:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Eminent Xin of the Liao Dynasty criticized Nansen&amp;rsquo;s group for killing a living being, committing wrongdoing. Head Monk Wen, on the other hand, helped Nansen out, saying, &amp;ldquo;An ancient text has it that he just made the gesture of cutting—how could he have simply cut it in two with one stroke, sending fresh blood gushing?&amp;rdquo; In these two critiques of the ancestor, Mr. Wen&amp;rsquo;s fault is the graver, whereas Mr. Xin&amp;rsquo;s fault is the lesser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, according to Wansong, go ahead and say it was wrong to the kill cat, but don&amp;rsquo;t say he didn&amp;rsquo;t really do it!&lt;/p&gt;
&lt;p&gt;Gerry Shishin Wick agrees, he said, &amp;ldquo;Contrary to the opinions of some modern Zen teachers, that knife did cut the fur, the skin, the muscle, and the viscera.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Why are these people insisting Nansen really killed the cat? This is about the stakes of our practice. We spend thousands of hours staring at the floor. Are we or aren&amp;rsquo;t we training ourselves to respond in life or death situations?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;ve been rock climbing for a few years and I do it in a style called &amp;ldquo;trad&amp;rdquo; or &amp;ldquo;traditional&amp;rdquo; lead climbing, which is fairly dangerous. Trad climbing involves constant risk assessment and managing your fear.&lt;/p&gt;
&lt;p&gt;(Note to readers: this section is best watched on video. I used props.) When I&amp;rsquo;m leading a climbing route, I climb up a more-or-less vertical cliff. I wear a harness and I tie the rope to it, which will catch me comfortably if I fall. My partner has the rope going through a belay device. The belayer can slowly give out slack, or when I fall my belayer uses the device to catch me. The device is attached to the belayer&amp;rsquo;s harness so their weight counters my weight and stops my fall.&lt;/p&gt;
&lt;p&gt;But in the beginning, the belayer can&amp;rsquo;t do anything, because I&amp;rsquo;m climbing from next to the belayer to above them. The first 10 or 15 feet of a trad climb is always totally unprotected, the rope and the belayer are useless, you just must not fall. As soon as I can, I find a crack in the rock and I place some piece of protection there. Usually it&amp;rsquo;s a cam. I carry a dozen of these with me as I climb, in different sizes. They work like harpoons: you retract the lobes, stick the cam into the crack in the rock, and then it&amp;rsquo;s stuck. Later when the climb is finished, you remove the cam by retracting the lobes again and pull it out.&lt;/p&gt;
&lt;p&gt;As I climb up the cliff, I place protection as I go, and clip the rope through it. So if I fall, the belayer locks their belay device, and I fall down past my last piece of protection and eventually the rope stops me. Hopefully, I stop before I hit a ledge or the ground and get hurt. As I climb above the last piece I placed, I always risk a fall at least twice that distance. If I climbed 5 feet above my last piece, I&amp;rsquo;ll fall 10 feet. Actually, it&amp;rsquo;s always much more than twice as far, because the rope is elastic and there&amp;rsquo;s always slack in the rope, and I might pull my belayer off the ground some distance before I stop.&lt;/p&gt;
&lt;p&gt;The worst risk is when I climb through a section of rock where there&amp;rsquo;s no good cracks for placing protection, or the cracks are the wrong shape. I might place a cam into a funny-shaped crack and think it&amp;rsquo;s the right size cam and it will hold me, but it&amp;rsquo;s not and it pops out and I fall much farther. This has never happened to me, but it happens to climbers all the time, and it&amp;rsquo;s my main fear when I&amp;rsquo;m climbing.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a video of me lead climbing &lt;a href=&#34;https://www.mountainproject.com/route/113327448/ariels-brassiere&#34;&gt;a route&lt;/a&gt; that was easy and chill for me. When the video starts I already have one cam in the rock. I make a move, find the next good crack, place another cam, and keep climbing:&lt;/p&gt;
&lt;blockquote class=&#34;instagram-media&#34; data-instgrm-captioned data-instgrm-permalink=&#34;https://www.instagram.com/reel/CtsFlphN_b8/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; data-instgrm-version=&#34;14&#34; style=&#34; background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&#34;&gt;&lt;div style=&#34;padding:16px;&#34;&gt; &lt;a href=&#34;https://www.instagram.com/reel/CtsFlphN_b8/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; style=&#34; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%;&#34; target=&#34;_blank&#34;&gt; &lt;div style=&#34; display: flex; flex-direction: row; align-items: center;&#34;&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 40px; margin-right: 14px; width: 40px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: column; flex-grow: 1; justify-content: center;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 100px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 60px;&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;padding: 19% 0;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display:block; height:50px; margin:0 auto 12px; width:50px;&#34;&gt;&lt;svg width=&#34;50px&#34; height=&#34;50px&#34; viewBox=&#34;0 0 60 60&#34; version=&#34;1.1&#34; xmlns=&#34;https://www.w3.org/2000/svg&#34; &gt;&lt;g stroke=&#34;none&#34; stroke-width=&#34;1&#34; fill=&#34;none&#34; fill-rule=&#34;evenodd&#34;&gt;&lt;g transform=&#34;translate(-511.000000, -20.000000)&#34; fill=&#34;#000000&#34;&gt;&lt;g&gt;&lt;path d=&#34;M556.869,30.41 C554.814,30.41 553.148,32.076 553.148,34.131 C553.148,36.186 554.814,37.852 556.869,37.852 C558.924,37.852 560.59,36.186 560.59,34.131 C560.59,32.076 558.924,30.41 556.869,30.41 M541,60.657 C535.114,60.657 530.342,55.887 530.342,50 C530.342,44.114 535.114,39.342 541,39.342 C546.887,39.342 551.658,44.114 551.658,50 C551.658,55.887 546.887,60.657 541,60.657 M541,33.886 C532.1,33.886 524.886,41.1 524.886,50 C524.886,58.899 532.1,66.113 541,66.113 C549.9,66.113 557.115,58.899 557.115,50 C557.115,41.1 549.9,33.886 541,33.886 M565.378,62.101 C565.244,65.022 564.756,66.606 564.346,67.663 C563.803,69.06 563.154,70.057 562.106,71.106 C561.058,72.155 560.06,72.803 558.662,73.347 C557.607,73.757 556.021,74.244 553.102,74.378 C549.944,74.521 548.997,74.552 541,74.552 C533.003,74.552 532.056,74.521 528.898,74.378 C525.979,74.244 524.393,73.757 523.338,73.347 C521.94,72.803 520.942,72.155 519.894,71.106 C518.846,70.057 518.197,69.06 517.654,67.663 C517.244,66.606 516.755,65.022 516.623,62.101 C516.479,58.943 516.448,57.996 516.448,50 C516.448,42.003 516.479,41.056 516.623,37.899 C516.755,34.978 517.244,33.391 517.654,32.338 C518.197,30.938 518.846,29.942 519.894,28.894 C520.942,27.846 521.94,27.196 523.338,26.654 C524.393,26.244 525.979,25.756 528.898,25.623 C532.057,25.479 533.004,25.448 541,25.448 C548.997,25.448 549.943,25.479 553.102,25.623 C556.021,25.756 557.607,26.244 558.662,26.654 C560.06,27.196 561.058,27.846 562.106,28.894 C563.154,29.942 563.803,30.938 564.346,32.338 C564.756,33.391 565.244,34.978 565.378,37.899 C565.522,41.056 565.552,42.003 565.552,50 C565.552,57.996 565.522,58.943 565.378,62.101 M570.82,37.631 C570.674,34.438 570.167,32.258 569.425,30.349 C568.659,28.377 567.633,26.702 565.965,25.035 C564.297,23.368 562.623,22.342 560.652,21.575 C558.743,20.834 556.562,20.326 553.369,20.18 C550.169,20.033 549.148,20 541,20 C532.853,20 531.831,20.033 528.631,20.18 C525.438,20.326 523.257,20.834 521.349,21.575 C519.376,22.342 517.703,23.368 516.035,25.035 C514.368,26.702 513.342,28.377 512.574,30.349 C511.834,32.258 511.326,34.438 511.181,37.631 C511.035,40.831 511,41.851 511,50 C511,58.147 511.035,59.17 511.181,62.369 C511.326,65.562 511.834,67.743 512.574,69.651 C513.342,71.625 514.368,73.296 516.035,74.965 C517.703,76.634 519.376,77.658 521.349,78.425 C523.257,79.167 525.438,79.673 528.631,79.82 C531.831,79.965 532.853,80.001 541,80.001 C549.148,80.001 550.169,79.965 553.369,79.82 C556.562,79.673 558.743,79.167 560.652,78.425 C562.623,77.658 564.297,76.634 565.965,74.965 C567.633,73.296 568.659,71.625 569.425,69.651 C570.167,67.743 570.674,65.562 570.82,62.369 C570.966,59.17 571,58.147 571,50 C571,41.851 570.966,40.831 570.82,37.631&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/div&gt;&lt;div style=&#34;padding-top: 8px;&#34;&gt; &lt;div style=&#34; color:#3897f0; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:550; line-height:18px;&#34;&gt;View this post on Instagram&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;padding: 12.5% 0;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: row; margin-bottom: 14px; align-items: center;&#34;&gt;&lt;div&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(0px) translateY(7px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34;background-color: #F4F4F4; height: 12.5px; transform: rotate(-45deg) translateX(3px) translateY(1px); width: 12.5px; flex-grow: 0; margin-right: 14px; margin-left: 2px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(9px) translateY(-18px);&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;margin-left: 8px;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 20px; width: 20px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; width: 0; height: 0; border-top: 2px solid transparent; border-left: 6px solid #f4f4f4; border-bottom: 2px solid transparent; transform: translateX(16px) translateY(-4px) rotate(30deg)&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;margin-left: auto;&#34;&gt; &lt;div style=&#34; width: 0px; border-top: 8px solid #F4F4F4; border-right: 8px solid transparent; transform: translateY(16px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; flex-grow: 0; height: 12px; width: 16px; transform: translateY(-4px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34; width: 0; height: 0; border-top: 8px solid #F4F4F4; border-left: 8px solid transparent; transform: translateY(-4px) translateX(8px);&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: column; flex-grow: 1; justify-content: center; margin-bottom: 24px;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 224px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 144px;&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;p style=&#34; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;&#34;&gt;&lt;a href=&#34;https://www.instagram.com/reel/CtsFlphN_b8/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; style=&#34; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;&#34; target=&#34;_blank&#34;&gt;A post shared by A. Jesse Jiryu Davis (@jessejiryudavis)&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://www.instagram.com/embed.js&#34;&gt;&lt;/script&gt;
&lt;hr&gt;
&lt;p&gt;Being afraid of falling and injury when I&amp;rsquo;m climbing is a fascinating state of mind! In regular life I&amp;rsquo;m not physically afraid, hardly at all—driving a car or biking in the city are the most dangerous things I do besides climbing, but I&amp;rsquo;m so used to those I&amp;rsquo;m not afraid. Besides, climbing is special: we like to say it&amp;rsquo;s &amp;ldquo;the slowest dangerous sport.&amp;rdquo; You can be clinging to the wall in some precarious place, trying to decide what to do next, feeling afraid, &lt;strong&gt;for quite a while&lt;/strong&gt;. So you can watch your scared mind at work, see what it does, and learn to manage it. When I&amp;rsquo;m afraid, it&amp;rsquo;s hard to focus on problem-solving. It&amp;rsquo;s hard to focus on how to minimize risk and complete this climb. I get distracted by worrying about falling, wishing I hadn&amp;rsquo;t started the climb, feeling hopeless.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the Zen of climbing for me: observing how fear works, and learning how to redirect my attention to the actual climbing. There&amp;rsquo;s a handful of techniques: focus on the breathing, breathe loud and slow. Focus on the senses, the feel of my fingertips and toes on the rock. Look around, look down, acknowledge the height I&amp;rsquo;m at. &amp;ldquo;Don&amp;rsquo;t look down&amp;rdquo; is terrible advice. There are mantras, like &amp;ldquo;I&amp;rsquo;m safe right now,&amp;rdquo; or my favorite, &amp;ldquo;Let the body climb.&amp;rdquo; These days all the climbing coaches are saying you should meditate! And of course I bring my 20 years of attentional training to climbing, but I also use tools that are specific to climbing, and specific to fear.&lt;/p&gt;
&lt;p&gt;Last month I was trying &lt;a href=&#34;https://www.mountainproject.com/route/105799143/mf&#34;&gt;one of the most challenging trad lead climbs&lt;/a&gt; I&amp;rsquo;d ever done, and my mentor &lt;a href=&#34;https://www.advancedrockcraft.com/&#34;&gt;Dustin&lt;/a&gt; was belaying me and coaching me from the ground. Two things happened that rattled me. First, when I was starting the climb, before I&amp;rsquo;d placed any protection, when I was just free-soloing the first 10 feet of the climb, my hand slipped. I didn&amp;rsquo;t fall, but it totally rattled me, and I climbed down to the ground and breathed for a couple minutes. I climbed up again, past that point, placed a few pieces of protection, and got past an overhanging roof. I was sure that was the crux, the hardest part of the climb, so I stood on a little foothold for a while, shaking out my forearm muscles, catching my breath, calming myself down. I thought I could just cruise up the rest of the climb and I was feeling good.&lt;/p&gt;
&lt;p&gt;Then came something that really threw me: I started to climb up again, I placed two more pieces, and arrived at the &lt;strong&gt;true&lt;/strong&gt; crux. It turned out the hardest part was higher up. I could see what I needed to do, I needed to step on a tiny foothold, then lunge leftward and catch a big handhold. It was a fairly easy move, but &lt;strong&gt;committing&lt;/strong&gt;: I was practically jumping for the hold. If I didn&amp;rsquo;t catch the handhold I would definitely fall. And because I was now 5 feet above my last cam, I&amp;rsquo;d fall 10 feet minimum, more likely 20 feet.&lt;/p&gt;
&lt;p&gt;I felt like I had two minds. The thinker said, it&amp;rsquo;s a longish fall but safe, my last cam is totally solidly placed in the crack, I want to try this move. C&amp;rsquo;mon, try it!! And my body said, &amp;ldquo;No.&amp;rdquo; At the point of no return, I froze.&lt;/p&gt;
&lt;p&gt;I repeatedly climbed up to the crux, hesitated, and climbed back down to a ledge below my last cam. I couldn&amp;rsquo;t calm myself down and I started to give up. I asked Dustin to pull the rope tight so I could hang from my cam. If he had, I would have ruined my chance to claim a clean ascent. Dustin said, &amp;ldquo;I can do that, but are you sure? Do you want to try one more time?&amp;rdquo; Just saying a word, and hearing him be calm and encouraging, gave me the oomph I needed. I took a couple deep breaths and tried one more time. As I approached the crux and hesitated again, Dustin yelled up, &amp;ldquo;Increase the tempo!&amp;rdquo; I lunged for the next hold and latched it, and finished the climb.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a time lapse video Dustin made:&lt;/p&gt;
&lt;blockquote class=&#34;instagram-media&#34; data-instgrm-permalink=&#34;https://www.instagram.com/reel/C-L49Q1SZ3E/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; data-instgrm-version=&#34;14&#34; style=&#34; background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&#34;&gt;&lt;div style=&#34;padding:16px;&#34;&gt; &lt;a href=&#34;https://www.instagram.com/reel/C-L49Q1SZ3E/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; style=&#34; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%;&#34; target=&#34;_blank&#34;&gt; &lt;div style=&#34; display: flex; flex-direction: row; align-items: center;&#34;&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 40px; margin-right: 14px; width: 40px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: column; flex-grow: 1; justify-content: center;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 100px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 60px;&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;padding: 19% 0;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display:block; height:50px; margin:0 auto 12px; width:50px;&#34;&gt;
    &lt;svg width=&#34;50px&#34; height=&#34;50px&#34; viewBox=&#34;0 0 60 60&#34; version=&#34;1.1&#34;
         xmlns=&#34;https://www.w3.org/2000/svg&#34;&gt;&lt;g stroke=&#34;none&#34; stroke-width=&#34;1&#34; fill=&#34;none&#34; fill-rule=&#34;evenodd&#34;&gt;&lt;g transform=&#34;translate(-511.000000, -20.000000)&#34; fill=&#34;#000000&#34;&gt;&lt;g&gt;&lt;path d=&#34;M556.869,30.41 C554.814,30.41 553.148,32.076 553.148,34.131 C553.148,36.186 554.814,37.852 556.869,37.852 C558.924,37.852 560.59,36.186 560.59,34.131 C560.59,32.076 558.924,30.41 556.869,30.41 M541,60.657 C535.114,60.657 530.342,55.887 530.342,50 C530.342,44.114 535.114,39.342 541,39.342 C546.887,39.342 551.658,44.114 551.658,50 C551.658,55.887 546.887,60.657 541,60.657 M541,33.886 C532.1,33.886 524.886,41.1 524.886,50 C524.886,58.899 532.1,66.113 541,66.113 C549.9,66.113 557.115,58.899 557.115,50 C557.115,41.1 549.9,33.886 541,33.886 M565.378,62.101 C565.244,65.022 564.756,66.606 564.346,67.663 C563.803,69.06 563.154,70.057 562.106,71.106 C561.058,72.155 560.06,72.803 558.662,73.347 C557.607,73.757 556.021,74.244 553.102,74.378 C549.944,74.521 548.997,74.552 541,74.552 C533.003,74.552 532.056,74.521 528.898,74.378 C525.979,74.244 524.393,73.757 523.338,73.347 C521.94,72.803 520.942,72.155 519.894,71.106 C518.846,70.057 518.197,69.06 517.654,67.663 C517.244,66.606 516.755,65.022 516.623,62.101 C516.479,58.943 516.448,57.996 516.448,50 C516.448,42.003 516.479,41.056 516.623,37.899 C516.755,34.978 517.244,33.391 517.654,32.338 C518.197,30.938 518.846,29.942 519.894,28.894 C520.942,27.846 521.94,27.196 523.338,26.654 C524.393,26.244 525.979,25.756 528.898,25.623 C532.057,25.479 533.004,25.448 541,25.448 C548.997,25.448 549.943,25.479 553.102,25.623 C556.021,25.756 557.607,26.244 558.662,26.654 C560.06,27.196 561.058,27.846 562.106,28.894 C563.154,29.942 563.803,30.938 564.346,32.338 C564.756,33.391 565.244,34.978 565.378,37.899 C565.522,41.056 565.552,42.003 565.552,50 C565.552,57.996 565.522,58.943 565.378,62.101 M570.82,37.631 C570.674,34.438 570.167,32.258 569.425,30.349 C568.659,28.377 567.633,26.702 565.965,25.035 C564.297,23.368 562.623,22.342 560.652,21.575 C558.743,20.834 556.562,20.326 553.369,20.18 C550.169,20.033 549.148,20 541,20 C532.853,20 531.831,20.033 528.631,20.18 C525.438,20.326 523.257,20.834 521.349,21.575 C519.376,22.342 517.703,23.368 516.035,25.035 C514.368,26.702 513.342,28.377 512.574,30.349 C511.834,32.258 511.326,34.438 511.181,37.631 C511.035,40.831 511,41.851 511,50 C511,58.147 511.035,59.17 511.181,62.369 C511.326,65.562 511.834,67.743 512.574,69.651 C513.342,71.625 514.368,73.296 516.035,74.965 C517.703,76.634 519.376,77.658 521.349,78.425 C523.257,79.167 525.438,79.673 528.631,79.82 C531.831,79.965 532.853,80.001 541,80.001 C549.148,80.001 550.169,79.965 553.369,79.82 C556.562,79.673 558.743,79.167 560.652,78.425 C562.623,77.658 564.297,76.634 565.965,74.965 C567.633,73.296 568.659,71.625 569.425,69.651 C570.167,67.743 570.674,65.562 570.82,62.369 C570.966,59.17 571,58.147 571,50 C571,41.851 570.966,40.831 570.82,37.631&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/div&gt;&lt;div style=&#34;padding-top: 8px;&#34;&gt; &lt;div style=&#34; color:#3897f0; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:550; line-height:18px;&#34;&gt;View this post on Instagram&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;padding: 12.5% 0;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: row; margin-bottom: 14px; align-items: center;&#34;&gt;&lt;div&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(0px) translateY(7px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34;background-color: #F4F4F4; height: 12.5px; transform: rotate(-45deg) translateX(3px) translateY(1px); width: 12.5px; flex-grow: 0; margin-right: 14px; margin-left: 2px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(9px) translateY(-18px);&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;margin-left: 8px;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 20px; width: 20px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; width: 0; height: 0; border-top: 2px solid transparent; border-left: 6px solid #f4f4f4; border-bottom: 2px solid transparent; transform: translateX(16px) translateY(-4px) rotate(30deg)&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;margin-left: auto;&#34;&gt; &lt;div style=&#34; width: 0px; border-top: 8px solid #F4F4F4; border-right: 8px solid transparent; transform: translateY(16px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; flex-grow: 0; height: 12px; width: 16px; transform: translateY(-4px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34; width: 0; height: 0; border-top: 8px solid #F4F4F4; border-left: 8px solid transparent; transform: translateY(-4px) translateX(8px);&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: column; flex-grow: 1; justify-content: center; margin-bottom: 24px;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 224px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 144px;&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;p style=&#34; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;&#34;&gt;&lt;a href=&#34;https://www.instagram.com/reel/C-L49Q1SZ3E/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; style=&#34; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;&#34; target=&#34;_blank&#34;&gt;A post shared by Dustin Portzline (@dustinportzline)&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://www.instagram.com/embed.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;What I love about climbing is how it makes my mind visible. I knew what had rattled me: I slipped near the start, and I was surprised by the second crux. I knew what I had to do, and I knew whether I was hesitating or committing to do the move. In the rest of my life there&amp;rsquo;s so much &amp;hellip; cloudiness. What must I do or say that I&amp;rsquo;m so afraid of, I don&amp;rsquo;t even let myself see it? How am I hesitating right now?&lt;/p&gt;
&lt;p&gt;What about you, is there something you should do but you&amp;rsquo;re hesitating? Are you admitting to yourself that you must do this thing, or are you trying to ignore it?&lt;/p&gt;
&lt;p&gt;Sometimes the crux comes at us visibly, like Nansen with a cleaver. It says, &amp;ldquo;Say a word!&amp;rdquo; Sometimes it&amp;rsquo;s easier to ignore. Either way, if we stand frozen and mute like Nansen&amp;rsquo;s monks, the cat dies. A mentor or a teacher is crucial in that moment. Because I trust Dustin&amp;rsquo;s wisdom, when he shouted up &amp;ldquo;increase the tempo!&amp;rdquo; it broke through my hesitation and I did the move with my whole being. Zen teachers in other areas of my life have given me the courage to unfreeze myself. What&amp;rsquo;s your crux right now? Who can help you unfreeze yourself?&lt;/p&gt;
&lt;hr&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/are-you-prepared-to-save-a-life/joshu.png&#34; alt=&#34;Ink drawing of an old monk with deep lines around his eyes.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Zhaozhou_Congshen#/media/File:Zhaozhou_Congshen-Fozu_zhengzong_daoying37.jpg&#34;&gt;Joshu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;When Nansen killed the cat, his wisest student Joshu was away, traveling. He returned that afternoon. He shook the snow from his wool cape and hung it up at the monastery entrance. He kicked off his heavy shoes and put on his straw sandals, and went in to say hello to Nansen. Nansen told him what had happened that morning. Joshu took off his sandals, put them on his head, and walked out. Nansen called after him, &amp;ldquo;If you&amp;rsquo;d been there you could&amp;rsquo;ve saved the cat!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s it mean, sandals on the head? It was 1200 years ago and no one&amp;rsquo;s sure anymore. Maybe sandals on the head symbolizes death: people were buried barefoot and maybe their sandals were placed above their heads in the grave? Or maybe people put their sandals on their heads at funerals, or maybe that interpretation is just a garbled version of the first interpretation? Either way, Nansen tells Joshu a story about death, and Joshu&amp;rsquo;s gesture is just an acknowledgment. &amp;ldquo;Yes. Death. Impermanence. Nothing lasts.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t have to know what Joshu&amp;rsquo;s gesture meant exactly. The point is, he acted. He didn&amp;rsquo;t hesitate, he responded. If he&amp;rsquo;d been in the kitchen that morning, when all the other monks froze, Joshu&amp;rsquo;s gesture would have been enough to save the cat.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/are-you-prepared-to-save-a-life/465784528_c46d718351_o.jpg&#34; alt=&#34;Four young monks sit on the ground and eat lunch and feed scraps to a small cat.&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.flickr.com/photos/emptysquare/465784528/&#34;&gt;Monastery in Kalimpong, West Bengal&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The worst climbing accident I&amp;rsquo;ve seen was a couple months ago. I had started leading an easy climb at a popular area, there were a few other climbers on routes nearby. I was about 25 feet up, when I heard the obvious sound of a climber falling near me: the jangling of his gear and the thud of his body hitting the rock. Someone screamed, and someone else shouted, &amp;ldquo;Call 911!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I was not out of danger myself, so I deliberately did not look to the side, I didn&amp;rsquo;t want to see the accident until I was safe. I looked down, I saw that I could safely climb the 25 feet down to the base, so I did that, pulling my protection out of the cracks as I went until I was standing next to my belayer on the ground.&lt;/p&gt;
&lt;p&gt;Then I looked over, about 50 feet to my right there was a climber lying on a ledge, a few body-lengths above the ground. I learned later that he&amp;rsquo;d been leading &lt;a href=&#34;https://www.mountainproject.com/route/105818020/beatle-brow-bulge&#34;&gt;a hard, overhanging climb&lt;/a&gt;, he fell, and two or three cams that he placed popped out of the rock so he fell almost all the way back to the ground. He tumbled, hit his head, his helmet broke off, he hit his head &lt;strong&gt;again&lt;/strong&gt;, and came to a stop on this ledge where he was lying unconscious. By the time I looked there was already a climber who had reached him and was checking his pulse and trying to talk to him.&lt;/p&gt;
&lt;p&gt;I coiled my rope and moved my pack out of the way and waited for rangers to arrive. I wanted to be helpful. I was curious, too. How badly was the climber hurt? How was the rescue going to go? What more could I learn about the accident?&lt;/p&gt;
&lt;p&gt;Just minutes after the accident a few rangers showed up. One of them, a young slim guy, took charge. He was brisk and friendly, almost cheerful. He was enjoying moving fast and putting his skills to work, enjoying his mastery. He looked up at the climber, asked the other people at the cliff base: what&amp;rsquo;s his name? Morgan. What happened? He fell and hit his head. All these ropes hanging from the wall right now, what&amp;rsquo;s this attached to? What&amp;rsquo;s that attached to? This ranger, this Joshu, he figured out what he needed to do, what resources were available, and made a plan. By this time a dozen climbers had gathered at the base, so he gave us all jobs. After a couple minutes he had three belayers managing three ropes, he&amp;rsquo;d tied himself to one of the ropes and climbed up to join Morgan. Morgan was somewhat conscious now, and Joshu introduced himself. He didn&amp;rsquo;t say, &amp;ldquo;I&amp;rsquo;m so sorry this happened to you,&amp;rdquo; he didn&amp;rsquo;t say, &amp;ldquo;Oh my god look at all this blood.&amp;rdquo; Two other rangers arrived with a stretcher. Joshu and another climber lowered Morgan from the ledge down into the stretcher, with the three belayers all working in sync, with Joshu instructing them all.&lt;/p&gt;
&lt;p&gt;Once Morgan was in the stretcher on the ground, we needed to carry him down to the road where the ranger&amp;rsquo;s pickup truck was waiting. Joshu told us not to just pick up the stretcher and walk down to the road with it, because the ground was rocky and slippery with leaves, we could stumble and drop Morgan. Joshu said, &amp;ldquo;We&amp;rsquo;re going to do the caterpillar thing.&amp;rdquo; As if everyone was familiar with this technique and everyone knows it&amp;rsquo;s called the caterpillar. We lined up in two rows and picked up the stretcher, then passed it to the next set of people downhill. Then the uphill people ran around behind the backs of the people holding the stretcher, so they became the new downhill people, and the uphill people passed the stretcher down to them. It was fun, like a bucket brigade or school game. Whenever I was holding the stretcher I looked down at Morgan&amp;rsquo;s face. He was bloody and it seemed like his nose was broken, and he was going in and out of consciousness, but he was alive and talking.&lt;/p&gt;
&lt;p&gt;The caterpillar reached the road and we loaded Morgan into the ranger truck, and off he went. I think the truck brought him to a helicopter, which brought him to a hospital. I&amp;rsquo;ve heard he was still conscious when he reached the hospital, so I assume he&amp;rsquo;s ok.&lt;/p&gt;
&lt;p&gt;I noticed about myself how unfazed I was. Sure, it&amp;rsquo;s the worst accident I&amp;rsquo;ve seen, but I wasn&amp;rsquo;t surprised about anything. I know that this is exactly the kind of accident trad climbers have. It was a reminder how important it is to place pieces of protection correctly, so they&amp;rsquo;ll hold me when I fall, but I already knew that. I wasn&amp;rsquo;t upset, I was just &lt;strong&gt;curious&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a coldness to me that I&amp;rsquo;ve long disliked about myself. I&amp;rsquo;m not very sympathetic to strangers, I don&amp;rsquo;t usually cry at movies. But after this experience I&amp;rsquo;ve decided it&amp;rsquo;s ok—I still &lt;strong&gt;act&lt;/strong&gt; helpfully. In fact, my coldness can be an asset, it seems to make me good in emergencies.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In contrast to Joshu and the other climbers on the scene, there was one young woman who had &lt;em&gt;completely&lt;/em&gt; lost it. I think she and Morgan were both part of a group of climbers who&amp;rsquo;d come up from the city for the day. She was walking around the base of the cliff sobbing. She was crying so hard that two of her friends had to help her walk to a flat spot, out of the way, where she could sit down and cry while they held her. The whole time we were rescuing Morgan, 20 or 30 minutes, she was off to the side crying with her two friends.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know her, I don&amp;rsquo;t know why the accident shocked her in a way it didn&amp;rsquo;t shock me. Maybe she was very close to Morgan, maybe it was her first time climbing outside and she didn&amp;rsquo;t know how dangerous it is, wasn&amp;rsquo;t prepared for someone to get hurt. I don&amp;rsquo;t blame her. But she couldn&amp;rsquo;t save the cat.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to keep climbing for many years and I&amp;rsquo;ll see more accidents. I&amp;rsquo;m taking some classes soon, like Wilderness First Aid, so I can be more helpful when that happens. If someone I know gets hurt, I won&amp;rsquo;t be so calm, but I think I can hold it together. I&amp;rsquo;m more confident about that now.&lt;/p&gt;
&lt;p&gt;When you read about Zen it&amp;rsquo;s easy to misunderstand the message. We can think, Zen practice has no goal. The Heart Sutra says, &amp;ldquo;no path, no wisdom, no gain&amp;rdquo;. Huineng said, &amp;ldquo;there&amp;rsquo;s no mirror to polish and no dust to fall on it.&amp;rdquo; Dogen says, &amp;ldquo;we practice because we&amp;rsquo;re already enlightened.&amp;rdquo; But practice has an effect. You know that—it has an effect on you right now, and over the decades of your practice it will transform you. It&amp;rsquo;s crucial to ask yourself, how are you training yourself? How are you changing?&lt;/p&gt;
&lt;p&gt;When Nansen comes with his cleaver, you&amp;rsquo;ll only have a moment to respond. Will you freeze, or will you save the cat?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/are-you-prepared-to-save-a-life/366669755_c1c19c414a_o.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;</description>
    </item>
    
    <item>
      <title>Review: Amazon MemoryDB: A Fast and Durable Memory-First Cloud Database</title>
      <link>https://emptysqua.re/blog/review-memorydb/</link>
      <pubDate>Mon, 26 Aug 2024 08:25:42 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-memorydb/</guid>
      
      
      
      
      <description>&lt;p&gt;&lt;a href=&#34;https://assets.amazon.science/e0/1b/ba6c28034babbc1b18f54aa8102e/amazon-memorydb-a-fast-and-durable-memory-first-cloud-database.pdf&#34;&gt;Amazon MemoryDB: A Fast and Durable Memory-First Cloud Database&lt;/a&gt;, by six AWS engineers, in SIGMOD 2024. AWS hacked up Redis (I mean this respectfully) to produce a Redis-compatible database-as-a-service called MemoryDB, with better durability and consistency. Here&amp;rsquo;s a video of my presentation to &lt;a href=&#34;https://charap.co/summer-2024-reading-group-papers-papers-171-180/&#34;&gt;the DistSys Reading Group&lt;/a&gt;, and a written review of the paper below.&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/jFpm1140zdA?si=QW_xNuJqH-vczCSf&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;h1&gt;The Problem&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/redis-logo.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 250px;&#34;&gt;
&lt;p&gt;It’s me, hi, I’m the problem, it’s me.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Redis is popular because it&amp;rsquo;s fast and supports fairly powerful data structures, which makes some kinds of applications much easier to build. But Redis has basically no durability or consistency guarantees. So Amazon wants to sell a better Redis.&lt;/p&gt;
&lt;p&gt;Digression: there&amp;rsquo;s been &lt;a href=&#34;https://redis.io/blog/redis-adopts-dual-source-available-licensing/&#34;&gt;some license drama&lt;/a&gt;. Redis&amp;rsquo;s owner, Redis Labs, changed the license from open source to source-available. &lt;a href=&#34;https://www.mongodb.com/legal/licensing/server-side-public-license/faq&#34;&gt;My company made a similar move&lt;/a&gt; and I think it&amp;rsquo;s justified. There&amp;rsquo;s a fork of open source Redis &lt;a href=&#34;https://valkey.io/&#34;&gt;called Valkey now&lt;/a&gt;, and it has a new logo:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/valkey-logo.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 250px;&#34;&gt;
&lt;p&gt;I have this thing where I get older but just never wiser.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Valkey sounds like a Norse warrior woman to me. I think their logo should look like this:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/valkyrie.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 250px;&#34;&gt;
&lt;p&gt;I’m a monster on the hill.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;However, &lt;a href=&#34;https://aws.amazon.com/memorydb/&#34;&gt;AWS marketing says&lt;/a&gt; MemoryDB is &amp;ldquo;OSS Redis-compatible&amp;rdquo;, and they don&amp;rsquo;t mention Valkey, I don&amp;rsquo;t know how this will play out long term. Will Amazon contribute to Valkey? Or will proprietary Redis, Valkey, and AWS&amp;rsquo;s version of Redis drift apart forever?&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/astronauts.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;One day I’ll watch as you’re leaving.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Anyway. Amazon wants to sell a better Redis, with stronger durability and consistency. How are they going to do it?&lt;/p&gt;
&lt;h1&gt;The Solution&lt;/h1&gt;

&lt;p&gt;Guaranteeing durability and consistency in a distributed database is always complex. In this paper, the authors&amp;rsquo; solution is to make a black box, call it &amp;ldquo;the transaction log&amp;rdquo;, put all the complexity inside, and close the box.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-memorydb/black-box.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-memorydb/black-box.excalidraw.png&#34;&gt;
&lt;/picture&gt;
&lt;p&gt;I should not be left to my own devices.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The authors don&amp;rsquo;t describe the transaction log internals; we don&amp;rsquo;t know how it provides the guarantees on which MemoryDB relies. Presumably that&amp;rsquo;ll be a future paper, or maybe we can just infer it&amp;rsquo;s running Raft or Paxos. This is frustrating for me because I&amp;rsquo;m here for the distributed systems, but this isn&amp;rsquo;t a distributed systems paper—it&amp;rsquo;s mostly a software engineering paper. This paper is about how Amazon decomposed Redis into two parts: 1) the transaction log, and 2) everything else. They replaced the log with something better, put the parts back together, and created MemoryDB.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/frankenstein.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Too big to hang out, slowly lurching toward your favorite city.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h1&gt;MemoryDB Architecture&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/memorydb-architecture.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 400px;&#34;&gt;
&lt;p&gt;Figure 1 from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the MemoryDB architecture. Clients send writes to the primary. In vanilla Redis, the primary streams operations &lt;strong&gt;asynchronously&lt;/strong&gt; to secondaries (aka &amp;ldquo;replicas&amp;rdquo;). The MemoryDB primary saves writes to the transaction log &lt;strong&gt;synchronously&lt;/strong&gt;. How did the authors make the change from async to sync without rewriting Redis internals? There&amp;rsquo;s a doohickey called a Tracker which knows when writes become durable, delays acks until then, and blocks reads on dirty data. This provides linearizability on the primary. My impression is it lives outside the Redis core code, and it intercepts requests and replies.&lt;/p&gt;
&lt;p&gt;Secondaries receive entries from the transaction log only after they are multi-AZ-durable. A client can opt in to read from one replica and get sequential consistency, or multiple replicas and get eventual consistency. In both cases, MemoryDB secondaries have better consistency than Redis secondaries, since the former don&amp;rsquo;t observe writes that can be rolled back.&lt;/p&gt;
&lt;h1&gt;Off-Box Snapshotting&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/off-box-snapshotting.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 400px;&#34;&gt;
&lt;p&gt;Figure 2 from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Snapshots are useful for initializing new followers or disaster recovery. Normal Redis does snapshotting by forking the main process. One child keeps processing transactions (taking advantage of copy-on-write) while the other child creates the snapshot from its read-only copy of the data. These two children are competing for RAM and CPU, though, so the machine has to be overprovisioned to make headroom for occasional snapshotting.&lt;/p&gt;
&lt;p&gt;With MemoryDB, when Amazon wants to take a snapshot it does it &amp;ldquo;off-box&amp;rdquo;. They start up a new Redis machine, restore it from the last snapshot in S3, replay the subsequent transactions, save a new snapshot to S3, and terminate the Redis machine. The customer workload proceeds without interference.&lt;/p&gt;
&lt;h1&gt;Advantages of a Disaggregated Log&lt;/h1&gt;

&lt;p&gt;MemoryDB is much like &lt;a href=&#34;https://www.amazon.science/publications/amazon-aurora-on-avoiding-distributed-consensus-for-i-os-commits-and-membership-changes&#34;&gt;Aurora&lt;/a&gt;: it keeps the open source execution layer at the top, ensuring compatibility and avoiding reimplementation, but replaces the transaction log at the bottom with a proprietary service that&amp;rsquo;s more scalable and durable. The authors claim their mysterious transaction log service guarantees 11 9s of durability. That&amp;rsquo;s more 9s than you can shake a stick at!&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/shake-a-stick.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 400px;&#34;&gt;
&lt;p&gt;Tale old as time.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Once the log is separated from the execution layer, you can scale durability separately from availability. For example, a single-primary-only deployment is low-availability but high-durability. If the primary dies, you may wait a while for a new primary to be initialized from the last snapshot, but you won&amp;rsquo;t lose data.&lt;/p&gt;
&lt;p&gt;Some other advantages seem Redis-specific. Since Redis is single-threaded, it&amp;rsquo;s crucial to offload as much work from the primary as possible. A vanilla Redis primary must work to fan-out log entries to its secondaries, but a MemoryDB primary writes each entry only to the transaction log, which handles fan-out. Furthermore, Redis elections provide few guarantees: a newly elected primary might be missing recent writes, and there can be multiple primaries. MemoryDB uses the log&amp;rsquo;s strong consistency as part of its election protocol to guarantee a single primary with all the previous primary&amp;rsquo;s writes.&lt;/p&gt;
&lt;h1&gt;Scaling&lt;/h1&gt;

&lt;p&gt;MemoryDB, like Redis, is both sharded and replicated. Amazon can scale MemoryDB nodes up or down, in or out, in three dimensions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The number of replicas per shard.&lt;/li&gt;
&lt;li&gt;Vertical scaling (using more or less powerful instances).&lt;/li&gt;
&lt;li&gt;Horizontal scaling (adding or removing shards).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Number of replicas&lt;/strong&gt;: More replicas permit read scaling (with weak consistency) and higher availability (there are more hot standbys). To add a replica, AWS restores one from the last S3 snapshot, then replays the transaction log.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vertical scaling&lt;/strong&gt;: AWS replaces each secondary with a new one using a different instance size, then hands over leadership and deletes the old primary. The paper says MemoryDB has &amp;ldquo;a collaborative leadership transfer, where the old instance actively hands over leadership, which minimizes downtime&amp;rdquo;. There are no details, but MongoDB and some other systems have similar ideas. I can imagine that the old primary ensures the new one is nearly caught-up in the transaction log before it starts the handover, then it stops accepting writes, relinquishes its lease, and tells the new primary to run for election immediately instead of waiting for a timeout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Horizontal scaling&lt;/strong&gt;: Redis supports sharding using something like consistent hashing. There are 16,384 slots. Each slot has a subset of the keys, assigned according to a hash of the key. Each shard is a replica set, and each shard owns a subset of the slots. Perhaps open source Redis can change the number of shards and reassign slots, the paper doesn&amp;rsquo;t cover this. In MemoryDB, slots are reassigned in the way you&amp;rsquo;d expect: the old owner streams data and data-changes to the new owner until they&amp;rsquo;re almost in sync, then the old owner stops accepting writes, streams the final updates to the new owner, and they both commit a change to the sharding metadata to transfer ownership.&lt;/p&gt;
&lt;p&gt;Presumably the transaction log service can also scale, but the log service is a black box so we don&amp;rsquo;t know.&lt;/p&gt;
&lt;h1&gt;Upgrading Versions&lt;/h1&gt;

&lt;p&gt;This part of the paper was clever and novel to me. MemoryDB upgrades the software version of a replica set thus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For any v1, v2: v2 can read v1&amp;rsquo;s log entries, but maybe not vice-versa.&lt;/li&gt;
&lt;li&gt;Each log entry is marked with the version of the primary that created it.&lt;/li&gt;
&lt;li&gt;Secondaries upgrade to v2 first.&lt;/li&gt;
&lt;li&gt;If there&amp;rsquo;s a failover during upgrade and a v2 node becomes a primary, v1 nodes don&amp;rsquo;t replicate its entries.&lt;/li&gt;
&lt;li&gt;Snapshots are built with v1 until all nodes are v2, so snapshots are legible if the upgrade is aborted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is simpler than &lt;a href=&#34;https://www.mongodb.com/docs/manual/reference/command/setFeatureCompatibilityVersion/&#34;&gt;MongoDB&amp;rsquo;s solution&lt;/a&gt;. It also doesn&amp;rsquo;t solve all the problems MongoDB solves, but still—I admire it.&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-memorydb/throughput.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;For a &lt;strong&gt;read-only&lt;/strong&gt; workload, MemoryDB is about as fast as Redis on small instances. There&amp;rsquo;s a step change when the instance size is upgraded to 2xl, then no more benefit. The authors say this is because of &amp;ldquo;Enhanced IO Multiplexing&amp;rdquo;. I think this is because Redis is single-threaded, but Enhanced IO Multiplexing lets some IO work be offloaded to other threads. Apparently that removes a bottleneck when they upgrade to 2xl, but then they hit some other bottleneck and bigger instances don&amp;rsquo;t help.&lt;/p&gt;
&lt;p&gt;For a &lt;strong&gt;write-only&lt;/strong&gt; workload Redis writes faster than MemoryDB, but there&amp;rsquo;s no durability guarantee. It&amp;rsquo;s interesting that, even though Redis is single-threaded, it can still get a performance boost from a bigger instance. Bigger instances have the same CPUs, just more of them, which I think is useless for Redis. But bigger instances also have more network bandwidth, maybe that helps.&lt;/p&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;This is a solid industrial-track paper about pragmatic software engineering for a distributed DB. It&amp;rsquo;s frustrating for distributed algorithms buffs, but we&amp;rsquo;re not its audience. For us, I wish Amazon had published a paper about the log first, or included some log details in this paper. Nevertheless I like it. The &lt;a href=&#34;#upgrading-versions&#34;&gt;upgrade protocol&lt;/a&gt; is wisely designed.&lt;/p&gt;
&lt;h1&gt;Further reading&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://muratbuffalo.blogspot.com/2024/05/amazon-memorydb-fast-and-durable-memory.html&#34;&gt;Murat Demirbas&amp;rsquo;s summary&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://brooker.co.za/blog/2024/04/25/memorydb.html&#34;&gt;Marc Brooker&amp;rsquo;s blog post&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>What&#39;s Next for Women in Tech?</title>
      <link>https://emptysqua.re/blog/whats-next-for-women-in-tech/</link>
      <pubDate>Sat, 24 Aug 2024 16:53:51 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/whats-next-for-women-in-tech/</guid>
      
      
      
      
      <description>&lt;p&gt;The Washington Post says &amp;ldquo;&lt;a href=&#34;https://www.washingtonpost.com/technology/2024/08/19/silicon-valley-dei-backlash/&#34;&gt;the movement to diversify Silicon Valley is crumbling&lt;/a&gt;&amp;rdquo;, and the story is particularly dire for gender-diversity nonprofits: Girls In Tech has dissolved, so has Women Who Code, and software companies have sharply cut their grants to others. But were they helping? The article links to this &lt;a href=&#34;https://www.dol.gov/agencies/wb/data/occupations-stem&#34;&gt;Department of Labor chart&lt;/a&gt; which shows women&amp;rsquo;s share of all STEM employment has grown from 25% in 2000 to only 26% in 2022. For some reason the Post didn&amp;rsquo;t cite a worse statistic on the same chart: women&amp;rsquo;s share of &amp;ldquo;computer occupations&amp;rdquo; has fallen from 30% to 24%.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/whats-next-for-women-in-tech/labor-chart.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Chart from Department of Labor, titled &amp;ldquo;Percentage of science, technology, engineering, and math (STEM) workers who are women&amp;rdquo;&#34; title=&#34;&#34;&gt;&lt;p&gt;I don&amp;rsquo;t know if organizations like Women Who Code fundamentally made a difference or not. I&amp;rsquo;ve partnered with a similar group called &lt;a href=&#34;https://pyladies.com/&#34;&gt;PyLadies&lt;/a&gt; on &lt;a href=&#34;https://emptysqua.re/blog/global-diversity-cfp-day-workshop/&#34;&gt;mentoring new conference speakers&lt;/a&gt;, and it seems like they make the tech scene more welcoming for women who are trying to break in. They also offer a space for experienced women to coordinate and support each other, perhaps this keeps mid-career women from &lt;a href=&#34;https://www.techtalentcharter.co.uk/why-women-leave-tech-jobs&#34;&gt;leaving the industry&lt;/a&gt;. PyLadies is still here and stronger than ever.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Like most male programmers I was aware of the gender imbalance since I went to college, and awoke to the urgency of the problem when &lt;a href=&#34;https://medium.com/@triketora/where-are-the-numbers-cb997a57252&#34;&gt;Tracy Chou&lt;/a&gt;, &lt;a href=&#34;https://www.nbcnews.com/tech/social-media/jesse-jackson-presses-facebook-techs-diversity-problem-n112106&#34;&gt;Jesse Jackson&lt;/a&gt;, and others demanded that the big software companies publish their diversity stats. &lt;a href=&#34;https://en.wikipedia.org/wiki/Ellen_Pao&#34;&gt;Ellen Pao&lt;/a&gt;&amp;rsquo;s activism made an impression on me too. In 2016 some women engineers at MongoDB, where I work, called a big meeting to discuss improving diversity and inclusion there. As I recall, the meeting was stressful and exciting—it was a big-bang for diversity efforts at MongoDB.&lt;/p&gt;
&lt;p&gt;I teamed up with a couple of colleagues in the engineering department and started shopping for diversity workshops. We tried an &lt;a href=&#34;https://frameshiftconsulting.com/ally-skills-workshop/&#34;&gt;Ally Skills workshop&lt;/a&gt; (which I&amp;rsquo;d recommend to anyone) and researched some other classes, but our then-CTO Eliot Horowitz wasn&amp;rsquo;t convinced we should bring any of these trainings to the company. He demanded evidence that these things were effective. I was frustrated at the time, and grateful in retrospect. I think Eliot was right. That year I read Iris Bohnet&amp;rsquo;s book &lt;a href=&#34;https://www.hup.harvard.edu/file/feeds/PDF/9780674986565_sample.pdf&#34;&gt;What Works: Gender Equality by Design&lt;/a&gt;, which was very critical of diversity trainings. They &lt;a href=&#34;https://hbr.org/2016/07/why-diversity-programs-fail&#34;&gt;seem to be counterproductive&lt;/a&gt; as often as not. Bohnet reviewed the evidence and argued instead for redesigning corporate processes and structures to be more inclusive.&lt;/p&gt;
&lt;p&gt;My activism within MongoDB declined after 2016. Executives and HR took over and there seemed to be less appetite for white male engineers like me to lead the charge. This might be for the better: the managers had the power and perhaps the expertise I lacked. A &lt;a href=&#34;https://web.archive.org/web/20210518140426/https://www.atlassian.com/dam/jcr:c009637c-1335-429d-9181-6a66685b712e/Atlassian_StateofDiversityTech_2018_StatsSummary.pdf&#34;&gt;2018 survey&lt;/a&gt; matches my experience. It found that individual tech employees&amp;rsquo; participation in diversity and inclusion fell by half year-over-year. Meanwhile, MongoDB and other companies have changed for the better, but somehow those Department of Labor numbers haven&amp;rsquo;t improved.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;What makes a difference for women in the software industry? My female colleagues tell me it&amp;rsquo;s most helpful to have &lt;a href=&#34;https://www.mongodb.com/company/careers/diversity-and-inclusion&#34;&gt;an affinity group&lt;/a&gt; within the company where they can be candid with each other, and to have sympathetic managers who are open to hearing about problems with the company culture, and to have women in leadership.&lt;/p&gt;
&lt;p&gt;And what can men do? I&amp;rsquo;m certain that individual male programmers can choose to make a difference by mentoring junior women engineers. I&amp;rsquo;ve &lt;a href=&#34;https://emptysqua.re/blog/mentoring/#your-companys-future-our-industrys-future&#34;&gt;believed this for a long time&lt;/a&gt;, Iris Bohnet said so in &lt;em&gt;What Works&lt;/em&gt;, and the &lt;a href=&#34;https://hbr.org/2016/12/the-men-who-mentor-women&#34;&gt;Harvard Business Review said so back in 2016&lt;/a&gt;. Mentorship boosts early-career women in tech, and it mitigates &lt;a href=&#34;https://onezero.medium.com/ctrl-alt-delete-the-planned-obsolescence-of-old-coders-9c5f440ee68&#34;&gt;the loss of mid-career women&lt;/a&gt;. I asked one of my female colleagues yesterday what has been most helpful for her, and she said &amp;ldquo;individual mentorship&amp;rdquo; first, before I revealed that that&amp;rsquo;s what I think, too.&lt;/p&gt;
&lt;p&gt;My analytics tell me that you, dear reader, are probably a male programmer. If you&amp;rsquo;re an experienced engineer, you should mentor women. If you&amp;rsquo;re worried about appearances, &lt;a href=&#34;https://www.chronicle.com/article/why-male-mentors-in-the-metoo-era-must-engage-more-not-run-for-the-hills/&#34;&gt;just don&amp;rsquo;t be creepy&lt;/a&gt; and you&amp;rsquo;ll be fine. Mentorship includes advice, a sympathetic ear, advocacy, and cheerleading. Here&amp;rsquo;s &lt;a href=&#34;https://ncwit.org/resource/top10maleadvocate/&#34;&gt;10 ways to support women in tech&lt;/a&gt;, from the National Center for Women and Information Technology.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Coaching Will Be The Last Human Job</title>
      <link>https://emptysqua.re/blog/coaching-will-be-the-last-human-job/</link>
      <pubDate>Sat, 17 Aug 2024 14:46:40 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/coaching-will-be-the-last-human-job/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/coaching-will-be-the-last-human-job/520910820_886d736257_k.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;Macro photo of my eye, with the reflection of a lens in the pupil, making it look bionic.&#34; title=&#34;&#34;&gt;&lt;p&gt;Sam Altman and Arianna Huffington &lt;a href=&#34;https://time.com/6994739/ai-behavior-change-health-care/&#34;&gt;plan to make an AI health coach&lt;/a&gt;, which will address chronic illness by encouraging people to eat better, take their medications, and get more sleep and exercise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Consider what it&amp;rsquo;s like to be a busy professional with diabetes. You might be struggling to manage your blood-sugar levels, often missing meals and exercise due to a hectic schedule. A personalized AI health coach, trained on your medical data and daily routines, could provide timely reminders to take your medication, suggest quick and healthy meal options, and encourage you to take short breaks for exercise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A busy professional might benefit from an AI coach, but as &lt;a href=&#34;https://www.slowboring.com/p/can-an-ai-health-coach-fight-chronic&#34;&gt;Ben Krauss points out&lt;/a&gt;, it&amp;rsquo;s the working class who suffer most from chronic disease, and have the highest obstacles to a healthy lifestyle.&lt;/p&gt;
&lt;p&gt;I want to criticize a different aspect of this pitch:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Yes, behavior change is hard. But through hyper-personalization, it&amp;rsquo;s also something that AI is uniquely positioned to solve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Behavior change is something that a &lt;em&gt;human&lt;/em&gt; coach is uniquely positioned to solve. No matter how smart, a mechanical coach&amp;rsquo;s impact is limited by my ability to ignore it. I&amp;rsquo;ve set myself all sorts of digital reminders and nudges and I routinely tell them to piss off. My employer nags me with automated messages about various policies, I delete them reflexively. So long as I know my nagger isn&amp;rsquo;t human, I feel no obligation to respect it.&lt;/p&gt;
&lt;p&gt;I have at various times hired a personal trainer, a couples therapist, and a climbing mentor. In all cases, I granted my coach the authority to nudge me to do something. I had some long-term goal (fitness, an honest relationship, courageous climbing), but I was hampered by my short-term laziness or fear. I used my coach&amp;rsquo;s authority or charisma or whatever as extra oomph to overcome my reluctance. AI could one day match any coach&amp;rsquo;s expertise and personalization, but I doubt it will replace a human coach&amp;rsquo;s ability to exert &lt;em&gt;peer pressure&lt;/em&gt;. Only a human can do that, because only a human is my peer. AI will either be my servant or, if it isn&amp;rsquo;t, then AI will no longer care about sharing recipes with me, it will be too busy &lt;a href=&#34;https://en.wikipedia.org/wiki/Instrumental_convergence#Paperclip_maximizer&#34;&gt;converting me to paperclips&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a recent example of peer pressure. My climbing mentor &lt;a href=&#34;https://www.advancedrockcraft.com/&#34;&gt;Dustin Portzline&lt;/a&gt; was belaying and coaching me while I was scared on &lt;a href=&#34;https://www.mountainproject.com/route/105799143/mf&#34;&gt;a climb&lt;/a&gt;. I repeatedly approached the crux, hesitated, and climbed back down to a ledge. I couldn&amp;rsquo;t calm myself and I started to give up. I asked Dustin to pull the rope tight so I could hang from it—if he had, I would have ruined my chance to claim a clean ascent. Dustin said, &amp;ldquo;I can do that, but are you sure? Do you want to try one more time?&amp;rdquo; His calmness and encouragement helped me collect myself. I respect him and I wanted to do my best for him. I tried once more. As I approached the crux and hesitated again, Dustin yelled up, &amp;ldquo;Increase the tempo!&amp;rdquo; &amp;hellip;that&amp;rsquo;s what I remember. He says he yelled, &amp;ldquo;Move faster!&amp;rdquo; I was kind of blacked-out with fear, half-animal, but I stand by my recollection. Anyway, his instruction was the extra motivation I needed. I lunged for the next hold and latched it, and finished the climb.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s Dustin&amp;rsquo;s version of events:&lt;/p&gt;
&lt;blockquote class=&#34;instagram-media&#34; data-instgrm-captioned data-instgrm-permalink=&#34;https://www.instagram.com/reel/C-L49Q1SZ3E/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; data-instgrm-version=&#34;14&#34; style=&#34; background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&#34;&gt;&lt;div style=&#34;padding:16px;&#34;&gt; &lt;a href=&#34;https://www.instagram.com/reel/C-L49Q1SZ3E/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; style=&#34; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%;&#34; target=&#34;_blank&#34;&gt; &lt;div style=&#34; display: flex; flex-direction: row; align-items: center;&#34;&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 40px; margin-right: 14px; width: 40px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: column; flex-grow: 1; justify-content: center;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 100px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 60px;&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;padding: 19% 0;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display:block; height:50px; margin:0 auto 12px; width:50px;&#34;&gt;
    &lt;svg width=&#34;50px&#34; height=&#34;50px&#34; viewBox=&#34;0 0 60 60&#34; version=&#34;1.1&#34;
         xmlns=&#34;https://www.w3.org/2000/svg&#34;&gt;&lt;g stroke=&#34;none&#34; stroke-width=&#34;1&#34; fill=&#34;none&#34; fill-rule=&#34;evenodd&#34;&gt;&lt;g transform=&#34;translate(-511.000000, -20.000000)&#34; fill=&#34;#000000&#34;&gt;&lt;g&gt;&lt;path d=&#34;M556.869,30.41 C554.814,30.41 553.148,32.076 553.148,34.131 C553.148,36.186 554.814,37.852 556.869,37.852 C558.924,37.852 560.59,36.186 560.59,34.131 C560.59,32.076 558.924,30.41 556.869,30.41 M541,60.657 C535.114,60.657 530.342,55.887 530.342,50 C530.342,44.114 535.114,39.342 541,39.342 C546.887,39.342 551.658,44.114 551.658,50 C551.658,55.887 546.887,60.657 541,60.657 M541,33.886 C532.1,33.886 524.886,41.1 524.886,50 C524.886,58.899 532.1,66.113 541,66.113 C549.9,66.113 557.115,58.899 557.115,50 C557.115,41.1 549.9,33.886 541,33.886 M565.378,62.101 C565.244,65.022 564.756,66.606 564.346,67.663 C563.803,69.06 563.154,70.057 562.106,71.106 C561.058,72.155 560.06,72.803 558.662,73.347 C557.607,73.757 556.021,74.244 553.102,74.378 C549.944,74.521 548.997,74.552 541,74.552 C533.003,74.552 532.056,74.521 528.898,74.378 C525.979,74.244 524.393,73.757 523.338,73.347 C521.94,72.803 520.942,72.155 519.894,71.106 C518.846,70.057 518.197,69.06 517.654,67.663 C517.244,66.606 516.755,65.022 516.623,62.101 C516.479,58.943 516.448,57.996 516.448,50 C516.448,42.003 516.479,41.056 516.623,37.899 C516.755,34.978 517.244,33.391 517.654,32.338 C518.197,30.938 518.846,29.942 519.894,28.894 C520.942,27.846 521.94,27.196 523.338,26.654 C524.393,26.244 525.979,25.756 528.898,25.623 C532.057,25.479 533.004,25.448 541,25.448 C548.997,25.448 549.943,25.479 553.102,25.623 C556.021,25.756 557.607,26.244 558.662,26.654 C560.06,27.196 561.058,27.846 562.106,28.894 C563.154,29.942 563.803,30.938 564.346,32.338 C564.756,33.391 565.244,34.978 565.378,37.899 C565.522,41.056 565.552,42.003 565.552,50 C565.552,57.996 565.522,58.943 565.378,62.101 M570.82,37.631 C570.674,34.438 570.167,32.258 569.425,30.349 C568.659,28.377 567.633,26.702 565.965,25.035 C564.297,23.368 562.623,22.342 560.652,21.575 C558.743,20.834 556.562,20.326 553.369,20.18 C550.169,20.033 549.148,20 541,20 C532.853,20 531.831,20.033 528.631,20.18 C525.438,20.326 523.257,20.834 521.349,21.575 C519.376,22.342 517.703,23.368 516.035,25.035 C514.368,26.702 513.342,28.377 512.574,30.349 C511.834,32.258 511.326,34.438 511.181,37.631 C511.035,40.831 511,41.851 511,50 C511,58.147 511.035,59.17 511.181,62.369 C511.326,65.562 511.834,67.743 512.574,69.651 C513.342,71.625 514.368,73.296 516.035,74.965 C517.703,76.634 519.376,77.658 521.349,78.425 C523.257,79.167 525.438,79.673 528.631,79.82 C531.831,79.965 532.853,80.001 541,80.001 C549.148,80.001 550.169,79.965 553.369,79.82 C556.562,79.673 558.743,79.167 560.652,78.425 C562.623,77.658 564.297,76.634 565.965,74.965 C567.633,73.296 568.659,71.625 569.425,69.651 C570.167,67.743 570.674,65.562 570.82,62.369 C570.966,59.17 571,58.147 571,50 C571,41.851 570.966,40.831 570.82,37.631&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/div&gt;&lt;div style=&#34;padding-top: 8px;&#34;&gt; &lt;div style=&#34; color:#3897f0; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:550; line-height:18px;&#34;&gt;View this post on Instagram&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;padding: 12.5% 0;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: row; margin-bottom: 14px; align-items: center;&#34;&gt;&lt;div&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(0px) translateY(7px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34;background-color: #F4F4F4; height: 12.5px; transform: rotate(-45deg) translateX(3px) translateY(1px); width: 12.5px; flex-grow: 0; margin-right: 14px; margin-left: 2px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(9px) translateY(-18px);&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;margin-left: 8px;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 20px; width: 20px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; width: 0; height: 0; border-top: 2px solid transparent; border-left: 6px solid #f4f4f4; border-bottom: 2px solid transparent; transform: translateX(16px) translateY(-4px) rotate(30deg)&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&#34;margin-left: auto;&#34;&gt; &lt;div style=&#34; width: 0px; border-top: 8px solid #F4F4F4; border-right: 8px solid transparent; transform: translateY(16px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; flex-grow: 0; height: 12px; width: 16px; transform: translateY(-4px);&#34;&gt;&lt;/div&gt; &lt;div style=&#34; width: 0; height: 0; border-top: 8px solid #F4F4F4; border-left: 8px solid transparent; transform: translateY(-4px) translateX(8px);&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;div style=&#34;display: flex; flex-direction: column; flex-grow: 1; justify-content: center; margin-bottom: 24px;&#34;&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 224px;&#34;&gt;&lt;/div&gt; &lt;div style=&#34; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 144px;&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;p style=&#34; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;&#34;&gt;&lt;a href=&#34;https://www.instagram.com/reel/C-L49Q1SZ3E/?utm_source=ig_embed&amp;amp;utm_campaign=loading&#34; style=&#34; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;&#34; target=&#34;_blank&#34;&gt;A post shared by Dustin Portzline (@dustinportzline)&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://www.instagram.com/embed.js&#34;&gt;&lt;/script&gt;
&lt;hr&gt;
&lt;p&gt;I experience from humans &lt;a href=&#34;https://en.wikipedia.org/wiki/The_Work_of_Art_in_the_Age_of_Mechanical_Reproduction&#34;&gt;an &amp;ldquo;aura&amp;rdquo; which shines from no mechanical reproduction&lt;/a&gt; of humanity. Being face-to-face with another human has the most powerful effect on us, but even a text message from a human demands our consideration in the way an automated text doesn&amp;rsquo;t. If I respect someone, then I care what they think of me and I don&amp;rsquo;t want to disappoint them; I&amp;rsquo;ll try harder. My personal trainer made me stronger partly by prescribing the right exercises, and partly by watching me and yelling, &amp;ldquo;C&amp;rsquo;mon! One more!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In fact the content of another person&amp;rsquo;s advice is less important than their &lt;em&gt;attention&lt;/em&gt;. Therapists sometimes offer insight, but they mostly listen as their patients discover themselves. &lt;a href=&#34;https://en.wikipedia.org/wiki/Hawthorne_effect&#34;&gt;Factory workers are more productive when a researcher dims the lights, or makes them brighter&lt;/a&gt;—the brightness doesn&amp;rsquo;t matter, the workers are more motivated because they know a researcher is paying attention to them.&lt;/p&gt;
&lt;p&gt;Of course, human coaches are very expensive, and &lt;a href=&#34;https://en.wikipedia.org/wiki/Baumol_effect&#34;&gt;Baumol&amp;rsquo;s cost disease&lt;/a&gt; makes them more so. If Altman and Huffington&amp;rsquo;s AI coach is cheaper than a human, then it&amp;rsquo;s worth a try, even though it won&amp;rsquo;t be as effective. But in the long term I expect Baumol&amp;rsquo;s cost disease to reverse: robots will do most of the work, and there will be a surplus of human labor looking for jobs. They will collect in the professions where the human aura is important or essential: therapists, stage actors, judges, nannies, masseuses, butlers, prostitutes, coaches.&lt;/p&gt;
&lt;p&gt;There may be AI-native generations in the future that respect machines as much as they respect people. The special aura of humanity will be gone for them. A machine&amp;rsquo;s attention, although divided among millions of users, will be as satisfying to them as the undivided attention of a human. Confessing their shame to a machine will be just as great a relief. A robot yelling &amp;ldquo;one more!&amp;rdquo; will be just as motivating. An AI judge&amp;rsquo;s decision will carry the same weight. Good luck to them, I can&amp;rsquo;t imagine their lives. For a while, though, I think that coaching will be among the last essentially human jobs.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Photo: &lt;a href=&#34;https://www.flickr.com/photos/emptysquare/520910820/&#34;&gt;my eye&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Summer Meditation Retreat</title>
      <link>https://emptysqua.re/blog/meditation-retreat-summer-2024/</link>
      <pubDate>Thu, 15 Aug 2024 22:15:35 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/meditation-retreat-summer-2024/</guid>
      
      
      
      
      <description>&lt;p&gt;August 2024. The &lt;a href=&#34;https://villagezendo.org/&#34;&gt;Village Zendo&lt;/a&gt;&amp;rsquo;s summer retreat. Walking meditation, 5am meditation by candlelight, and Zen student Seiryu&amp;rsquo;s first dharma talk.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/meditation-retreat-summer-2024/voice-of-wisdom-sesshin-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Lights Out Climbing</title>
      <link>https://emptysqua.re/blog/lights-out-climbing/</link>
      <pubDate>Fri, 28 Jun 2024 07:55:01 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/lights-out-climbing/</guid>
      
      
      
      
      <description>&lt;p&gt;June 26, 2023. &amp;ldquo;Pride Climb &amp;amp; Glow Night&amp;rdquo; at the Gravity Vault climbing gym in Poughkeepsie.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lights-out-climbing/lights-out-climbing-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lights-out-climbing/lights-out-climbing-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lights-out-climbing/lights-out-climbing-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lights-out-climbing/lights-out-climbing-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lights-out-climbing/lights-out-climbing-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>New Abbot Ceremony</title>
      <link>https://emptysqua.re/blog/shisanshiki/</link>
      <pubDate>Wed, 19 Jun 2024 10:20:55 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/shisanshiki/</guid>
      
      
      
      
      <description>&lt;p&gt;June 16, 2024. The &amp;ldquo;Descending and Ascending the Mountain Ceremony&amp;rdquo;, or Shisanshiki, at the Village Zendo in NYC. Our abbot Enkyo O&amp;rsquo;Hara Roshi retired and was succeeded by Shinryu Sinclair Roshi.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/shisanshiki/shisanshiki-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/shisanshiki/shisanshiki-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/shisanshiki/shisanshiki-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/shisanshiki/shisanshiki-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Climbing in Lost City</title>
      <link>https://emptysqua.re/blog/lost-city/</link>
      <pubDate>Sat, 15 Jun 2024 21:38:01 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/lost-city/</guid>
      
      
      
      
      <description>&lt;p&gt;June 15, 2024. Climbing in Lost City, an undocumented area of the Gunks. The local tradition prohibits publishing information about climbing routes here. The first image is of Dustin Portzline on &amp;ldquo;Keelhauled&amp;rdquo;, an ungraded roof-crack boulder problem. The others are on a nameless 5.9 route.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lost-city/lost-city-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lost-city/lost-city-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lost-city/lost-city-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lost-city/lost-city-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/lost-city/lost-city-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mohonk Trail Builders</title>
      <link>https://emptysqua.re/blog/mohonk-trail-builders/</link>
      <pubDate>Tue, 11 Jun 2024 15:15:14 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/mohonk-trail-builders/</guid>
      
      
      
      
      <description>&lt;p&gt;June 11, 2024. The Mohonk Trail Builders lifting big rocks to build a trail in the Mohonk Preserve.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-trail-builders/mohonk-trail-builders-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-trail-builders/mohonk-trail-builders-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-trail-builders/mohonk-trail-builders-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-trail-builders/mohonk-trail-builders-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/mohonk-trail-builders/mohonk-trail-builders-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Absurdland in the Gunks</title>
      <link>https://emptysqua.re/blog/gunks-absurdland/</link>
      <pubDate>Sun, 09 Jun 2024 17:04:01 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/gunks-absurdland/</guid>
      
      
      
      
      <description>&lt;p&gt;June 7, 2024. My friends climbing &lt;a href=&#34;https://www.mountainproject.com/route/105799190/absurdland&#34;&gt;Absurdland&lt;/a&gt; in the Gunks as a storm approaches.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-absurdland/2024-06-7-gunks-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-absurdland/2024-06-7-gunks-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-absurdland/2024-06-7-gunks-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-absurdland/2024-06-7-gunks-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/gunks-absurdland/2024-06-7-gunks-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Bonticou Crag</title>
      <link>https://emptysqua.re/blog/bonticou-crag/</link>
      <pubDate>Wed, 05 Jun 2024 14:37:06 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/bonticou-crag/</guid>
      
      
      
      
      <description>&lt;p&gt;June 3, 2024. A Gunks cliff at sunset. I did a quick hike to the cliff in the evening and scrambled up, then I alternated taking photos and eating a sandwich as the light changed. A couple of these are HDRs: I steadied my digital camera on some rocks and took three exposure-bracketed frames, then merged them in Lightroom. The blossoms are mountain laurel.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-crag/ajdavis_20240603_3461.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-crag/ajdavis_20240603_3510.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-crag/ajdavis_20240603_3536.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-crag/ajdavis_20240603_3558.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-crag/ajdavis_20240603_3738-HDR.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-crag/ajdavis_20240603_3751-HDR.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Review: Detock: High Performance Multi-region Transactions at Scale</title>
      <link>https://emptysqua.re/blog/review-detock/</link>
      <pubDate>Wed, 29 May 2024 09:18:59 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-detock/</guid>
      
      
      
      
      <description>&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/8DyJNou3pJM?si=KS5MhJxPCzSCSyC9&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href=&#34;https://dl.acm.org/doi/10.1145/3589293&#34;&gt;Detock: High Performance Multi-region Transactions at Scale&lt;/a&gt;, SIGMOD 2023. This paper is about &lt;a href=&#34;https://jepsen.io/consistency/models/strict-serializable&#34;&gt;strict serializable&lt;/a&gt; transactions in a geo-distributed database. It&amp;rsquo;s named &amp;ldquo;Detock&amp;rdquo; for &lt;u&gt;de&lt;/u&gt;terministic deadl&lt;u&gt;ock&lt;/u&gt; avoidance. My presentation to the &lt;a href=&#34;https://charap.co/spring-2024-reading-group-papers-161-170/&#34;&gt;Distributed Systems Reading Group&lt;/a&gt; is above, and my written summary is below.&lt;/p&gt;
&lt;hr&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-detock/john-calvin.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Calvin&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Detock&amp;rsquo;s goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strict-serializable, multi-region transactions.&lt;/li&gt;
&lt;li&gt;Low latency and high throughput under high contention.&lt;/li&gt;
&lt;li&gt;Handle contention between multi- and single-region transactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Detock descends from a lineage of &amp;ldquo;deterministic&amp;rdquo; databases invented by Daniel Abadi and others, starting with &lt;a href=&#34;https://dl.acm.org/doi/10.1145/2213836.2213838&#34;&gt;Calvin: Fast Distributed Transactions for Partitioned Database Systems&lt;/a&gt; in 2012. Calvin decides in advance how a sequence of transactions will execute, before the transactions fan out to the partitions. (It took me years to realize it was named for John Calvin, who taught that souls were predestined for heaven or hell.) Then there was &lt;a href=&#34;https://dl.acm.org/doi/10.14778/3342263.3342647&#34;&gt;SLOG: Serializable, Low-latency, Geo-replicated Transactions&lt;/a&gt; in 2019, which applies deterministic transactions to geo-replication. Detock has the same architecture and mostly the same code as SLOG, but it resolves deadlocks differently, as you&amp;rsquo;ll see.&lt;/p&gt;
&lt;h1&gt;Detock&amp;rsquo;s Architecture&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-detock/detock-architecture.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Detock is partitioned and distributed across geographic regions. A region can have multiple partitions. Each item in the datastore has one home region, which holds the authoritative fresh copy of that item. An item can be asynchronously replicated from its home region to other regions; the other regions can have a read-only cache of the item.&lt;/p&gt;
&lt;p&gt;A transaction can arrive at any partition; the partition becomes the transaction&amp;rsquo;s coordinator. Transactions are one-shot, which is a requirement of deterministic databases. We can&amp;rsquo;t do SQL-style conversational transactions. The read set and write set are either declared by the client, or can be determined with static analysis of the transaction&amp;rsquo;s code if it&amp;rsquo;s a procedure, or else the coordinator does a reconnaissance transaction to determine the read and write set. If the coordinator uses a reconnaissance transaction, and then the data changes such that the read and write set become invalid, that&amp;rsquo;s detected somehow and the transaction is retried.&lt;/p&gt;
&lt;p&gt;The coordinator uses a &amp;ldquo;home directory&amp;rdquo; to map data items to their home regions, annotates the read/write sets with their respective regions, and forwards the transaction to all participant regions.&lt;/p&gt;
&lt;h1&gt;Single-Home Transactions&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-detock/single-home.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;For a single-home transaction, the transaction is added to the log in its home region. These logs are stored in batches and written to disk every five milliseconds (batched to minimize disk I/O, I think).&lt;/p&gt;
&lt;p&gt;Single-home transactions are arranged in a graph according to their dependencies, and there won&amp;rsquo;t be any cycles. The paper explains why single-home transactions can&amp;rsquo;t have dependency cycles with each other, I think this is because they&amp;rsquo;re one-shot transactions which have already been totally ordered by the sequencer. Since they can&amp;rsquo;t have cycles, they don&amp;rsquo;t have deadlocks, so the scheduler dispatches them to the workers in a straightforward way. Whenever all a transaction&amp;rsquo;s dependencies have finished, the transaction can run.&lt;/p&gt;
&lt;p&gt;There can be single-home transactions that are in one region but multiple &lt;strong&gt;partitions&lt;/strong&gt;, the Detock paper isn&amp;rsquo;t interested in these. It just says, &amp;ldquo;Transactions accessing multiple partitions in the same replica follow a deterministic execution protocol similar to Calvin and thus do not require two-phase commit.&amp;rdquo; I guess I need to re-read the Calvin paper.&lt;/p&gt;
&lt;p&gt;Each region&amp;rsquo;s local log is asynchronously replicated to other regions. All regions replicate each other&amp;rsquo;s transactions at different times, there&amp;rsquo;s no coordination. When a region replicates a remote log it can play the transactions there to update its local copy of the remote data. I think this makes all the copies eventually consistent, but there&amp;rsquo;s no stronger guarantee.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s talk about multi-home transactions, this is the interesting part.&lt;/p&gt;
&lt;h1&gt;Multi-Home Transactions&lt;/h1&gt;

&lt;p&gt;Let&amp;rsquo;s say a transaction comes to some region, and its list of items spans several regions.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-detock/multi-home-1.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-detock/multi-home-1.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Here&amp;rsquo;s my understanding of the algorithm. A client sends a transaction to an arbitrary region. The forwarder checks the read and write sets and annotates the keys with their home regions. Since this transaction involves keys in Region A and Region B the forwarder forwards it to Region B.&lt;/p&gt;
&lt;p&gt;In both regions, the forwarder notices that this is a multi-home transaction, so it creates something called a &amp;ldquo;Graph Placement Transaction&amp;rdquo;, I&amp;rsquo;ve drawn these as T1a and T1b.&lt;/p&gt;
&lt;p&gt;A &amp;ldquo;Graph Placement Transaction&amp;rdquo; is the part of the transaction that only uses keys in one region. So T1a is the part of the transaction on keys in A, same for T1b. Graph Placement Transactions are like single-home transactions, and they&amp;rsquo;re added to a batch by the sequencer, like single-home transactions. But scheduling them is much more complex than scheduling single-home transactions.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say another transaction T2 arrives around the same time in Region B. And let&amp;rsquo;s say that T2&amp;rsquo;s Graph Placement Transactions are sequenced so that T2 is first in Region B and second in Region A.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-detock/multi-home-2.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-detock/multi-home-2.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;The two regions&amp;rsquo; log managers communicate the sequences they chose. As these Graph Placement Transactions arrive they&amp;rsquo;re processed by the scheduler. They arrive in different orders at different regions&amp;rsquo; schedulers. So maybe in Region A, T1 arrives before T2, and vice versa in Region B. So the scheduler can&amp;rsquo;t just execute each transaction as soon as possible once all conflicting transactions are finished, that would lead to different outcomes in different regions. We need some way for these Graph Placement Transactions to be scheduled in the same order everywhere, despite the asynchronous replication. The authors write,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GraphPlacementTxns establish an order between multi-home and single-home transactions at the region that generated the GraphPlacementTxn. However, they do not globally order multi-home transactions, since two different regions may generate GraphPlacementTxns for a set of multi-home transactions in different orders. There is thus a concern that the generated graph may contain cycles, which would lead to deadlock during processing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This deterministic scheduling is where Detock diverges substantially from SLOG. It&amp;rsquo;s the major contribution, and the hardest for me to understand.&lt;/p&gt;
&lt;h1&gt;Deterministic Deadlock Avoidance&lt;/h1&gt;

&lt;p&gt;To explain Detock&amp;rsquo;s deadlock avoidance, first imagine you know all the transactions that will ever occur, and you know the dependencies among them. You can &lt;strong&gt;condense&lt;/strong&gt; the dependency graph into strongly connected components (SCC).&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-detock/condense.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;A strongly connected component (SCC) is a subgraph where all the transactions are reachable from all the others. Therefore it contains at least one cycle. Within an SCC, Detock schedules transactions according to their unique transaction ids, which are assigned by their coordinators. Among SCCs there are no cycles: the blue arrows can&amp;rsquo;t form cycles, since you&amp;rsquo;ve isolated the cycles within the orange SCCs. Therefore you can just schedule the SCCs by &lt;a href=&#34;https://en.wikipedia.org/wiki/Topological_sorting&#34;&gt;topologically sorting&lt;/a&gt; them.&lt;/p&gt;
&lt;p&gt;This works if you know all transactions, but in reality transactions are arriving continuously. When is a transaction ready to be scheduled?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For every vertex corresponding a multi-home transaction &lt;em&gt;T&lt;/em&gt; in the dependency graph, let &lt;em&gt;GPTotal&lt;/em&gt; (&lt;em&gt;T&lt;/em&gt; ) be the total number of GraphPlacementTxns generated for &lt;em&gt;T&lt;/em&gt;, a counter &lt;em&gt;GP&lt;/em&gt; (&lt;em&gt;T&lt;/em&gt; ) is associated with &lt;em&gt;T&lt;/em&gt; to keep track of the number of GraphPlacementTxns of &lt;em&gt;T&lt;/em&gt; that have been added to the graph so far. We define two types of vertices:&lt;/p&gt;
&lt;p&gt;A complete vertex &lt;em&gt;T&lt;/em&gt; is either a single-home transaction or a multi-home transaction with &lt;em&gt;GP&lt;/em&gt; (&lt;em&gt;T&lt;/em&gt; ) equal to &lt;em&gt;GPTotal&lt;/em&gt; (&lt;em&gt;T&lt;/em&gt; ).&lt;/p&gt;
&lt;p&gt;A stable vertex &lt;em&gt;T&lt;/em&gt; is a complete vertex and there does not exist a path going from an incomplete vertex to &lt;em&gt;T&lt;/em&gt; .&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So a transaction is stable in a region if its Graph Placement Transactions have all arrived, as have those for all transactions it depends on. I guess &lt;em&gt;GPTotal&lt;/em&gt; (&lt;em&gt;T&lt;/em&gt; ) is calculated at the beginning, when the forwarder creates the Graph Placement Transactions. Once the transaction is stable it can be scheduled; transactions are scheduled in the same order in all regions.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-detock/multi-home-3.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-detock/multi-home-3.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;I think that the ordering algorithm is the same as &lt;a href=&#34;https://dl.acm.org/doi/pdf/10.1145/2517349.2517350&#34;&gt;Egalitarian Paxos&lt;/a&gt; (&amp;ldquo;EPaxos&amp;rdquo;), and Detock introduces the mechanism for waiting until a transaction is &amp;ldquo;stable&amp;rdquo;, but that&amp;rsquo;s just my guess.&lt;/p&gt;
&lt;p&gt;In pathological cases, conflicting transactions continuously arrive at different regions in different orders, and the set of unstable transactions grows forever. Detock gets livelocked: each transaction&amp;rsquo;s dependencies are never resolved and Detock can never start executing it. The more often transactions arrive in regions in the &lt;em&gt;same&lt;/em&gt; order, the lower the risk of livelock. Detock improves its chances thus: the coordinators assign each transaction a timestamp in the future; a transaction is scheduled once its timestamp has passed, by which time most lower-timestamped transactions have probably already arrived. (This is very similar to &lt;a href=&#34;https://emptysqua.re/blog/review-nezha/#deadline-ordered-multicast-dom&#34;&gt;Deadline-Ordered Multicast in Nezha&lt;/a&gt; and several earlier papers.) The authors call this &amp;ldquo;opportunistic reordering&amp;rdquo;.&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;

&lt;p&gt;Like a lot of papers lately, Detock has a massive evaluation section. The authors compare Detock to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calvin, which globally orders all transactions and has no optimizations for geo-distributed transactions,&lt;/li&gt;
&lt;li&gt;SLOG, which globally orders multi-home transactions using one ordering service in one region,&lt;/li&gt;
&lt;li&gt;SLOG (slow), which is Calvin plus global consensus for the ordering of multi-home transactions,&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.usenix.org/conference/osdi16/technical-sessions/presentation/mu&#34;&gt;Janus&lt;/a&gt;, an EPaxos variant optimized for geo-distributed transactions,&lt;/li&gt;
&lt;li&gt;CockroachDB, which uses Spanner-style nondeterministic concurrency control based on synchronized clocks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a fair fight, they reimplemented the first four protocols within &lt;a href=&#34;https://github.com/umd-dslam/Detock&#34;&gt;the same codebase as Detock&lt;/a&gt;. They vary workload skewness, network latency, the ratio of multi-home to single-home transactions, the ratio of multi-partition to single-partition transactions within a region, etc. etc. for multiple benchmarks. I was most interested in this sub-chart of Figure 6 (I&amp;rsquo;ve edited its layout):&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-detock/figure-6.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This is a YCSB experiment with high contention (a few very hot keys) and no data partitioning within regions. It&amp;rsquo;s a rare example of a system not beating all rivals in all circumstances in its evaluation section. Detock&amp;rsquo;s throughput falls as the percent of multi-home transactions rises, since multi-home transactions require more work at more regions than single-home. When most transactions are multi-home, in fact, Calvin&amp;rsquo;s naïve algorithm actually beats Detock. The paper claims (and I agree) that this is an unlikely scenario, though.&lt;/p&gt;
&lt;p&gt;The graph shows how critical opportunistic reordering is for Detock: the blue line at the bottom is pathetic.&lt;/p&gt;
&lt;p&gt;In other experiments, the authors show that Detock&amp;rsquo;s distributed processing of multi-home transactions beats SLOG, where the centralized ordering service is a bottleneck. Plus, SLOG suffers more from contention, since its multi-home transactions hold locks for longer. (See the SLOG and Detock papers for details.)&lt;/p&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;This paper is well-written but its content is ineluctably complex. It&amp;rsquo;s not a single clever algorithm like &lt;a href=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/&#34;&gt;timestamp as a service&lt;/a&gt; or &lt;a href=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/&#34;&gt;leader leases&lt;/a&gt;. Instead, it&amp;rsquo;s a new combination of existing parts, many of them already intricate. If you don&amp;rsquo;t know Calvin and EPaxos and maybe SLOG, this paper is hard to read. But its complexity is realistic: real distributed databases are horribly complex. We make them by combining all the state-of-the-art components, trying to stake out an unclaimed position on the Pareto frontier. If you think Detock is complex, try a real-world protocol like &lt;a href=&#34;https://cwiki.apache.org/confluence/download/attachments/188744725/Accord.pdf&#34;&gt;Cassandra&amp;rsquo;s&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The authors can&amp;rsquo;t claim &amp;ldquo;our protocol is absolutely the best&amp;rdquo;; as always, they have to claim &amp;ldquo;our protocol makes better tradeoffs in realistic scenarios&amp;rdquo;. Detock looks like a solid improvement over previous similar systems, and I appreciate the giant effort the authors made to benchmark Detock and report their results honestly.&lt;/p&gt;
&lt;h1&gt;See Also&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://muratbuffalo.blogspot.com/2023/07/detock-high-performance-multi-region.html&#34;&gt;Murat Demirbas&amp;rsquo;s summary of Detock&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.acm.org/doi/pdf/10.1145/2517349.2517350&#34;&gt;Egalitarian Paxos&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Review: Timestamp as a Service, not an Oracle</title>
      <link>https://emptysqua.re/blog/review-timestamp-as-a-service/</link>
      <pubDate>Thu, 23 May 2024 15:18:28 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-timestamp-as-a-service/</guid>
      
      
      
      
      <description>&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/rxDvzEKylrQ?si=woPUZ32iSp7uIgoM&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; style=&#34;margin-bottom: 1em&#34;  allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href=&#34;https://www.vldb.org/pvldb/vol17/p994-li.pdf&#34;&gt;Timestamp as a Service, not an Oracle&lt;/a&gt;, by authors from Alibaba Cloud, in Proceedings of VLDB this year. Watch my presentation to the &lt;a href=&#34;https://charap.co/spring-2024-reading-group-papers-161-170/&#34;&gt;Distributed Systems Reading Group&lt;/a&gt; above, or read my summary below.&lt;/p&gt;
&lt;h1&gt;Timestamp Oracles&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/oracle-delphi.jpg&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 400px;&#34;&gt;
&lt;p&gt;Priestess of Delphi (1891) by John Collier.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;An oracle is someone who speaks for a god and reveals divine knowledge. In computer science we&amp;rsquo;ve used &amp;ldquo;oracle&amp;rdquo; to refer to theoretical machines that could do something impossible, like solve the halting problem or produce truly random numbers. Also, for some reason, real actual machines that produce monotonic timestamps are called &amp;ldquo;timestamp oracles&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;A timestamp oracle is used by a distributed database to get monotonically increasing IDs, for ordering events. It&amp;rsquo;s a single server in your data center which provides a larger number every time you ask it. The timestamp might or might not be related to the wall clock.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why not Lamport clocks or vector clocks? They require you to pass these clock values between clients and servers, through all the layers of your multi-tier architecture. &lt;a href=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/&#34;&gt;I know personally that database users find that burdensome&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Why not synchronized clocks? Syncing clocks is hard, and no matter how precise the clock is, you need to add some latency to wait out the uncertainty. Well-synced clocks are becoming widely available, though; see &lt;a href=&#34;https://emptysqua.re/blog/review-huygens-paper/&#34;&gt;Huygens&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/blogs/compute/its-about-time-microsecond-accurate-clocks-on-amazon-ec2-instances/&#34;&gt;AWS Time Sync&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The paper mentions that timestamp oracles are used by various distributed systems: PolarDB-X, OceanBase, CORFU, TiDB placement driver, Percolator, Postgres-XL. I&amp;rsquo;ve only heard of half of these. The &lt;a href=&#34;https://docs.pingcap.com/tidb/stable/tidb-architecture#placement-driver-pd-server&#34;&gt;TiDB placement driver&lt;/a&gt; (&amp;ldquo;TiDB-PD&amp;rdquo;) includes a timestamp oracle in its implementation, and it&amp;rsquo;s the main example that this paper&amp;rsquo;s authors use as a comparison for evaluating their alternative.&lt;/p&gt;
&lt;p&gt;Timestamp oracles only work for one data center. The timestamp consumers should be on a very low-latency link to the timestamp oracle server; cross-DC links are too slow for practical use. This paper doesn&amp;rsquo;t try to solve that problem: this paper&amp;rsquo;s timestamp-as-a-service is also intended for one data center.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;fault-tolerant&lt;/em&gt; timestamp oracle is a consensus group: each new timestamp is majority-committed. If the leader fails, the next leader must know the highest timestamp that the previous leader produced. The paper mentions that this can be optimized: the leader could reserve a range of timestamps. It majority-commits the range, and it gives out timestamps until the range is exhausted. A new leader reserves a higher range of timestamps than any previous one.&lt;/p&gt;
&lt;p&gt;A consensus group is fault-tolerant, but nevertheless, losing the leader causes some brief unavailability. Especially since this consensus group must use &lt;a href=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/&#34;&gt;timed leader leases&lt;/a&gt;, for speed and consistency. Therefore the new leader has to wait for the previous lease to expire. The paper shows that TiDB-PD is unavailable for 10 seconds after the leader dies. The black throughput line drops to zero each time the leader is killed:&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/tidb-pd-unavailability.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 9 from the paper, lower half&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Besides being a single point of failure, the leader is a bottleneck—you can&amp;rsquo;t get timestamps from followers, so a system could saturate the timestamp oracle leader.&lt;/p&gt;
&lt;h1&gt;A Service, Not An Oracle&lt;/h1&gt;

&lt;p&gt;The authors propose a timestamp service, rather than an oracle. They say an oracle is one server, therefore a single point of failure, even if it&amp;rsquo;s the leader of a consensus group. A service, however, is not a single point of failure. I don&amp;rsquo;t know if everyone agrees with these definitions of oracle and service, but it&amp;rsquo;s a useful distinction for this paper. The paper says a Timestamp-as-a-Service is &amp;ldquo;a distributed algorithm that computes logical timestamps from a consensusless cluster of clocks.&amp;rdquo; That means no unavailability from loss of the leader, and no bottleneck on the leader. Consensuslessness is not only fun to say, it&amp;rsquo;s the funnest part of this paper!&lt;/p&gt;
&lt;h1&gt;The Algorithm, 1.0&lt;/h1&gt;

&lt;p&gt;The paper presents the Timestamp-as-a-Service (TaaS) algorithm in two stages, starting with a simplified version that assumes no server failures or message loss. There can be any number of clients, and &lt;em&gt;N&lt;/em&gt; servers.&lt;/p&gt;
&lt;p&gt;A client starts a session &lt;em&gt;S&lt;/em&gt; by sending the bottom timestamp ⊥ to all servers. The paper generally uses σ (sigma) for a session but I&amp;rsquo;m going to use &lt;em&gt;S&lt;/em&gt; because I don&amp;rsquo;t read Greek. That symbol for &amp;ldquo;bottom timestamp&amp;rdquo;, you could think of it as negative infinity. Whenever you see that symbol you know you&amp;rsquo;re probably in the world of lattices and order theory and abstract algebra.&lt;/p&gt;
&lt;p&gt;A &amp;ldquo;session&amp;rdquo; in this paper is not a sequence of database commands. It&amp;rsquo;s just the commands required to get one timestamp, then the session is over. The client gets its timestamp and then forgets everything, no state persists into the next session. So the client always starts its session by sending ⊥, even if it got a timestamp in a previous session.&lt;/p&gt;
&lt;p&gt;Each server has a persistent timestamp, which is somehow initialized to some value when the server is born. When the server receives the client request, it increments its timestamp and sends its new current timestamp back to the client. Thus if a server replies with &amp;ldquo;5&amp;rdquo;, the client knows the server&amp;rsquo;s current timestamp is 5, and it knows the server had a timestamp less than 5 before the client talked to it. This will be important.&lt;/p&gt;
&lt;p&gt;The session is complete once all servers have replied. The client uses the &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest timestamp from the replies, for some &lt;em&gt;M&lt;/em&gt; ≤ &lt;em&gt;N&lt;/em&gt;. All clients must use the same &lt;em&gt;M&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;M&lt;/em&gt; can be anything! &lt;em&gt;M&lt;/em&gt; should be the smallest majority, so if &lt;em&gt;N&lt;/em&gt; is 5 then &lt;em&gt;M&lt;/em&gt; should be 3. The paper starts abstractly, by showing TaaS is correct for any &lt;em&gt;M&lt;/em&gt; from 1 to &lt;em&gt;N&lt;/em&gt;. Eventually it admits that for maximum fault tolerance, &lt;em&gt;M&lt;/em&gt; should be the smallest majority, same as a quorum in &lt;a href=&#34;https://emptysqua.re/blog/paxos-vs-raft/&#34;&gt;Paxos or any consensus algorithm&lt;/a&gt;. But for now, &lt;em&gt;M&lt;/em&gt; is any number 1 through &lt;em&gt;N&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example of TaaS 1.0 in action.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/algorithm-v1.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Figure 2 from the paper.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;There are Clients V and Client W, and Servers X, Y, and Z. Session Alpha starts concurrently with Session Beta. Session Gamma starts after Session Alpha. Let&amp;rsquo;s say &lt;em&gt;M&lt;/em&gt; = 2, so at the end of each session the client chooses the second‑smallest timestamp from all the server replies.&lt;/p&gt;
&lt;p&gt;Session Alpha gets timestamp 1, Beta gets timestamp 2, and Gamma gets timestamp 3. Beta&amp;rsquo;s allowed to have any timestamp since it&amp;rsquo;s concurrent with the others. So you can see these timestamps uphold the linearizability guarantee. The important constraint is, Session Alpha&amp;rsquo;s timestamp must be less than Session Gamma&amp;rsquo;s, and it is: 1 is less than 3.&lt;/p&gt;
&lt;p&gt;The client could send its messages to all servers in parallel, or any order within a session, and the latencies could be of any length, TaaS still works.&lt;/p&gt;
&lt;h2&gt;Theorem 1&lt;/h2&gt;

&lt;p&gt;&amp;ldquo;The timestamp for session &lt;em&gt;T&lt;/em&gt; is guaranteed larger than the timestamp for any session &lt;em&gt;S&lt;/em&gt; that ended before &lt;em&gt;T&lt;/em&gt; began.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;This sounds like a linearizability guarantee, and I believe you could call the timestamp a linearizable data structure. Proof:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest response in &lt;em&gt;S&lt;/em&gt; ≤ the &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest server state at the end of &lt;em&gt;S&lt;/em&gt;. (Servers&amp;rsquo; timestamps increase monotonically, so their timestamps at the end of &lt;em&gt;S&lt;/em&gt; must be ≥ their responses in &lt;em&gt;S&lt;/em&gt;.)&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest server state at the end of &lt;em&gt;S&lt;/em&gt; ≤ the &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest server state at the start of &lt;em&gt;T&lt;/em&gt;. (Monotonicity, and &lt;em&gt;S&lt;/em&gt; ends before &lt;em&gt;T&lt;/em&gt; starts.)&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest server state at the start of &lt;em&gt;T&lt;/em&gt; &amp;lt; the &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest response in &lt;em&gt;T&lt;/em&gt;. (The client makes all the servers increment their timestamps during &lt;em&gt;T&lt;/em&gt;.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So by transitivity, the left side of #1 &amp;lt; the right side of #3. &lt;em&gt;&lt;strong&gt;Q.E.D.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;The Algorithm, 2.0&lt;/h1&gt;

&lt;p&gt;In the second and final version of TaaS, servers can crash-fail indefinitely, and they can come back online.&lt;/p&gt;
&lt;p&gt;A restarted server still guarantees monotonicity, i.e. it remembers the last timestamp it produced before it crashed. The only way to guarantee this is some sort of replication.The paper suggests using RAID, or cloud storage like S3 which has its own replication, or make each timestamp server a consensus group with fault-tolerance. You might ask, if we make each timestamp server a consensus group, haven&amp;rsquo;t we come full-circle to the &amp;ldquo;timestamp oracle&amp;rdquo; that the paper says is bad? Not quite: now we have a separate group for each timestamp server, so if one group loses its leader, TaaS can proceed with the other groups without waiting for a new leader.&lt;/p&gt;
&lt;p&gt;The TaaS client&amp;rsquo;s goal is to find a timestamp t such that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;t &amp;gt; &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest of all servers&amp;rsquo; timestamps when the session started&lt;/li&gt;
&lt;li&gt;t ≤ &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest of all servers&amp;rsquo; timestamps when the session ends&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two properties are the two facts that Theorem 1 depends on.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example where Server X is partitioned from the client. Let&amp;rsquo;s say &lt;em&gt;M&lt;/em&gt;=2; we want the 2&lt;sup&gt;nd&lt;/sup&gt;‑smallest timestamp.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/session-delta.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;From Figure 3.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The client remembers that it got timestamp 5 in some past session from Server X. This memory is a new feature of the fault-tolerant version of TaaS.&lt;/p&gt;
&lt;p&gt;In session δ, the client gets a 4 and a 5 from the servers it can reach. It picks 5 as the timestamp for this session, because the client knows, without talking to Server X:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5 &amp;gt; 2&lt;sup&gt;nd&lt;/sup&gt;-smallest timestamp at the start of the session: The client heard Servers Y and Z respond with timestamps 4 and 5, so their previous timestamps were less than 5.&lt;/li&gt;
&lt;li&gt;5 ≤ 2&lt;sup&gt;nd&lt;/sup&gt;-smallest timestamp at the end of the session: The client knows Servers X and Z now have timestamps at least 5.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two facts that Theorem 1 relies on are both true, so the client can pick 5 without talking to Server X.&lt;/p&gt;
&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/session-epsilon.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;From Figure 3.&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;In session ε the client gets 5 and 6 from the servers it can reach. Now it doesn&amp;rsquo;t know the second‑smallest timestamp. If Server X is talking to some other client, it might have advanced to 7; then the second‑smallest would be 6. Or Server X might be at timestamp 5.5—timestamps don&amp;rsquo;t have to be integers! (I wish the paper had mentioned this earlier.) If Server X has 5.5, then 5.5 would be the second‑smallest. We don&amp;rsquo;t know. What&amp;rsquo;s the solution?&lt;/p&gt;
&lt;p&gt;The client does something new now. Instead of sending the bottom timestamp to all the servers, like it did before, it continues this session. It thinks 6 might be the second‑smallest timestamp. So it sends 6 to any servers that might have less than 6: Server Y. Then Server Y updates its own timestamp to at least 6, increments it by one, and returns it.&lt;/p&gt;
&lt;p&gt;Now the client has 6 from Z, 7 from Y. X is still down, but the client doesn&amp;rsquo;t care, it can choose 6:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6 &amp;gt; 2&lt;sup&gt;nd&lt;/sup&gt;-smallest timestamp at the start of the session: The client heard Servers Y and Z respond with 5 and 6, so they started with less than 6.&lt;/li&gt;
&lt;li&gt;6 ≤ 2&lt;sup&gt;nd&lt;/sup&gt;-smallest timestamp at the end of the session: The client eventually heard Servers Z and Y respond with 6 and 7, they ended with at least 6.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the client achieved certainty by advancing one of the server&amp;rsquo;s timestamps. That&amp;rsquo;s a specific example, here&amp;rsquo;s the general algorithm.&lt;/p&gt;
&lt;h2&gt;Algorithm 2.0 pseudocode&lt;/h2&gt;

&lt;p&gt;The paper uses a pseudocode that&amp;rsquo;s hard for me to read, so of course I&amp;rsquo;ll make my own pseudocode that seems easier to me &lt;a href=&#34;https://emptysqua.re/blog/write-python-not-pseudocode/&#34;&gt;but may be worse for you&lt;/a&gt;. Sorry.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// global map from server to timestamp, initially bottom timestamp for
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// all servers. the client persistently tracks this. 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cache = {server: ⟘ for server in servers}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// client code to acquire one timestamp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;def do_session():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  // &amp;#34;session&amp;#34; is like &amp;#34;cache&amp;#34; but reset with top timestamp each session
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  session = {server: ⟙ for server in servers}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  // like TaaS 1.0, initially send bottom timestamp to all servers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  send timestamp ⟘ to all servers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  while true:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    reply = await next reply
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    // update global cache with max per server, local with min
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cache[reply.server] = max(cache[reply.server], reply.timestamp)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    session[reply.server] = min(session[reply.server], reply.timestamp)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    if we received at least M replies:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      candidate = Mth-smallest value in session
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      if candidate ≤ Mth-smallest value in cache:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        return candidate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      else if no more quickly-available replies:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        // promote the candidate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        for server in servers:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          if cache[server] &amp;lt; candidate:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            send candidate timestamp to server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As the client gets replies, it updates the global and local maps. The purpose of these two maps is to determine when we know the two facts we need for Theorem 1.&lt;/p&gt;
&lt;p&gt;Each server&amp;rsquo;s timestamp is guaranteed to increase monotonically, but a client could get replies out of order. So it uses &lt;code&gt;max&lt;/code&gt; when updating the global &lt;code&gt;cache&lt;/code&gt;, to ignore delayed messages. The client uses &lt;code&gt;min&lt;/code&gt; when updating the per-session map called &lt;code&gt;session&lt;/code&gt;. I don&amp;rsquo;t understand why; it seems that if the client got a 6 from Server X, then a 5, it should keep the 6, because it knows that 5 is a delayed message. The algorithm is still correct, it just has unnecessary retries.&lt;/p&gt;
&lt;p&gt;How does the client know when it&amp;rsquo;s acquired a correct timestamp, at the &lt;code&gt;return timestamp&lt;/code&gt; line? Remember, the goal is to find a timestamp t such that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;t &amp;gt; &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest of all servers&amp;rsquo; timestamps when the session started: at least &lt;em&gt;M&lt;/em&gt; servers started &amp;lt; candidate, according to session-local map&lt;/li&gt;
&lt;li&gt;t ≤ &lt;em&gt;M&lt;/em&gt;&lt;sup&gt;th&lt;/sup&gt;‑smallest of all servers&amp;rsquo; timestamps when the session ends: at least &lt;em&gt;M&lt;/em&gt; servers end &amp;gt; candidate, according to global map&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The client knows &lt;code&gt;candidate&lt;/code&gt; satisfies the first criterion, because according to the session-local map, there are &lt;em&gt;M&lt;/em&gt; servers that had smaller timestamps when the session started, before the client talked to them. All the values in the session-local map are values that servers incremented during the session, so the starting values were smaller.&lt;/p&gt;
&lt;p&gt;The client knows &lt;code&gt;candidate&lt;/code&gt; meets the second criterion because it explicitly checks it in the global &lt;code&gt;cache&lt;/code&gt;. The client hasn&amp;rsquo;t talked to all the servers, but it&amp;rsquo;s talked to enough of them to know that this fact is true. It wants to ensure that future sessions are guaranteed to get a larger timestamp than this one, and it&amp;rsquo;s learned enough to guarantee that.&lt;/p&gt;
&lt;p&gt;If the client has no candidate, it tries to make the second criterion true by advancing the timestamps on servers that might have less than &lt;code&gt;candidate&lt;/code&gt;. The client tries all lagging servers, including those that were unavailable recently, in case they came back online.&lt;/p&gt;
&lt;p&gt;At the line, &lt;code&gt;else if no more quickly-available replies&lt;/code&gt;, there&amp;rsquo;s some timeout while the client waits to see if the remaining servers are going to reply soon or not. I guess that&amp;rsquo;s a tunable parameter.&lt;/p&gt;
&lt;h1&gt;What Should &lt;em&gt;M&lt;/em&gt; Be?&lt;/h1&gt;

&lt;p&gt;It&amp;rsquo;s finally time to descend from lofty abstraction and decide a value for &lt;em&gt;M&lt;/em&gt;. The authors write, &amp;ldquo;The system allows at most min(&lt;em&gt;N&lt;/em&gt; − &lt;em&gt;M&lt;/em&gt;, &lt;em&gt;N&lt;/em&gt; − 1) downs while continuing its service.&amp;rdquo; So for optimal fault-tolerance &lt;em&gt;M&lt;/em&gt; should be the median of 1 .. &lt;em&gt;N&lt;/em&gt;:&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/formula-23937a9b21915f03fe7764b9819b6e13dcef7fd4ecf6f25a0942a5f4d656309b.svg&#34; type=&#34;image/svg+xml&#34;&gt;
  &lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/formula-23937a9b21915f03fe7764b9819b6e13dcef7fd4ecf6f25a0942a5f4d656309b.png&#34; style=&#34;display:inline;width:auto;height:1em;vertical-align:middle&#34; alt=&#34;\left\lceil \frac{n+1}{2} \right\rceil&#34;&gt;
&lt;/picture&gt;&lt;p&gt;I suppose it&amp;rsquo;s theoretically interesting that &lt;em&gt;M&lt;/em&gt; could be any number, but practically, it should be a majority, which makes this protocol resemble consensus a lot. However, the authors insist this isn&amp;rsquo;t consensus, because servers never talk with each other. TaaS is a &amp;ldquo;bipartite architecture&amp;rdquo;, where clients and servers talk to each other, but servers don&amp;rsquo;t talk to servers and clients don&amp;rsquo;t talk to clients. There&amp;rsquo;s a nice little discussion in Section 6.2 of the definition of consensus and consensusless consistency.&lt;/p&gt;
&lt;h1&gt;Unique Timestamps&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/agent-smith.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Concurrent sessions may get the same timestamp. Solution: each server appends its server ID to the returned timestamp.&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;

&lt;p&gt;Comparing TaaS to a consensus-based timestamp oracle (TiDB-PD), the authors claim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the happy case, TaaS is higher-latency than TiDB-PD, especially with more servers.&lt;/li&gt;
&lt;li&gt;TiDB-PD stalls for 10 seconds after leader failure.&lt;/li&gt;
&lt;li&gt;TaaS latency doesn&amp;rsquo;t increase much when servers are slow or dead, if a majority is healthy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thus TaaS is closer to &lt;a href=&#34;https://aws.amazon.com/builders-library/reliability-and-constant-work/&#34;&gt;constant work&lt;/a&gt;, which makes it more stable as part of a whole system and avoids &lt;a href=&#34;https://charap.co/metastable-failures-in-distributed-systems/&#34;&gt;metastable failures&lt;/a&gt;. Here&amp;rsquo;s Figure 9 again, now with TaaS included:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/figure-9.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;At the 1- and 2-minute marks, the authors killed a server. TiDB-PD suffered a complete stall in throughput, TaaS merely increased latency a bit.&lt;/p&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;The algorithm is fairly simple, but the authors explain it with needlessly weird notation. These are three pieces of notation actually used in this paper:&lt;/p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/weird-notation.png&#34; style=&#34;max-width: 300px&#34;&gt;
&lt;p&gt;This looks less like math and more like an alien language. The one on the bottom means the &amp;ldquo;second‑smallest timestamp received by session delta.&amp;rdquo; I guess that&amp;rsquo;s ok, but they never explain why delta gets an upside-down hat? Is that a rule in this alien culture?&lt;/p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-timestamp-as-a-service/alien.png&#34; style=&#34;max-width: 100%&#34;&gt;
&lt;p&gt;More seriously, I&amp;rsquo;m curious about several questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TaaS doesn&amp;rsquo;t assume timestamps are integers. If it did, i.e. if there were a minimum increment amount, could the fault-tolerant algorithm be more efficient?&lt;/li&gt;
&lt;li&gt;What happens when a client restarts and loses its long-lived global cache? Does that weaken fault-tolerance?&lt;/li&gt;
&lt;li&gt;What about reconfiguration?: how are timestamps servers initialized, added, or removed?&lt;/li&gt;
&lt;li&gt;Has Alibaba deployed this? It seems like they haven&amp;rsquo;t. Why?—did they use synced clocks instead?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I enjoyed this paper, and spent a long time understanding it (as you can see). It describes a new protocol in the classic distributed systems style. It provides rigorous explanations and proofs, and informative experiments. If you need monotonic ids within one data center and you can&amp;rsquo;t use synchronized clocks, TaaS is a simple solution with stable performance during failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; One of the authors has &lt;a href=&#34;https://x.com/YishuaiLi/status/1802958253602779241&#34;&gt;answered some of my questions&lt;/a&gt; about the protocol.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How To Use MongoDB Causal Consistency</title>
      <link>https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/</link>
      <pubDate>Tue, 14 May 2024 15:53:22 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/</guid>
      
      
      
      
      <description>&lt;p&gt;MongoDB implemented a consistency level called &lt;a href=&#34;https://jepsen.io/consistency/models/causal&#34;&gt;causal consistency&lt;/a&gt; in 2017 (&lt;a href=&#34;https://emptysqua.re/blog/driver-features-for-mongodb-3-6/&#34;&gt;version 3.6&lt;/a&gt;). It&amp;rsquo;s quite a handy consistency level, strong enough for most applications and still performant. I helped design the API for causal consistency, but when we released it I dropped the ball and didn&amp;rsquo;t publicize or document it well. MongoDB&amp;rsquo;s causal consistency didn&amp;rsquo;t get the fame it deserved in 2017; I&amp;rsquo;ll try to rectify that now.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/replica-set.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/replica-set.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;MongoDB implements a Raft-like consensus protocol. Most people deploy MongoDB as a three-server replica set, with one primary and two secondaries. Only the primary executes writes, which are replicated by secondaries with a small delay. You can read from the primary or the secondaries. Your application talks to the servers via an instance of the &lt;a href=&#34;https://www.mongodb.com/docs/drivers/&#34;&gt;MongoClient&lt;/a&gt; class.&lt;/p&gt;
&lt;h1&gt;The Problem With Secondary Reads&lt;/h1&gt;

&lt;div style=&#34;text-align: center; margin-bottom: 1em&#34;&gt;&lt;em&gt;
&lt;img src=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/influencer.png&#34; alt=&#34;&#34; style=&#34;display: block; margin: auto; margin-bottom: 0.5em; max-width: 100%;&#34;&gt;
&lt;p&gt;Liam&lt;/p&gt;
&lt;p&gt;&lt;/em&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with the classic example: you&amp;rsquo;ve implemented a social media site, with MongoDB as your database. Some influencer named Liam posts something witty to your site, then clicks on his profile to admire his wittiness. But Liam doesn&amp;rsquo;t see his latest post! He desperately refreshes, and phew: soon his new post appears. What happened?&lt;/p&gt;
&lt;p&gt;The problem is that you have tried to decrease load on the primary by reading posts from secondaries. For example, in Python:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-py3&#34; data-lang=&#34;py3&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;client&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;MongoClient&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;mongodb://srv1,srv2,srv3/?replicaSet=rs&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Configure this collection object to read from secondaries.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;db&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;get_collection&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;posts&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;read_preference&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ReadPreference&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;SECONDARY&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# The insert message goes to the primary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# and awaits majority replication.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;insert_one&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Liam&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;contents&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;I&amp;#39;m very witty&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Read from secondary, due to &amp;#34;read_preference&amp;#34; above.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;user_posts&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;list&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;({&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Liam}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MongoClient sends the &lt;code&gt;insert&lt;/code&gt; command to the primary, which &lt;a href=&#34;https://www.mongodb.com/docs/manual/reference/write-concern/#implicit-default-write-concern&#34;&gt;by default&lt;/a&gt; awaits acknowledgment from a majority of servers: in a three-server replica set, a majority is the primary plus one secondary. But then, MongoClient sends the &lt;code&gt;find&lt;/code&gt; command to a random secondary—perhaps the other secondary, which may not have replicated the new post yet.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/problem-sequencediagram.org.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/problem-sequencediagram.org.png&#34; alt=&#34;A sequence diagram showing that the find command can be executed on a secondary that hasn&amp;rsquo;t replicated the insert command yet&#34; title=&#34;The problem&#34;&gt;
&lt;/picture&gt;&lt;p&gt;Reading from secondaries is unpredictable. You can&amp;rsquo;t reliably &lt;a href=&#34;https://jepsen.io/consistency/models/read-your-writes&#34;&gt;read your writes&lt;/a&gt;. You can&amp;rsquo;t do &lt;a href=&#34;https://jepsen.io/consistency/models/monotonic-reads&#34;&gt;monotonic reads&lt;/a&gt; either: as you execute a series of reads on secondaries, you&amp;rsquo;ll use secondaries with different amounts of replication lag, so your data will seem to randomly jump back and forth in time. Secondary reads give you only the weakest guarantee, &lt;a href=&#34;https://en.wikipedia.org/wiki/Eventual_consistency&#34;&gt;eventual consistency&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Causal Consistency&lt;/h1&gt;

&lt;p&gt;We want you to be able to read from secondaries with reasonable consistency, so in MongoDB 3.6 we introduced causal consistency, which guarantees read-your-writes and monotonic reads. As we wrote in &lt;a href=&#34;https://dl.acm.org/doi/10.1145/3299869.3314049&#34;&gt;Implementation of Cluster-wide Logical Clock and Causal Consistency in MongoDB&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Causal Consistency is defined as a model that preserves a partial order of events in a distributed system. If an event A causes another event B, causal consistency provides an assurance that every other process in the system observes the event A before observing event B. Causal order is transitive: if A causes B and B causes C then A causes C. Non causally ordered events are concurrent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You enable causal consistency with a &lt;a href=&#34;https://pymongo.readthedocs.io/en/stable/api/pymongo/client_session.html&#34;&gt;session&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-py3&#34; data-lang=&#34;py3&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Note read_concern, which isn&amp;#39;t in the code above.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;db&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;get_collection&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;posts&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;read_preference&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ReadPreference&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;SECONDARY&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;read_concern&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ReadConcern&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;majority&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# start_session() has causal_consistency=True by default.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;start_session&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;insert_one&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Liam&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;contents&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;I&amp;#39;m very witty&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;},&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;session&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Read your previous write, even from a secondary!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;user_posts&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;list&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Liam}, session=s))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# This query returns data at least as new as the previous&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# query, even if it chooses a different secondary.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;count_documents&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;({},&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;session&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You have to pass the session parameter with every command. This is too easy to forget—I forgot it when I first wrote this example!&lt;/p&gt;
&lt;p&gt;In a causally consistent session, you&amp;rsquo;ll read your writes and get monotonic reads from secondaries. Both read concern and write concern must be set to &amp;ldquo;majority&amp;rdquo;, as &lt;a href=&#34;https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/&#34;&gt;explained in the MongoDB manual&lt;/a&gt;. &amp;ldquo;Majority&amp;rdquo; is the default write concern, so I configured only the read concern explicitly.&lt;/p&gt;
&lt;p&gt;How does MongoDB ensure causal consistency? It uses a logical clock (aka a &lt;a href=&#34;https://lamport.azurewebsites.net/pubs/time-clocks.pdf&#34;&gt;Lamport clock&lt;/a&gt;) called &lt;em&gt;clusterTime&lt;/em&gt; to &lt;a href=&#34;https://dl.acm.org/doi/pdf/10.1145/3299869.3314049&#34;&gt;partially order events across all servers in a replica set or sharded cluster&lt;/a&gt;. Whenever the client sends a write operation to a server, the server advances its logical clock and returns the new clock value to the MongoClient. Then, if the MongoClient&amp;rsquo;s next message is a query, it passes the &lt;em&gt;afterClusterTime&lt;/em&gt; parameter, which asks the server to return data including all writes up to that clusterTime. If the server is a lagged secondary, it waits until has sufficiently caught up:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/causal-sequencediagram.org.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/causal-sequencediagram.org.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;If you query a secondary that hasn&amp;rsquo;t yet caught up to that point in time, according to the logical clock, then your query blocks until the secondary replicates to that point. (Yes, the parameter is called afterClusterTime, but the secondary only needs to replicate &lt;strong&gt;up to&lt;/strong&gt; that clusterTime, not after it.)&lt;/p&gt;
&lt;h1&gt;The Fine Print&lt;/h1&gt;

&lt;p&gt;As I said, causal consistency requires write concern &amp;ldquo;majority&amp;rdquo; (the default) and read concern &amp;ldquo;majority&amp;rdquo; (not the default). Other configurations give weaker guarantees, &lt;a href=&#34;https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/&#34;&gt;see the thorough documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Your client-side session object tracks, in memory, an ever-increasing clusterTime value, which it exchanges with every MongoDB server it talks to. If you lose the clusterTime, you lose causal consistency. Thus you must use the same MongoClient and the same session object throughout. Except, it&amp;rsquo;s possible to&amp;hellip;&lt;/p&gt;
&lt;h1&gt;Transfer the clusterTime&lt;/h1&gt;

&lt;p&gt;Here&amp;rsquo;s the trick we should&amp;rsquo;ve publicized: you can &lt;strong&gt;transfer&lt;/strong&gt; the clusterTime from one session to another. The sessions can&amp;rsquo;t be concurrent—you must wait for one session to end before you use the next one—but even if the sessions belong to different MongoClients, or different processes, or they run on different machines, they&amp;rsquo;ll form a causally consistent chain.&lt;/p&gt;
&lt;p&gt;Continuing the example above, inside the &amp;ldquo;with start_session&amp;rdquo; block, capture the session&amp;rsquo;s clusterTime and operationTime:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-py3&#34; data-lang=&#34;py3&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;start_session&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;posts_collection&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;insert_one&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Liam&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;contents&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;I&amp;#39;m very witty&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;},&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;session&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;cluster_time&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;cluster_time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;operation_time&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;operation_time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you have logical clock values from the primary &lt;strong&gt;after&lt;/strong&gt; it inserted the post. You can create a new session, optionally on a different client or even a different machine, and fast-forward its logical clock:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-py3&#34; data-lang=&#34;py3&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;client2&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;MongoClient&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;mongodb://srv1,srv2,srv3/?replicaSet=rs&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Same get_collection args as before.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;posts_collection2&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client2&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;db&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;get_collection&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;posts&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;read_preference&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ReadPreference&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;SECONDARY&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;read_concern&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;ReadConcern&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;majority&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;client2&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;start_session&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;s2&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;s2&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;advance_cluster_time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;cluster_time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#dfdfdf&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;s2&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;advance_operation_time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;operation_time&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;user_posts2&lt;/span&gt; &lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#204a87&#34;&gt;list&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;posts_collection2&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;({&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4e9a06&#34;&gt;&amp;#34;Liam&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;},&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;session&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;s2&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After you call &lt;code&gt;advance_cluster_time&lt;/code&gt; and &lt;code&gt;advance_operation_time&lt;/code&gt;, subsequent operations on that session (don&amp;rsquo;t forget the session parameter!) are guaranteed to reflect all changes up to that time.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&#34;https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/&#34;&gt;the MongoDB Manual&amp;rsquo;s example code for transferring logical clocks with each driver&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Not So Convenient&lt;/h1&gt;

&lt;p&gt;Why do you need to transfer two clock values between sessions, instead of one? It&amp;rsquo;s bad API design; it reveals implementation details that could have and should have been hidden. I was in a rush during the MongoDB 3.6 cycle and I didn&amp;rsquo;t take the time to understand our logical clocks and propose a convenient API. It&amp;rsquo;s hard to change now, millions of people depend on the current APIs, but &lt;a href=&#34;https://jira.mongodb.org/browse/DRIVERS-2860&#34;&gt;some MongoDB engineers are pushing to fix it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, transferring two values isn&amp;rsquo;t so bad, the real inconvenience is piping these values through the layers of your application stack. If you use MongoDB in your web application, you probably have something like this:&lt;/p&gt;
&lt;div style=&#34;text-align: center&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/three-tier.png&#34; style=&#34;max-width: 70%&#34;&gt;
&lt;/div&gt;
&lt;p&gt;Guaranteeing causal consistency with this architecture is toilsome. When Liam posts his witticism, he clicks a button in the frontend (a Javascript web app in this example), which sends the post to the middle tier, which calls &lt;code&gt;insert&lt;/code&gt; with the MongoDB driver. Then the middle tier must capture the session&amp;rsquo;s clusterTime and operationTime, and return them to the frontend, which saves them in &lt;a href=&#34;https://en.wikipedia.org/wiki/Web_storage&#34;&gt;web storage&lt;/a&gt;. When Liam refreshes the page, the frontend must load the clusterTime and operationTime from web storage and send them with its request to the middle tier, which uses them to call &lt;code&gt;advance_operation_time&lt;/code&gt; and &lt;code&gt;advance_cluster_time&lt;/code&gt; on its session before executing &lt;code&gt;find&lt;/code&gt; on a secondary. This guarantees Liam sees his post, but what a pain in the tuchus!&lt;/p&gt;
&lt;p&gt;In the years since MongoDB 3.6, we could&amp;rsquo;ve documented this process better, and encouraged framework authors to build it into their application frameworks to ease the burden on developers. We didn&amp;rsquo;t do that. As far as I know, causal consistency is rarely used. It&amp;rsquo;s a shame, since it&amp;rsquo;s performant and conceptually simple.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not too late to make causal consistency popular. If you want to help (especially if you maintain a multi-tier app framework), please write to me! But I now guess that consistent secondary reads &lt;strong&gt;without&lt;/strong&gt; application logic are the real solution. I hope to research it later this year.&lt;/p&gt;
&lt;h1&gt;Further Reading&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/&#34;&gt;Read Isolation, Consistency, and Recency&lt;/a&gt; in the MongoDB Manual.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.acm.org/doi/10.1145/3299869.3314049&#34;&gt;Implementation of Cluster-wide Logical Clock and Causal Consistency in MongoDB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.acm.org/doi/10.14778/3352063.3352125&#34;&gt;Tunable consistency in MongoDB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hengxin.github.io/papers/2022-JCST-MongoDB-CCC.pdf&#34;&gt;Checking Causal Consistency of MongoDB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mongodb/specifications/blob/master/source/causal-consistency/causal-consistency.md&#34;&gt;The MongoDB driver specification for causal consistency&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.acm.org/doi/10.14778/3352063.3352140&#34;&gt;Adapting TPC-C Benchmark to Measure Performance of Multi-document Transactions in MongoDB&lt;/a&gt;, this paper uses causally-consistent reads in its benchmark.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Monks at Holy Cross, part 2</title>
      <link>https://emptysqua.re/blog/holy-cross-2/</link>
      <pubDate>Mon, 22 Apr 2024 16:26:06 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/holy-cross-2/</guid>
      
      
      
      
      <description>&lt;p&gt;April 15, 2024. Monks at &lt;a href=&#34;https://holycrossmonastery.com/&#34;&gt;Holy Cross&lt;/a&gt;, an Anglican Benedictine monastery in the Hudson Valley. See &lt;a href=&#34;https://emptysqua.re/blog/holy-cross/&#34;&gt;part 1 here&lt;/a&gt;.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-011.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-012.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-013.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-014.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-015.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-016.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-017.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-018.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross-2/holy-cross-2024-04-15-019.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Jyakuen&#39;s Shuso Hossen</title>
      <link>https://emptysqua.re/blog/jyakuen-shuso-hossen/</link>
      <pubDate>Sun, 24 Mar 2024 22:08:13 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/jyakuen-shuso-hossen/</guid>
      
      
      
      
      <description>&lt;p&gt;March 24, 2024. A shuso hossen ceremony at the &lt;a href=&#34;https://villagezendo.org&#34;&gt;Village Zendo&lt;/a&gt;. Sherri Jyakuen Posey gave her first dharma talk and became a senior Zen student.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-001.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-002.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-003.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-004.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-005.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-008.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/jyakuen-shuso-hossen/jyakuen-shuso-hossen-009.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Monks at Holy Cross</title>
      <link>https://emptysqua.re/blog/holy-cross/</link>
      <pubDate>Sat, 16 Mar 2024 10:19:46 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/holy-cross/</guid>
      
      
      
      
      <description>&lt;p&gt;March 5, 2024. Monks at &lt;a href=&#34;https://holycrossmonastery.com/&#34;&gt;Holy Cross&lt;/a&gt;, an Anglican Benedictine monastery in the Hudson Valley. See &lt;a href=&#34;https://emptysqua.re/blog/holy-cross-2/&#34;&gt;part 2 here&lt;/a&gt;.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-10.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-11.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-12.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-13.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-14.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/holy-cross/holy-cross-15.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Bodhisattvas Always Smile</title>
      <link>https://emptysqua.re/blog/bodhisattvas-always-smile/</link>
      <pubDate>Wed, 13 Mar 2024 22:42:22 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/bodhisattvas-always-smile/</guid>
      
      
      
      
      <description>&lt;p&gt;I gave a talk at the New Paltz Zen Center against doomerism. We vow to bear witness to all the suffering in the world, but there&amp;rsquo;s no reason to be gloomy about it. Let&amp;rsquo;s be cheerful bodhisattvas and enjoy our lives!&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/gE7E_reF8YU?si=QaayvgcrhOblEdzX&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen style=&#34;margin-bottom: 1em&#34;&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;p&gt;Here&amp;rsquo;s three images. When you read the news, or you think about the state of the world, which one are you? What&amp;rsquo;s your state of mind?&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/scream.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This is Edvard Munch&amp;rsquo;s &amp;ldquo;The Scream&amp;rdquo;, painted in 1893. There&amp;rsquo;s an emaciated figure, holding its head in its hands and its mouth is open like it&amp;rsquo;s howling, it&amp;rsquo;s in a disorienting, apocalyptic landscape with a red-streaked sky.&lt;/p&gt;
&lt;p&gt;Munch wrote about the inspiration for this painting: &amp;ldquo;One evening I was walking along a path, the city was on one side and the fjord below. I felt tired and ill. I stopped and looked out over the fjord – the sun was setting, and the clouds turning blood red. I sensed a scream passing through nature; it seemed to me that I heard the scream. I painted this picture, painted the clouds as actual blood. The color shrieked. This became &lt;em&gt;The Scream&lt;/em&gt;.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/dog-meme.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The second image is a comic strip. There&amp;rsquo;s a dog sitting at a table with a cup of coffee, and all around it the house is on fire, and a cloud of smoke hangs under the ceiling. The dog is wearing a little bowler hat, it has a stupid smile on its face, and it&amp;rsquo;s saying, &amp;ldquo;This is fine,&amp;rdquo; as huge flames close in around it.&lt;/p&gt;
&lt;p&gt;This was drawn in 2013 by an artist named K.C. Green for &lt;a href=&#34;https://gunshowcomic.com/648&#34;&gt;his webcomic&lt;/a&gt;, it&amp;rsquo;s part of a 6-panel comic where the dog catches fire as he says, &amp;ldquo;I&amp;rsquo;m okay with the events that are unfolding currently&amp;rdquo;, &amp;ldquo;that&amp;rsquo;s okay, things are going to be okay&amp;rdquo;. Then his face melts off.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/dog-full.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The first two panels became an Internet meme that people use to express, &amp;ldquo;You&amp;rsquo;re in denial. There&amp;rsquo;s a crisis of some sort and you should be panicking.&amp;rdquo;&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/avalokiteshvara-cave.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This is an image of Avalokiteshvara, &lt;a href=&#34;https://silkroadgallery.co.uk/products/avalokiteshvara-cave-painting-mustang&#34;&gt;painted on a cave wall in Nepal in the 12th Century&lt;/a&gt;. Avalokiteshvara&amp;rsquo;s name means literally &amp;ldquo;down-looking lord&amp;rdquo;. But, I learned that the name was originally Avalokitasvara—slightly different—and it meant &amp;ldquo;perceiving sound&amp;rdquo;, which is how we think of the Bodhisattva, as a being that hears the cries of the suffering world. This being was first mentioned in the Avatamsaka Sutra, written about 2000 years ago, where he appeared as male. In the Lotus Sutra, slightly later, Avalokiteshvara has many forms, both male and female.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/guanyin.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;In China he or she is called Guanyin and can be male or female. This statue of Guanyin is at the Met, it was made in the 10th or 11th Century in China.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/kanzeon.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Here&amp;rsquo;s a contemporary statue. Here she&amp;rsquo;s pouring the water of compassion from a container.&lt;/p&gt;
&lt;p&gt;In Japan, Guanyin is called Kanzeon or Kwanon or Kannon. The Canon camera company is named after her, by the way. Here&amp;rsquo;s an early Canon logo.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/canon-logo.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;What I want you to notice is, even though Kanzeon hears the cries of all the suffering beings of the world, she&amp;rsquo;s smiling. Or at least, she looks peaceful. At the &lt;a href=&#34;https://villagezendo.org&#34;&gt;Village Zendo&lt;/a&gt; we are all on the Bodhisattva Path, we chant the Bodhisattva Vows, bodhisattvas like Kanzeon are our role models. So let&amp;rsquo;s ask ourselves: when we hear the cries of the suffering beings, are we smiling, or not?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://villagezendo.org/2024/02/22/talk-by-zenshin-caplow-the-path-of-courage/&#34;&gt;Zenshin Caplow gave a terrific talk at the zendo a couple weeks ago&lt;/a&gt;, she focused on the &lt;em&gt;courage&lt;/em&gt; of a bodhisattva. Please listen to that talk, it&amp;rsquo;s super insightful. Today, I&amp;rsquo;m not talking about courage, but about the &lt;em&gt;cheerfulness&lt;/em&gt; of a bodhisattva!&lt;/p&gt;
&lt;p&gt;Consider those three images: The Scream, or the oblivious dog in the burning house, or smiling Kanzeon. Which way are you?&lt;/p&gt;
&lt;p&gt;I hear depression or exhaustion from a lot of people in the Village Zendo, and a lot of friends, and a lot of people who care about justice and democracy and the climate. What I don&amp;rsquo;t hear is people making it their goal to cheer up and enjoy their lives, despite the daily crises.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;m going to try to convince you to make the last image, the smiling Kanzeon, your role model. I want you to make it your goal to be cheerful and enjoy your life no matter what world events you hear about. I have three reasons why you should do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The world isn&amp;rsquo;t going to hell.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re a more effective bodhisattva if you&amp;rsquo;re cheerful.&lt;/li&gt;
&lt;li&gt;Emptiness.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Number one&lt;/strong&gt;, the world isn&amp;rsquo;t going to hell. Do you think it is? Do you think we&amp;rsquo;re all going to die of global warming, or people are starving, or that the world is getting more violent? You can slice statistics in infinite ways, but overall, life has improved for nearly everyone, and it&amp;rsquo;ll probably continue to improve for a long time.&lt;/p&gt;
&lt;p&gt;Global poverty is declining rapidly. There&amp;rsquo;s a statistic called the International Poverty Line, defined as living on less than $2.15 a day. The number of people below that line has decreased from nearly 40% of people in 1990 to less than 10% today, in other words the share of the population in extreme poverty is a quarter of what it was in 1990. In the same period infant mortality fell by half. Deaths from famine have become extremely rare compared to past decades.&lt;/p&gt;
&lt;p&gt;As I say this, I&amp;rsquo;m aware that people are dying of hunger in Gaza right now. I&amp;rsquo;m not minimizing that. The opposite: I&amp;rsquo;m saying we must act to end hunger everywhere, and we should be encouraged, knowing that it&amp;rsquo;s possible, because we&amp;rsquo;ve nearly accomplished it.&lt;/p&gt;
&lt;p&gt;Of course, the big negative trend is global warming. But it doesn&amp;rsquo;t mean the planet is going to burn up or we&amp;rsquo;re all going to die. The Intergovernmental Panel on Climate Change says the most likely scenario is 2 or 3 degrees Celsius of warming by the year 2100. That means we&amp;rsquo;re going to be hotter than the goal that they set, but not by too much, and it&amp;rsquo;s far better than the worst-case scenario. A lot of climate activists will cite scenarios where Manhattan is underwater and all of Africa is a desert, but that&amp;rsquo;s based on the IPCC&amp;rsquo;s worst-case scenario, and it&amp;rsquo;s now clear that won&amp;rsquo;t happen, partly because people are taking action! The US&amp;rsquo;s total carbon emissions, for example, have been falling since 2007. Plus, as countries get richer, they tend to use more efficient energy sources. And as countries get richer their population growth slows. The global population will probably peak in the year 2100 or sooner, and start shrinking after that, which should make it easier to reduce carbon emissions. Taking everything together, the IPCC projects that over the next century, despite global warming, global poverty will keep falling, hunger will be rarer, average lifespan will increase, standards of living will improve. Global warming is bad, but humanity&amp;rsquo;s progress continues anyway.&lt;/p&gt;
&lt;p&gt;Of course there are crises. The last few years have been fairly violent, with wars between Russia and Ukraine, and between Israel and Hamas. And there have been a few other wars. Just as an example of how distorted our view can be, the Tigray War in Ethopia a few years ago was much bigger than the Israel-Hamas war but it was hardly covered by US media.&lt;/p&gt;
&lt;p&gt;Kanzeon doesn&amp;rsquo;t lose her cool when a war breaks out. She knows there have been far more violent years in the 70s and 80s, and of course those years are dwarfed by WWI and WWII. Overall, wars between countries have become very rare compared to the 20th Century. And anyway, Kanzeon understands human nature, we&amp;rsquo;re prone to violence because we have greed, anger, and ignorance. So she expects there to be some wars from time to time. It doesn&amp;rsquo;t change her mission. Kanzeon just keeps working to wake us up.&lt;/p&gt;
&lt;p&gt;If you think the world is going to hell, that&amp;rsquo;s your view, it&amp;rsquo;s arising in your mind. The world seems to be going to hell because of what you choose to focus on, and that is probably influenced by how you consume the news. And the news is influenced by media companies&amp;rsquo; desparate need for your attention, it&amp;rsquo;s what they need to survive. Of course they focus on the negative! Global poverty keeps falling year after year, but media companies know that stories about continuous improvement don&amp;rsquo;t generate clicks. Be aware of this distortion and try to correct it, seek out good news, and seek out information about big, long-term trends, not the brief crises of the day.&lt;/p&gt;
&lt;p&gt;Kanzeon sees the whole world over the long term, that&amp;rsquo;s why she keeps smiling through each crisis.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Number two&lt;/strong&gt;, you&amp;rsquo;re a more effective bodhisattva if you&amp;rsquo;re cheerful.&lt;/p&gt;
&lt;p&gt;If we imagine that today&amp;rsquo;s wars are unusually bad, it can seem like we&amp;rsquo;re losing the struggle for peace, and that&amp;rsquo;s discouraging. Or if we think we&amp;rsquo;re all doomed on a burning planet, that&amp;rsquo;s super discouraging. I hear people say there&amp;rsquo;s no point bringing children into a doomed world. Have children or don&amp;rsquo;t, I have no opinion, but make an informed decision: their lives will probably be better than ours. If we&amp;rsquo;re aware of the progress we&amp;rsquo;re making, towards peace, towards a carbon-neutral economy, towards global prosperity, that encourages us to keep trying!&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a story about Kanzeon, which of course has many versions, so here&amp;rsquo;s mine: Once upon a time, early in Kanzeon&amp;rsquo;s career, he had just one head and two arms. But he worked furiously hard and liberated countless beings from suffering. He paused, he looked around, and the number of suffering beings still seemed overwhelming. He was so frustrated his head exploded. Buddha put his head back together in a different form: it became 11 faces. A peaceful face to calmly gaze in each of the 10 directions, so Kanzeon wouldn&amp;rsquo;t lose track again. And on top of these 10 faces, Buddha placed an 11th face, a wrathful face for the suffering beings who need a kick in the ass to liberate them.&lt;/p&gt;
&lt;p&gt;So Kanzeon went back into action. But trying to save all beings at once, he moved his arms so fast they shattered. So Buddha did some magical upgrades, and gave Kanzeon 1000 arms, and in each of his 1000 palms Buddha placed an eye, so Kanzeon could give individual attention to every being simultaneously.&lt;/p&gt;
&lt;p&gt;But you and I are not 11-faced and 1000-armed. Generally we have one face each, and at most two arms. If I tried to respond to every crisis simultaneously, my head would explode and my arms would shatter. I need to remember that this body, this brain, is just one of Kanzeon&amp;rsquo;s hands and eyes. Or maybe I&amp;rsquo;m one of her fingers, maybe I&amp;rsquo;m one of her cells. I can&amp;rsquo;t do it all. So I focus where I think I&amp;rsquo;m effective: I lead the sangha at Sing Sing, &lt;a href=&#34;https://emptysqua.re/blog/tithing-ten-percent/&#34;&gt;I donate 10% of my income to GiveWell&lt;/a&gt;, which directs it to the most effective charities, like vitamins and vaccines to people in Africa. I support the Village Zendo and &lt;a href=&#34;https://npzc.org&#34;&gt;New Paltz Zen Center&lt;/a&gt;. I focus on a few things, and I&amp;rsquo;m satisfied. I am doing enough!&lt;/p&gt;
&lt;p&gt;Kanzeon responds to all the cries of the world, no one and nothing is left out, and my actions are a part of that.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m doing enough, I don&amp;rsquo;t read too much news. I don&amp;rsquo;t need to know the ins and outs of US politics. I don&amp;rsquo;t need to follow the daily events of every war. It&amp;rsquo;s okay. I need to be informed, but I don&amp;rsquo;t need daily information to do my bodhisattva work. If I got daily information it would be bad for me.&lt;/p&gt;
&lt;p&gt;If you take one piece of advice from this whole talk, it&amp;rsquo;s this: turn off news alerts on your phone. If Shakyamuni Buddha were here today, he&amp;rsquo;d make all his monks and nuns turn off their news alerts before he shaved their heads. It&amp;rsquo;s essential.&lt;/p&gt;
&lt;video controls style=&#34;width: 100%; margin-bottom: 1em&#34;&gt;
  &lt;source src=&#34;https://emptysqua.re/blog/bodhisattvas-always-smile/RPReplay_Final1709084436.mov&#34; type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;
&lt;p&gt;If you have an iPhone, here&amp;rsquo;s how to do it. Open Settings, search for whatever news app is sending you alerts, like maybe the NYTimes app, then tap Notifications and turn them off. If you have an Android or a smartwatch, I&amp;rsquo;m sure it&amp;rsquo;s equally easy. It is so important that you do this.&lt;/p&gt;
&lt;p&gt;You might be wondering, how can a bodhisattva ignore the daily news? What about bearing witness? Bearing witness is one of the &lt;a href=&#34;https://www.upaya.org/zen/liturgy/peacemaking/&#34;&gt;tenets of the Zen Peacemaker Order&lt;/a&gt;, which we belong to. We occasionally recite these tenets and vow to follow them. So am I allowed to focus on a few things, and turn off the news? Yes I am! Bearing witness is &lt;em&gt;intimate&lt;/em&gt;. It&amp;rsquo;s being fully present with someone. You have to enter their world and experience it with them. You can&amp;rsquo;t do that if you&amp;rsquo;re being interrupted by New York Times headlines. The headlines are a media company&amp;rsquo;s interpretation of the world. Bearing witness is a direct experience. The two things could not be more different. If you turn off the news it gives you space to bear witness, and I find that when I do bear witness, it fulfills me and gives me courage to act, cheerfully!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So that was number two, a cheerful bodhisattva is an effective bodhisattva. &lt;strong&gt;My third and final argument&lt;/strong&gt; why you should try to be cheerful is: emptiness. Nothing is separate or solid, it only briefly appears that way. Beings appear and disappear constantly, like waves rise up from the ocean and fall back into it. Fairly soon, everyone in this room and this Zoom call will die. Later, the sun&amp;rsquo;s fusion reaction will use up all its hydrogen, and it will start fusing helium instead; it will heat up and destroy the earth. If humanity escapes the earth, we could survive a while longer, but some day the whole universe will run out of energy and become cold and silent and everything will die.&lt;/p&gt;
&lt;p&gt;What I like about Buddhism is that we face the facts. We practice accepting death. Unlike the oblivious dog saying &amp;ldquo;this is fine&amp;rdquo;, we do see the flames and we know what&amp;rsquo;s going to happen. But we accept it, because the beings that appear and disappear are just waves in the ocean. They are empty of meaning as individuals, they are inseparable parts of the magnificent whole. The knowledge of this fact is called &lt;em&gt;prajna paramita&lt;/em&gt;, &amp;ldquo;wisdom perfected&amp;rdquo;. As the Heart Sutra tells us, Avalokiteshvara practices prajna paramita! The paradox of being a bodhisattva is to value each individual and care about their well-being, without ever forgetting that we are each just a momentary expression of the whole shebang, never really separate. When we see this, our own peace of mind is stable, it doesn&amp;rsquo;t depend on anything, certainly not on the daily news cycle.&lt;/p&gt;
&lt;p&gt;As Samantabadhra Bodhisattva says in the Avatamsaka Sutra:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Even though the void of space, together with the states of beings, with the karmas of beings, with the sorrows of beings, though all these will end, yet my approval and joy in the merits of all beings will not end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Samantabadhra isn&amp;rsquo;t depressed by the thought of the end of the world, actually he sounds ecstatic, because he knows his joy doesn&amp;rsquo;t depend on anything but his vow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So I&amp;rsquo;ve argued that the bodhisattva path is cheerful action and enjoying our lives. The bodhisattva vow isn&amp;rsquo;t vowing to be depressed about the state of the world, it&amp;rsquo;s vowing to be like Kanzeon—both in our attitude and in our action. Think again about those three images, the scream, the oblivious dog, the smiling bodhisattva. If you feel more like screaming than smiling, ask yourself: what about my practice is having this result? What might I do differently to have a different result?&lt;/p&gt;
&lt;p&gt;The hardest news for me to handle, lately, is the likelihood that Trump will be reëlected. But I do not intend to be anxious about it. My method is, instead of obsessively reading news coverage, I&amp;rsquo;m just doing what I can. Keishin and I will lead a fundraiser for Biden this fall, we did this in 2020 and helped raise tens of thousands of dollars, and we&amp;rsquo;ll do that again. And I&amp;rsquo;ll take September and October off work and look for ways to volunteer for Democrats. My local congressman is a Democrat. We&amp;rsquo;re in a swing district here, he won by one percent last year, so working for his reëlection is a high-leverage way to make a difference. Electing Democrats seems like the most effective way to combat global warming and protect human rights. We all know Democrats are far from perfect, but it looks like the bodhisattva path to me.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s my experience that obsessing about politics is depressing, but participating in politics is really fun and satisfying. So I&amp;rsquo;ll do what I can during this campaign, and then I&amp;rsquo;ll move on and enjoy my life. A bodhisattva is never stuck, a bodhisattva&amp;rsquo;s action is always flowing.&lt;/p&gt;
&lt;p&gt;If Trump wins, I don&amp;rsquo;t intend to be depressed about it. I&amp;rsquo;ll keep up with the news as much as is useful, and that&amp;rsquo;s it. I&amp;rsquo;ll cheerfully work to contain the damage however I can, and I&amp;rsquo;ll move on and enjoy my life. I won&amp;rsquo;t get stuck, and I won&amp;rsquo;t fall apart.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;ll leave you with a verse by Enkyo Roshi, from her book &amp;ldquo;A Little Bit of Zen&amp;rdquo;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We can’t lose our heads.&lt;br&gt;
Oh, Bodhisattva!&lt;br&gt;
Losing your head doesn’t help—&lt;br&gt;
Instead, extend your hand.&lt;br&gt;
Offer your words, your wisdom, your effort.&lt;br&gt;
That’s what a Bodhisattva does, day in and day out.&lt;br&gt;
Serving all of life.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Ordination at Christ Episcopal</title>
      <link>https://emptysqua.re/blog/christ-episcopal-ordination/</link>
      <pubDate>Mon, 11 Mar 2024 22:43:28 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/christ-episcopal-ordination/</guid>
      
      
      
      
      <description>&lt;p&gt;March 5, 2024. An ordination ceremony for Emily Carter at Christ Episcopal Church in Poughkeepsie.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2015.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2030.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2036.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2039.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2050.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2144.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2153.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/christ-episcopal-ordination/ajdavis_20240309_2167.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Climbing in Puerto Rico</title>
      <link>https://emptysqua.re/blog/2024-puerto-rico-climbing/</link>
      <pubDate>Thu, 15 Feb 2024 16:16:36 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/2024-puerto-rico-climbing/</guid>
      
      
      
      
      <description>&lt;p&gt;February 2024. Photos of my friends (and one friendly stranger) sport-climbing on the limestone crags near San Juan (&lt;a href=&#34;https://www.mountainproject.com/area/106361084/monagas-aka-nuevo-bayamon&#34;&gt;Monagas&lt;/a&gt; and &lt;a href=&#34;https://www.mountainproject.com/area/118295627/flying-coconut-ciales&#34;&gt;Flying Coconut&lt;/a&gt;).&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/2024-puerto-rico-climbing/pr-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Review: Antipode: Enforcing Cross-Service Causal Consistency in Distributed Applications</title>
      <link>https://emptysqua.re/blog/review-antipode/</link>
      <pubDate>Mon, 29 Jan 2024 21:02:14 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-antipode/</guid>
      
      
      
      
      <description>&lt;p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-antipode/ptolemy-map.png&#34; style=&#34;display:inline;width:auto;height:auto;vertical-align:middle&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;em&gt;&lt;a href=&#34;https://collections.leventhalmap.org/search/commonwealth:3f462s124&#34;&gt;Ptolemy Map, 1482&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 2015 some Facebook researchers &lt;a href=&#34;https://research.facebook.com/publications/challenges-to-adopting-stronger-consistency-at-scale/&#34;&gt;threw down a gauntlet&lt;/a&gt;, challenging anyone who dared to provide stronger consistency in big, heterogeneous systems. In 2023, some researchers (mostly Portuguese) responded with &lt;a href=&#34;https://www.dpss.inesc-id.pt/~rodrigo/antipode-full.pdf&#34;&gt;Antipode: Enforcing Cross-Service Causal Consistency in Distributed Applications&lt;/a&gt;. Antipode defines an interesting new consistency model, &lt;em&gt;cross-service causal consistency&lt;/em&gt;, and an enforcement technique they claim is practical in such systems.&lt;/p&gt;
&lt;h1&gt;Motivating Example&lt;/h1&gt;

&lt;p&gt;Here&amp;rsquo;s an example from the paper. A social network is composed of services:&lt;/p&gt;
&lt;p&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-antipode/example.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-antipode/example.excalidraw.png&#34;&gt;
&lt;/picture&gt;
&lt;em&gt;My simplification of the paper&amp;rsquo;s Figure 2&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This system has the following problematic workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Author uploads a post; it&amp;rsquo;s received by the Post-Upload service in Region A.&lt;/li&gt;
&lt;li&gt;Post-Upload sends the post to Post-Storage,&lt;/li&gt;
&lt;li&gt;&amp;hellip; which saves the post to a local replica of its datastore.&lt;/li&gt;
&lt;li&gt;Post-Upload tells the post-id to Notifier,&lt;/li&gt;
&lt;li&gt;&amp;hellip; which saves the post it a local replica of its (separate) datastore.&lt;/li&gt;
&lt;li&gt;Both datastores eventually replicate to Region B in arbitrary order.&lt;/li&gt;
&lt;li&gt;In Region B, as soon as Notifier replicates the notification,&lt;/li&gt;
&lt;li&gt;&amp;hellip; it triggers Follower-Notify,&lt;/li&gt;
&lt;li&gt;&amp;hellip; which retrieves the post from Post-Storage,&lt;/li&gt;
&lt;li&gt;&amp;hellip; which retrieves it from its local replica of its datastore.&lt;/li&gt;
&lt;li&gt;Once Follower-Notify has the post, it relays it to Follower.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The problem, as astute readers can predict, is if Notifier&amp;rsquo;s datastore replicates sooner than Post-Storage&amp;rsquo;s. In that case, Follower-Notify will learn about the post too soon; it&amp;rsquo;ll try to retrieve it from Post-Storage but the post won&amp;rsquo;t be there yet.&lt;/p&gt;
&lt;p&gt;This is a consistency violation of some sort—the paper will define it exactly, in a moment. We could prevent the anomaly by making all datastores replicate &lt;em&gt;synchronously&lt;/em&gt;. In that case, once Post-Storage has acknowledged storing the post in Region A, it has &lt;em&gt;also&lt;/em&gt; replicated it to Region B, so it&amp;rsquo;s certainly there by the time Follower-Notify tries to retrieve it. But this kills all parallelism. Maybe there&amp;rsquo;s a better way?&lt;/p&gt;
&lt;h1&gt;Causal Consistency Isn&amp;rsquo;t Enough&lt;/h1&gt;

&lt;p&gt;What about Lamport-style causal consistency with &lt;a href=&#34;https://lamport.azurewebsites.net/pubs/time-clocks.pdf&#34;&gt;logical clocks&lt;/a&gt;? This wouldn&amp;rsquo;t prevent the anomaly. The paper doesn&amp;rsquo;t explain in detail why, so I&amp;rsquo;ll try.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://en.wikipedia.org/wiki/Lamport_timestamp#Algorithm&#34;&gt;Lamport&amp;rsquo;s algorithm&lt;/a&gt;, each process has a clock value (perhaps just an integer), which is incremented and propagated whenever processes exchange messages. Lamport clocks could prevent the anomaly if we had &lt;em&gt;one&lt;/em&gt; replicated datastore:&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-antipode/causal-consistency.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-antipode/causal-consistency.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;ol&gt;
&lt;li&gt;Post-Storage in Region A saves the post, gets a Lamport clock value of 42 from the datastore.&lt;/li&gt;
&lt;li&gt;Post-Storage in Region A &lt;em&gt;directly&lt;/em&gt; notifies Post-Storage in Region B and tells it the clock value.&lt;/li&gt;
&lt;li&gt;Post-Storage in Region B reads from its replica of the datastore. It tells the datastore to wait until it&amp;rsquo;s replicated up to clock value 42 before executing the query:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Many datastores (&lt;a href=&#34;https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/&#34;&gt;including MongoDB&lt;/a&gt;) support causal consistency this way, and it would prevent the anomaly described above. This doesn&amp;rsquo;t work in the example from the paper&amp;rsquo;s Figure 2, however. The problem is, there are two datastores replicating &lt;em&gt;concurrently&lt;/em&gt; in Figure 2. Causal consistency is a only a partial order, not total; it allows the Post-Storage&amp;rsquo;s or the Notifier&amp;rsquo;s datastore to replicate first. With multiple replicated datastores, the anomaly is allowed by causal consistency, so we have to define a stricter consistency level.&lt;/p&gt;
&lt;h1&gt;Cross-service Causal Consistency&lt;/h1&gt;

&lt;p&gt;The Antipode authors define a new consistency level that prohibits the anomaly: &amp;ldquo;cross-service causal consistency&amp;rdquo;. They abbreviate it &amp;ldquo;XCY&amp;rdquo;, which perhaps makes sense in Portuguese. Cross-service causal consistency includes several ideas:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lineage&lt;/strong&gt;: A DAG of operations, partially ordered with Lamport&amp;rsquo;s &amp;ldquo;happens-before&amp;rdquo; relation. A lineage begins with some &amp;ldquo;start&amp;rdquo; operation, such as a client request or a cron job, and proceeds until each branch completes with a &amp;ldquo;stop&amp;rdquo; operation.&lt;/p&gt;
&lt;p&gt;In Figure 2 there are two lineages: one is spawned when Author uploads a post. I&amp;rsquo;ll call this Lineage A. It has two branches (leading to Post-Storage and Notifier), and it includes concurrent replication of two datastores to Region B. The other lineage, which I&amp;rsquo;ll call Lineage B, is spawned when Follower-Notify in Region B receives the notification. Lineage B then reads from Post-Storage in Region B, and notifies Follower.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-antipode/lineages.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-antipode/lineages.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;The authors use a data set from Alibaba, where lineages are hairy: &amp;ldquo;User requests typically form a tree, where more than 10% of stateless microservices fan out to at least five other services, and where the average call depth is greater than four. Additionally, this tree contains, on average, more than five stateful services,&amp;rdquo; i.e. services with datastores.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads-from-lineage&lt;/strong&gt;: An operation &lt;em&gt;b&lt;/em&gt; reads-from-lineage &lt;em&gt;L&lt;/em&gt; if &lt;em&gt;b&lt;/em&gt; reads a value written by an operation in &lt;em&gt;L&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-service causal order&lt;/strong&gt;: This is denoted with the squiggly arrow ⤳. For two operations &lt;em&gt;a&lt;/em&gt; and &lt;em&gt;b&lt;/em&gt;, if &lt;em&gt;a&lt;/em&gt; happens-before &lt;em&gt;b&lt;/em&gt; or &lt;em&gt;b&lt;/em&gt; reads-from-lineage &lt;em&gt;L&lt;/em&gt;, where &lt;em&gt;L&lt;/em&gt; includes &lt;em&gt;a&lt;/em&gt;, then &lt;em&gt;a&lt;/em&gt; ⤳ &lt;em&gt;b&lt;/em&gt;. Cross-service causal order is a transitive partial order, like happens-before.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;XCY&lt;/strong&gt;: This is the paper&amp;rsquo;s new consistency level. An execution obeys XCY if you can find a serial order of operations obeying cross-service causal order.&lt;/p&gt;
&lt;p&gt;XCY is the consistency level that Figure 2 violates! When Follower-Notify tries to read Author&amp;rsquo;s post in Region B, that should happen &lt;em&gt;after&lt;/em&gt; all the events in Lineage A, including the replication of the post to Region B.&lt;/p&gt;
&lt;h2&gt;My Feelings About Lineages&lt;/h2&gt;

&lt;p&gt;I feel uncomfortable, as if there&amp;rsquo;s a purer mathematical concept obscured by the specifics of microservice architectures. Why are the borders between Lineages A and B drawn where they are? Could we split these operations into more than two lineages, or combine them into one?&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; that a lineage is a general concept (&amp;ldquo;a DAG of operations&amp;rdquo;), but Antipode finds it convenient for microservice architectures to split lineages thus: Operations in a lineage are connected by happens-before. When a service reads a value from storage, this operation does &lt;em&gt;not&lt;/em&gt; join the lineage that wrote the value. Instead, it&amp;rsquo;s connected by reads-from-lineage. The goal of &amp;ldquo;cross-service causal consistency&amp;rdquo; is to make a partial order of lineages, such that replicated data stores appear not-replicated. (I was confused about this until I read the &lt;a href=&#34;https://www.dpss.inesc-id.pt/~rodrigo/antipode-full.pdf&#34;&gt;paper&amp;rsquo;s appendix&lt;/a&gt;. You should read the appendix, too.)&lt;/p&gt;
&lt;p&gt;I think there&amp;rsquo;s a more general idea of &amp;ldquo;recursive&amp;rdquo; or &amp;ldquo;nested&amp;rdquo; causal consistency trying to be born. This general idea would include lineages, defined however you want, and lineages could contain nested lineages. Cross-service causal consistency is a specialization of this general idea.&lt;/p&gt;
&lt;h2&gt;Tracking And Enforcing Cross-Service Causal Consistency&lt;/h2&gt;

&lt;p&gt;This paper describes a system for enforcing XCY, called &amp;ldquo;Antipode&amp;rdquo;, which means &amp;ldquo;&lt;a href=&#34;https://en.wikipedia.org/wiki/Antipodes&#34;&gt;opposite side&lt;/a&gt;&amp;rdquo;; maybe this refers to end-to-end consistency guarantees across geographic regions. Or maybe it &lt;a href=&#34;https://en.wikipedia.org/wiki/Abarimon#/media/File:Schedel&#39;sche_Weltchronik-Reverse_feet.jpg&#34;&gt;refers to mythical beings with reversed feet&lt;/a&gt; for some reason.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-antipode/reverse-feet.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Anyway, whenever services exchange messages as part of the regular functioning of the application, Antipode piggybacks lineage information. Since microservice architectures already piggyback info for distributed tracing, Antipode doesn&amp;rsquo;t add much coding-time or runtime burden. Additionally, Antipode places shims in front of all datastores; the shims add lineage information to reads and writes. (Antipode borrows the technique from &lt;a href=&#34;http://www.bailis.org/papers/bolton-sigmod2013.pdf&#34;&gt;Bolt-on Causal Consistency&lt;/a&gt;.) Lineage info accumulates along causal branches within a lineage, and gets dropped whenever a branch ends.&lt;/p&gt;
&lt;p&gt;Developers can customize lineage tracking; they can explicitly add or remove dependencies. If one lineage depends on another in a way that Antipode doesn&amp;rsquo;t detect, a developer can transfer lineage info between them.&lt;/p&gt;
&lt;p&gt;(MongoDB drivers let you transfer causality info between sessions, too, although it&amp;rsquo;s basically undocumented; &lt;a href=&#34;https://emptysqua.re/blog/how-to-use-mongodb-causal-consistency/&#34;&gt;I explain it here&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Antipode could enforce XCY automatically, on each read operation, but instead it provides an explicit &lt;code&gt;barrier&lt;/code&gt; operation that developers must call to wait for dependencies to be satisfied. This seems error-prone, but it sometimes permits developers to reduce latency by carefully choosing where to place their &lt;code&gt;barrier&lt;/code&gt; calls. The authors write,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One argument that can be made against barrier is that it is as explicit as today&amp;rsquo;s application-level solutions, since both of them require the developer to manually select its locations. What makes Antipode&amp;rsquo;s approach better suited is not only barrier, but its combination with the implicit/explicit dependency tracking, which keeps services loosely coupled and does not require end-to-end knowledge of what to enforce.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This bit about &amp;ldquo;loose coupling&amp;rdquo; is insightful: you can place your &lt;code&gt;barrier&lt;/code&gt; call somewhere, and if you later add dependencies, &lt;code&gt;barrier&lt;/code&gt; will enforce them without code changes. On the other hand, having one &lt;code&gt;barrier&lt;/code&gt; call for all dependencies requires you to wait for all of them at once, including those you don&amp;rsquo;t need yet.&lt;/p&gt;
&lt;p&gt;How does &lt;code&gt;barrier&lt;/code&gt; know how long to wait?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Antipode&amp;rsquo;s &lt;code&gt;barrier&lt;/code&gt; API call enforces the visibility of a lineage. It takes a lineage as an argument and will block until all writes contained in the lineage are visible in the underlying datastores. Internally, a &lt;code&gt;barrier&lt;/code&gt; will inspect the write identifiers in the lineage and contact the corresponding datastores. For each datastore, barrier will call the datastore-specific &lt;code&gt;wait&lt;/code&gt; API, which will block until the write identifier is visible in that datastore. Note that &lt;code&gt;wait&lt;/code&gt; is datastore-specific because visibility depends on the design choices and consistency model of the underlying datastore. Once &lt;code&gt;wait&lt;/code&gt; has returned for all identifiers in the lineage, &lt;code&gt;barrier&lt;/code&gt; will return.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In our example, this means that before Follower-Notify retrieves Author&amp;rsquo;s post from Post-Storage, it calls &lt;code&gt;barrier&lt;/code&gt;, which queries the Post-Storage datastore and waits until it&amp;rsquo;s sufficiently up-to-date.&lt;/p&gt;

&lt;picture&gt;
    &lt;source srcset=&#34;https://emptysqua.re/blog/review-antipode/barrier.excalidraw.svg&#34; type=&#34;image/svg+xml&#34;&gt;
    &lt;img style=&#34;max-width:100%&#34; src=&#34;https://emptysqua.re/blog/review-antipode/barrier.excalidraw.png&#34;&gt;
&lt;/picture&gt;&lt;p&gt;This is an extra round trip (red arrow) even if the datastore is &lt;em&gt;already&lt;/em&gt; up-to-date. I think this could be optimized away with something like &lt;a href=&#34;https://www.mongodb.com/docs/manual/reference/read-concern/#read-operations-and-afterclustertime&#34;&gt;MongoDB&amp;rsquo;s afterClusterTime&lt;/a&gt;, but Antipode&amp;rsquo;s API would have to change. Luckily, you can limit the consistency check to nearby replicas:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We implemented a practical optimization strategy specifically tailored for geo-replicated datastores. This involves implementing the wait procedure to enforce dependencies only from replicas that are co-located with its caller, thereby avoiding (whenever the underlying datastore allows it) global enforcement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t fully understand &lt;code&gt;barrier&lt;/code&gt; from the paper&amp;rsquo;s description. If it&amp;rsquo;s waiting for all writes from Lineage A to be visible in Region B, how does it know about writes that Lineage A hasn&amp;rsquo;t even &lt;em&gt;started&lt;/em&gt; yet? Must it wait for all branches of Lineage A to finish? If so, how? And what if an operation in Lineage A crashes or hangs?&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;

&lt;p&gt;The authors evaluate Antipode with three benchmarks and a dozen brands of datastore, and ask 1) would there be XCY violations without Antipode, and 2) what is the cost of preventing them? The answer to question 1 is yes. For question 2:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lineage info adds 200 bytes to 14 kb per message in the authors&amp;rsquo; benchmarks (developers might need to explicitly prune lineages in their own systems).&lt;/li&gt;
&lt;li&gt;Waiting for consistency increases latency, by definition.&lt;/li&gt;
&lt;li&gt;Enforcing XCY decreases throughput by 2-15%.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;Cross-service causal consistency is a neat concept. The chief argument for it, buried in the middle of the paper, is &lt;em&gt;decoupling&lt;/em&gt;: it permits microservices to read consistently from multiple replicated data stores, without knowing the details of the microservices that wrote to them. This limits the impact of changes to any part of your system. With Death Star architectures like Alibaba or AWS, decoupling is crucial.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-antipode/aws-deathstar.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://twitter.com/Werner/status/741673514567143424&#34;&gt;The AWS microservices &amp;ldquo;death star&amp;rdquo; in 2008&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Antipode&amp;rsquo;s API is higher-level than manually enforcing cross-service dependencies. I think it could be useful as part of an even higher-level &amp;ldquo;cloud programming language&amp;rdquo; that automatically decomposes, distributes, and parallelizes high-level logic, while detecting consistency requirements and enforcing them. I&amp;rsquo;m aware of cloud programming language projects like &lt;a href=&#34;https://www.unison-lang.org/&#34;&gt;Unison&lt;/a&gt;, &lt;a href=&#34;https://www.winglang.io/&#34;&gt;Wing&lt;/a&gt;, &lt;a href=&#34;https://spritely.institute/news/spritely-goblins-v010-for-guile-and-racket.html&#34;&gt;Goblins&lt;/a&gt;, &lt;a href=&#34;https://muratbuffalo.blogspot.com/2023/05/new-directions-in-cloud-programming.html&#34;&gt;Hydro&lt;/a&gt;, &lt;a href=&#34;https://www2.eecs.berkeley.edu/Pubs/TechRpts/2009/EECS-2009-173.pdf&#34;&gt;Dedalus&lt;/a&gt;, &lt;a href=&#34;https://www.cs.cornell.edu/andru/papers/gallifrey/snapl.pdf&#34;&gt;Gallifrey&lt;/a&gt;, and so on. They&amp;rsquo;re at various stages of development and levels of abstraction. If this paper&amp;rsquo;s definition of lineage were generalized to encompass more kinds of causal relations among operations, it could express the constraints of a variety of constructs in high-level cloud programming languages, and something like Antipode could enforce them.&lt;/p&gt;
&lt;h1&gt;Further Reading&lt;/h1&gt;

&lt;p&gt;Other systems that strengthen the consistency of existing systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.usenix.org/conference/osdi20/presentation/shi&#34;&gt;FlightTracker: Consistency across Read-Optimized Online Stores at Facebook&lt;/a&gt;, and &lt;a href=&#34;https://charap.co/reading-group-flighttracker-consistency-across-read-optimized-online-stores-at-facebook/&#34;&gt;Aleksey Charapko&amp;rsquo;s summary&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.bailis.org/papers/bolton-sigmod2013.pdf&#34;&gt;Bolt-on Causal Consistency&lt;/a&gt;, and &lt;a href=&#34;https://blog.acolyer.org/2015/09/01/bolt-on-causal-consistency/&#34;&gt;Adrian Colyer&amp;rsquo;s summary&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Review: Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency</title>
      <link>https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/</link>
      <pubDate>Thu, 25 Jan 2024 11:35:03 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/vanitas.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;&lt;a href=&#34;https://www.andrew.cmu.edu/course/15-440/assets/READINGS/gray1989.pdf&#34;&gt;Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency&lt;/a&gt;, Cary G. Gray and David R. Cheriton, 1989. Old and good. I read this simple paper because it seems to be ground zero for timed leases in distributed systems, in which I&amp;rsquo;m now intensely interested.&lt;/p&gt;
&lt;h1&gt;The Protocol&lt;/h1&gt;

&lt;p&gt;The authors discuss a distributed file system, e.g. for a network of &lt;a href=&#34;https://en.wikipedia.org/wiki/Diskless_node&#34;&gt;diskless workstations&lt;/a&gt; connected to a shared file server. (The file server itself is a single machine, not replicated. Yes, it&amp;rsquo;s a single point of failure. This is 1989.) The workstations can read or write files by exchanging messages with the server.&lt;/p&gt;
&lt;p&gt;Each workstation is a write-through cache: when it reads a file, it caches it in its local RAM for future reads.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/read.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;When a workstation updates files in its RAM, it synchronously updates the server&amp;rsquo;s copy.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/write.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Of course, all sorts of race conditions can cause inconsistency. Workstation A could read and cache a file, then workstation B updates it, then workstation A re-reads the file from its now-stale cache and sees outdated data.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/stale-read.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The authors want to provide consistency: &amp;ldquo;By consistent, we mean that the behaviour is equivalent to there being only a single (uncached) copy of the data except for the performance benefit of the cache.&amp;rdquo; Their solution is a timed lease.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A cache using leases requires a valid lease on the datum (in addition to holding the datum) before it returns the datum in response to a read, or modifies the datum in response to a write. When a datum is fetched from the server (the primary storage site of the datum), the server also returns a lease guaranteeing that the data will not be written by any client during the lease term unless the server first obtains the approval of this leaseholder. If the datum is read again within the term of the lease (and the datum is still in the cache), the cache provides immediate access to the datum without communicating with the server. After the lease expires, a read of the datum requires that the cache first extend the lease on the datum, updating the cache if the datum has been modified since the lease expired. When a client writes a datum, the server must defer the request until each leaseholder has granted approval or the term of its lease has expired.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So before Workstation B can write to a file, it must acquire from the server a lease on that file, with a timeout that will expire some time in the future. Workstation B completes its writes while holding the lease, and either extends the lease or allows it to expire. When Workstation A then re-reads the file from its cache, it must get a &lt;em&gt;new&lt;/em&gt; lease on it, thus discovering that the file has changed, and refresh its copy from the server. After this, Workstation A can keep reading the file until its lease expires. Meanwhile, no other workstation can modify it.&lt;/p&gt;
&lt;p&gt;The server itself can read and write files, and it needs a lease to do so, the same as a workstation.&lt;/p&gt;
&lt;p&gt;It seems that the system grants multiple shared leases for reading, or one exclusive lease for writing. The authors don&amp;rsquo;t say so, but they imply it. E.g., the passage above mentions &amp;ldquo;each leaseholder&amp;rdquo;, indicating there could be more than one per file. These days a paper like this would crush all ambiguity with pseudocode, a TLA+ specification, a formal proof, and a link to an open-source implementation on GitHub. I guess in 1989 you could mumble a few pages about leases and the ACM would publish it.&lt;/p&gt;
&lt;p&gt;If a workstation or the server wants access to a file that&amp;rsquo;s already leased to another workstation, the lease-wanter can wait, or ask the leaseholder to relinquish the lease early. A leaseholder can proactively relinquish an unexpired lease that it doesn&amp;rsquo;t need anymore.&lt;/p&gt;
&lt;p&gt;If many nodes want a lease on the same file, the server enqueues them. The paper doesn&amp;rsquo;t specify the scheduling policy, except to mention in a footnote that writers take priority.&lt;/p&gt;
&lt;h1&gt;Fault Tolerance&lt;/h1&gt;

&lt;p&gt;If a workstation gets disconnected from the other nodes, it might have crashed, or it might be partitioned and still reading from its cache any files for which it has valid leases. Thus to guarantee consistency, the server must wait for the disconnected workstation&amp;rsquo;s leases to expire before it can grant more on the same files. Since the workstation is practically stateless, crash recovery is trivial.&lt;/p&gt;
&lt;p&gt;If the server crashes and restarts, it must remember all the leases it granted before. It could durably record each lease it grants, but that might make disk I/O a bottleneck. The authors propose that the server durably records only the maximum expiration time. When it restarts, it waits for that maximum expiration to pass before granting new leases.&lt;/p&gt;
&lt;p&gt;The protocol is resilient to delayed or lost messages, but it does require all nodes&amp;rsquo; clocks to advance at the same speed, modulo a small, known epsilon.&lt;/p&gt;
&lt;h1&gt;Optimal Lease Terms&lt;/h1&gt;

&lt;p&gt;Short leases minimize recovery time after a workstation or server failure. They also minimize &amp;ldquo;false sharing&amp;rdquo;: when a node has to wait for a lease, although the leaseholder is no longer using it. Long leases reduce lease-requesting traffic and latency at the workstations.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/latency.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The large portion of the paper constructs an analytical model of these tradeoffs. I&amp;rsquo;ll ignore this section since it&amp;rsquo;s fairly specific to the authors&amp;rsquo; system. Plus &lt;a href=&#34;https://emptysqua.re/blog/review-queue-theory-book/&#34;&gt;I&amp;rsquo;m generally skeptical of analytic performance modeling, compared to simulation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s an interesting discussion, however, of the optimal lease terms for different sorts of files and workstations. The server should grant longer leases to more distant workstations to compensate for network latency. Operating system files are very frequently read and almost never written, so the server should grant very long read leases on them. In fact, it should just grant bulk read leases on whole &lt;em&gt;directories&lt;/em&gt; of OS files, and proactively issue read lease extensions to all workstations so long as no write to the OS files is pending. On the other hand, the server should grant short leases on a frequently-written file. The authors say &amp;ldquo;a heavily write-shared file might be given a lease term of zero&amp;rdquo;, which I don&amp;rsquo;t understand—how can a workstation use a lease that&amp;rsquo;s already expired by the time the workstation knows it has acquired it? Anyway, the authors conclude, &amp;ldquo;a server can dynamically pick lease terms on a per file and per client cache basis using the analytic model, assuming the necessary performance parameters am monitored by the server.&amp;rdquo;&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;

&lt;p&gt;The authors evaluate performance not with a real-life test, but by applying their analytic model to some real-world data. They use a trace of file accesses from one workstation to one file server, while the workstation recompiled a program. There&amp;rsquo;s no contention in the trace (since there&amp;rsquo;s one workstation), so they simulate various levels of contention in their evaluation. Network latencies were measured in separate tests, then fed to the model. This is so many steps removed from reality, it wouldn&amp;rsquo;t fly in a modern research journal. However, I &lt;em&gt;do&lt;/em&gt; appreciate this abstracted approach; it can be more revealing than testing a fully-implemented system with its adventitious complexity and noise.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-leases-for-distributed-file-cache-consistency/evaluation.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The authors measure server load (i.e., the number of messages it must process) for different numbers of workstations (the &amp;ldquo;sharing level&amp;rdquo;, S) and different lease terms. They set the server load to 1 with 1 workstation and zero-term leases, and measure other configurations relative to that one. The workload is 96% reads, so longer terms reduce server load because workstations can mostly read from cache. I&amp;rsquo;d be curious to see latency at the workstations, too. I&amp;rsquo;d expect longer terms to decrease latency in this read-heavy workload, and increase it in a write-heavy workload.&lt;/p&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;A short and sweet paper, worth reading for historical interest. If I publish a paper about leases I&amp;rsquo;ll need to know what&amp;rsquo;s in this one so I can cite it. I wish they had described their protocol more precisely, perhaps in pseudocode, before they rushed to model it analytically and draw charts. But all is forgiven: this is the paper that coined the term &amp;ldquo;lease&amp;rdquo; and introduced the world to an elegant consistency technique.&lt;/p&gt;
&lt;p&gt;See also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.acolyer.org/2014/10/31/leases-an-efficient-fault-tolerant-mechanism-for-distributed-file-cache-consistency/&#34;&gt;Review by the Morning Paper&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Murat Demirbas, &lt;a href=&#34;https://muratbuffalo.blogspot.com/2019/09/do-leases-buy-us-anything.html&#34;&gt;Do leases buy us anything?&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Image: &lt;a href=&#34;https://www.wga.hu/html_m/c/collier/vanitas6.html&#34;&gt;Edwart Collier, 1662&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Review: Exploiting a Natural Network Effect for Scalable, Finegrained Clock Synchronization</title>
      <link>https://emptysqua.re/blog/review-huygens-paper/</link>
      <pubDate>Mon, 22 Jan 2024 16:30:27 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-huygens-paper/</guid>
      
      
      
      
      <description>&lt;p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-huygens-paper/Christiaan_Huygens-painting.jpeg&#34; style=&#34;display:inline;width:auto;height:auto;vertical-align:middle&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;em&gt;Christiaan Huygens by Caspar Netscher, 1671.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is a review of &lt;a href=&#34;https://www.usenix.org/system/files/conference/nsdi18/nsdi18-geng.pdf&#34;&gt;Exploiting a Natural Network Effect for Scalable, Finegrained Clock Synchronization&lt;/a&gt;, from Stanford and Google researchers. It describes Huygens, a protocol for very accurate clock synchronization, plus (an undersold benefit) very accurate measurements of one-way network latency. I read it because the Huygens protocol is used by &lt;a href=&#34;https://emptysqua.re/blog/review-nezha/&#34;&gt;Nezha, which I reviewed last week&lt;/a&gt;. The Huygens and Nezha papers share two authors.&lt;/p&gt;
&lt;h1&gt;Motivation&lt;/h1&gt;

&lt;p&gt;As Barbara Liskov wrote in 1991, there are &lt;a href=&#34;https://dl.acm.org/doi/pdf/10.1145/112600.112601&#34;&gt;practical uses of clocks in distributed systems&lt;/a&gt;, such as establishing an order of events on different servers without communication. In an especially insightful paragraph, the authors write:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In order to achieve &lt;a href=&#34;https://cloud.google.com/spanner/docs/true-time-external-consistency&#34;&gt;external consistency&lt;/a&gt;, a write-transaction in &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf&#34;&gt;Spanner&lt;/a&gt; has to wait out the clock uncertainty period, &lt;em&gt;T&lt;/em&gt;, before releasing locks on the relevant records and committing. Spanner can afford this wait time because &lt;em&gt;T&lt;/em&gt; is comparable to the delay of the two-phase-commit protocol across globally distributed data centers. However, for databases used by real-time, single data center applications, the millisecond-level clock uncertainty would fundamentally limit the database’s write latency, throughput and performance. Thus, if a low latency database, for example, &lt;a href=&#34;https://web.stanford.edu/~ouster/cgi-bin/papers/ramcloud-tocs.pdf&#34;&gt;RAMCloud&lt;/a&gt;, were to provide external consistency by relying on clock synchronization, it would be critical for &lt;em&gt;T&lt;/em&gt; to be in the order of 10s of nanoseconds so as not degrade the performance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;NTP, the usual clock-sync protocol, is only accurate to a few milliseconds. More accurate protocols require specialized hardware. The Huygens protocol (&amp;ldquo;HOY-gons&amp;rdquo;, named for &lt;a href=&#34;https://en.wikipedia.org/wiki/Christiaan_Huygens&#34;&gt;the inventor of the pendulum clock&lt;/a&gt;) gives nanosecond accuracy in ordinary data centers. The authors summarize it thus: &amp;ldquo;First, coded probes identify and reject impure probe data (data captured by probes which suffer queuing delays, random jitter, and NIC timestamp noise). Next, Huygens processes the purified data with Support Vector Machines, a widely-used and powerful classifier, to accurately estimate one-way propagation times and achieve clock synchronization to within 100 nanoseconds. Finally, Huygens exploits a natural network effect (the idea that a group of pair-wise synchronized clocks must be transitively synchronized) to detect and correct synchronization errors even further.&amp;rdquo;&lt;/p&gt;
&lt;h2&gt;The Buddy System&lt;/h2&gt;

&lt;p&gt;Clock-sync protocols like NTP estimate network round-trip time (RTT) thus: server A sends a message called a &amp;ldquo;probe&amp;rdquo; to server B, which responds with an &amp;ldquo;ack&amp;rdquo;. Both messages are timestamped by the sending and receiving network interface cards (NICs). Server A averages the durations between probes and acks to estimate RTT. But some messages experience random queueing delays. Huygens wants to find probe-ack pairs that passed between the servers in minimum time without queueing delays; the authors call these &amp;ldquo;pure&amp;rdquo; probes. They use only pure probes for RTT and clock-skew estimation.&lt;/p&gt;
&lt;p&gt;Huygens distinguishes pure and impure probes with a crafty little algorithm: Server A sends a probe, waits a small time &lt;em&gt;s&lt;/em&gt; according to A&amp;rsquo;s clock, then sends a second probe. (For some reason these probe pairs are called &amp;ldquo;coded&amp;rdquo; probes.) If the time between the probes on the receiving server B is very close to &lt;em&gt;s&lt;/em&gt;, Huygens calls both probes pure. (The duration &lt;em&gt;s&lt;/em&gt; is small enough that the two servers&amp;rsquo; differential clock drift is irrelevant.)&lt;/p&gt;
&lt;h2&gt;The Forbidden Zone&lt;/h2&gt;

&lt;p&gt;Huygens uses pure probes to calculate the largest and smallest possible clock discrepancies between servers. For example, if server A sends a probe at time 0 according to A&amp;rsquo;s clock, and B receives the probe at time 5 according to B&amp;rsquo;s clock, B&amp;rsquo;s clock can&amp;rsquo;t be more than 5 units ahead of A&amp;rsquo;s: otherwise the message would&amp;rsquo;ve arrived before it was sent, in absolute time. Thus a message from A to B reveals the &lt;em&gt;upper&lt;/em&gt; bound of B&amp;rsquo;s clock skew relative to A&amp;rsquo;s.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-huygens-paper/message.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;A message going the opposite direction reveals the &lt;em&gt;lower&lt;/em&gt; bound.&lt;/p&gt;
&lt;p&gt;Any of these numbers could be negative: if the message leaves from A at time 0 and arrives at B at time -5, B&amp;rsquo;s clock can&amp;rsquo;t be more than -5 units ahead of A&amp;rsquo;s (i.e. it must be at least 5 units behind).&lt;/p&gt;
&lt;p&gt;Even &amp;ldquo;pure&amp;rdquo; probes exhibit some random variation in latency. Quicker probes give tighter bounds: if the message leaves at time 0 and arrives at time 4, we&amp;rsquo;d know B&amp;rsquo;s clock can&amp;rsquo;t be more than 4 units ahead of A&amp;rsquo;s. But we don&amp;rsquo;t know how much of that difference is clock skew and how much is network latency—not yet.&lt;/p&gt;
&lt;p&gt;Over the course of seconds, as the servers&amp;rsquo; clocks drift towards or away from each other, the bounds change, producing this difficult and delightful chart:&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://emptysqua.re/blog/review-huygens-paper/clock-bounds.png&#34; style=&#34;display:inline;width:auto;height:auto;vertical-align:middle&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;em&gt;Figure 4 from the paper.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The quickest messages give the tightest bounds, visible as dense blue and green lines. Slowdowns seem to be randomly distributed, making sparse fields of blue dots above the least upper bound, and green dots below the greatest lower bound.&lt;/p&gt;
&lt;p&gt;The handful of dots &lt;em&gt;between&lt;/em&gt; the bounds are in the &amp;ldquo;forbidden zone&amp;rdquo;. They &lt;em&gt;seem&lt;/em&gt; to be unusually quick messages that provide tighter bounds than the other dots, but it&amp;rsquo;s a lie. In fact, they&amp;rsquo;re an artifact of NIC timestamp noise: when a server sends a message, there&amp;rsquo;s an occasional delay of 10s or 100s of nanoseconds before it records the transmit timestamp, making the transmission time seem shorter than it is. This is small enough that these probes are considered &amp;ldquo;pure&amp;rdquo;, but large enough to hurt Huygens&amp;rsquo;s accuracy. Huygens uses a very well-known statistical method called a &lt;a href=&#34;https://en.wikipedia.org/wiki/Support_vector_machine&#34;&gt;support vector machine&lt;/a&gt; to find the dense lines that border the forbidden zone and filter out the samples inside it.&lt;/p&gt;
&lt;h2&gt;Detecting Asymmetric Delays&lt;/h2&gt;

&lt;p&gt;Other protocols like NTP assume symmetric network delays: they assume the one-way delay (OWD) from server A to server B is practically the same as vice versa. The Huygens authors find that this is mostly true in their data centers, but slight asymmetries violate this assumption enough to hurt clock synchronization. They exploit a natural network effect (hence the paper&amp;rsquo;s title): additional servers help detect asymmetries. For example (from the paper), say that servers A and B think that A&amp;rsquo;s clock is 20 units ahead of B&amp;rsquo;s, but due to asymmetry they&amp;rsquo;re wrong, A&amp;rsquo;s clock is only 10 units ahead of B&amp;rsquo;s:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-huygens-paper/a-to-b.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;If these were the only servers, the error would be undetectable. But say there&amp;rsquo;s a server C that thinks it&amp;rsquo;s 15 units behind B and 5 units ahead of A:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-huygens-paper/three-servers.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This is impossible, because summing the offsets A→B→C→A we find a &amp;ldquo;loop offset surplus&amp;rdquo; of 10 units (20 + -15 + 5). Huygens evenly distributes the surplus among the pairwise offsets, improving accuracy. In this example, the offset from A to B is now estimated to be 16.7; not the accurate value of 10, but better than the wrong value of 20.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-huygens-paper/three-servers-corrected.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;In a real data center, &amp;ldquo;each server probes 10-20 others, regardless of the total number of servers in the network&amp;rdquo;, and Huygens analyzes the entire graph of pairwise synchronized clocks.&lt;/p&gt;
&lt;h1&gt;Their Evaluation&lt;/h1&gt;

&lt;p&gt;The authors built two testbeds, each with several racks and switches, and dozens of servers. One testbed represents, according to the authors, a &amp;ldquo;state-of-art data center&amp;rdquo;, and the other a &amp;ldquo;low-end commodity data center&amp;rdquo;. The two testbeds have different timestamping features in their NICs, and different network latencies.&lt;/p&gt;
&lt;p&gt;I was wondering how they&amp;rsquo;d evaluate clock synchronization—after all, the problem they&amp;rsquo;re trying to solve is the lack of a perfect clock to compare to. They evaluate pairwise synchronization with a &lt;a href=&#34;https://netfpga.org/NetFPGA-CML.html&#34;&gt;NetFPGA-CML board&lt;/a&gt;, which has four ethernet ports and one clock. They attach two separate VMs to two of the ports, and connect them to the rest of the servers in the low-end testbed. The two VMs are banned from talking directly to each other, they must sync their clocks via intermediate servers. Since the actual clock discrepancy between these VMs is zero, the authors can measure how closely Huygens has synchronized their clocks. They get a mean error of 13.4 ns and a 99th-percentile error of 30.2 ns; this is tiny, and the low variance is especially impressive.&lt;/p&gt;
&lt;p&gt;There are additional evaluations that show that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If each server probes &lt;em&gt;K&lt;/em&gt; other servers, clock error falls as &lt;em&gt;K&lt;/em&gt; grows.&lt;/li&gt;
&lt;li&gt;Network load hurts synchronization, but not too much: a few probes can still get through without delay, and Huygens correctly identifies these as &amp;ldquo;pure&amp;rdquo; and uses them for clock sync.&lt;/li&gt;
&lt;li&gt;Huygens is orders of magnitude better than NTP.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;My Evaluation&lt;/h1&gt;

&lt;p&gt;The results in this paper seem exceptionally useful for distributed systems. Huygens appears to achieve incredibly tight clock synchronization, plus two features that the paper undersells: Huygens also measures the clock error &lt;em&gt;bounds&lt;/em&gt;, and the one-way delay between servers. These three measurements could enable countless futuristic distributed protocols. Furthermore, the explanations in the paper are thorough and clear. The only part I found intimidating was their description of the loop-analysis algorithm, but I think that&amp;rsquo;s inherently complex.&lt;/p&gt;
&lt;p&gt;Unfortunately, I can&amp;rsquo;t tell from the paper whether Huygens works in public clouds. Do customers like me have the required access to NIC timestamps? Do we need it? If Huygens works in public clouds, I wish this paper had evaluated it there. The Huygens code is closed-source, so third parties can&amp;rsquo;t test it themselves.&lt;/p&gt;
&lt;p&gt;Several of the authors founded &lt;a href=&#34;https://www.clockwork.io/&#34;&gt;Clockwork&lt;/a&gt;, which &amp;ldquo;runs on all major clouds&amp;rdquo;, synchronizes clocks, and measures clock error bounds and one-way delays. Clockwork claims &amp;ldquo;sync accuracy as low as 5 – 10 ns with hardware timestamps, 100s of ns – a few μs with software timestamps.&amp;rdquo; It looks to me like they ported Huygens to public clouds using software timestamps, and its accuracy there is reduced but still great.&lt;/p&gt;
&lt;h1&gt;See Also&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www-users.cselabs.umn.edu/Fall-2021/csci8980-ec/papers/Time.pdf&#34;&gt;Quartz: Time-as-a-Service for Coordination in Geo-Distributed Systems&lt;/a&gt;, &lt;a href=&#34;https://bitbucket.org/sandeepdsouza93/quartz/&#34;&gt;it&amp;rsquo;s open source&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&#34;https://www.usenix.org/conference/osdi20/presentation/li-yuliang&#34;&gt;Sundial paper&lt;/a&gt; and &lt;a href=&#34;https://muratbuffalo.blogspot.com/2021/03/sundial-fault-tolerant-clock.html&#34;&gt;Murat&amp;rsquo;s summary&lt;/a&gt;. It focuses on a fault-tolerant, fast-recovering time sync service.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ciechanow.ski/mechanical-watch/&#34;&gt;This spectacular explanation of mechanical wristwatches&lt;/a&gt; is irrelevant, but read it; you&amp;rsquo;ll be forever changed.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Review: Nezha: Deployable and High-Performance Consensus Using Synchronized Clocks</title>
      <link>https://emptysqua.re/blog/review-nezha/</link>
      <pubDate>Sun, 14 Jan 2024 08:25:53 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/review-nezha/</guid>
      
      
      
      
      <description>
&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/nezha-conquers-the-dragon-king.jpg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;This is a review of &lt;a href=&#34;https://www.vldb.org/pvldb/vol16/p629-geng.pdf&#34;&gt;Nezha: Deployable and High-Performance Consensus Using Synchronized Clocks&lt;/a&gt;, from NYU and Stanford researchers last year. To understand this paper I had to relearn how quorums work in Paxos and Fast Paxos, so we&amp;rsquo;ll start there.&lt;/p&gt;


&lt;h1&gt;Classic Paxos Quorums&lt;/h1&gt;

&lt;p&gt;In Paxos there are proposers, acceptors, and learners. Each server usually plays all three roles, but the protocol&amp;rsquo;s often described as if the roles are separate (confusingly, &lt;a href=&#34;https://emptysqua.re/blog/python-paxos-jepsen/&#34;&gt;in my opinion&lt;/a&gt;). The protocol is like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A client sends value &lt;em&gt;v&lt;/em&gt; to a proposer.&lt;/li&gt;
&lt;li&gt;The proposer sends &amp;ldquo;prepare&amp;rdquo; to the acceptors, with a unique current round number.&lt;/li&gt;
&lt;li&gt;The proposer hears &amp;ldquo;promise&amp;rdquo; replies from a majority of acceptors.&lt;/li&gt;
&lt;li&gt;The proposer knows that only it can propose a value for this round.&lt;/li&gt;
&lt;li&gt;The proposer sends &amp;ldquo;accept&amp;rdquo; to the acceptors with the value &lt;em&gt;v&lt;/em&gt; for this round.&lt;/li&gt;
&lt;li&gt;A learner hears &amp;ldquo;learn&amp;rdquo; messages from a majority of acceptors, it knows &lt;em&gt;v&lt;/em&gt; is the value for this round.&lt;/li&gt;
&lt;li&gt;The learner sends an acknowledgment to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Real systems use MultiPaxos, in which the &amp;ldquo;prepare&amp;rdquo;/&amp;ldquo;promise&amp;rdquo; exchange is done once to establish a &amp;ldquo;distinguished proposer&amp;rdquo;, which then drives a series of &amp;ldquo;accept&amp;rdquo;/&amp;ldquo;learn&amp;rdquo; exchanges indefinitely. &lt;a href=&#34;https://emptysqua.re/blog/paxos-vs-raft/&#34;&gt;Optimized MultiPaxos is nearly the same as Raft, it&amp;rsquo;s just explained much worse&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Fast Paxos Quorums&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Before any proposals, a proposer sends &amp;ldquo;accept any&amp;rdquo; to the acceptors with &lt;em&gt;no&lt;/em&gt; value for this round.&lt;/li&gt;
&lt;li&gt;The proposer hears &amp;ldquo;promise&amp;rdquo; replies from a majority of acceptors.&lt;/li&gt;
&lt;li&gt;Fast track: the client sends value &lt;em&gt;v&lt;/em&gt; &lt;u&gt;directly&lt;/u&gt; to the acceptors!&lt;/li&gt;
&lt;li&gt;A learner hears &amp;ldquo;learn&amp;rdquo; messages from 3/4 of the acceptors, with the same value.&lt;/li&gt;
&lt;li&gt;The learner sends an acknowledgment to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fast Paxos saves a single one-way message delay compared to regular Paxos. But it risks collisions: several clients can send several values to the acceptors in the same round. Fast Paxos uses a larger &amp;ldquo;fast quorum&amp;rdquo; size to check for collisions. If no value is chosen by a fast quorum, the system goes into a slow error-recovery mode. Fast Paxos is safe—it will never accept multiple values in a round—but its performance is brittle; it&amp;rsquo;s slow under contention.&lt;/p&gt;
&lt;h1&gt;Quorum Sizes&lt;/h1&gt;

&lt;p&gt;A classic Paxos quorum must be a majority, so that any two quorums &lt;em&gt;Q&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; and &lt;em&gt;Q&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt; share at least one node. For example with 9 acceptors, a quorum is at least 5:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/classic-quorum.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Thus if a minority of acceptors fails, at least one survivor remembers accepting &lt;em&gt;v&lt;/em&gt;. (If a majority of acceptors fails, the system won&amp;rsquo;t accept more values.)&lt;/p&gt;
&lt;p&gt;The rule for fast quorums is, any classic quorum &lt;em&gt;Q&lt;/em&gt; must share at least one node with any &lt;em&gt;two&lt;/em&gt; fast quorums &lt;em&gt;R&lt;/em&gt;&lt;sub&gt;0&lt;/sub&gt; and &lt;em&gt;R&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;. With 9 acceptors, a classic quorum is at least 5 as before, and a fast quorum is at least 7:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/fast-quorum.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;If a fast quorum accepts &lt;em&gt;v&lt;/em&gt; and then a minority of acceptors fails, at least one survivor remembers &lt;em&gt;v&lt;/em&gt;, and no other value could&amp;rsquo;ve been accepted by a fast or classic quorum. In classic Paxos, &amp;ldquo;no other value&amp;rdquo; is guaranteed by the &amp;ldquo;prepare&amp;rdquo; phase, where one proposer hears a majority promise to stop accepting other proposers&amp;rsquo; values with earlier round numbers. But in Fast Paxos all the failed acceptors could&amp;rsquo;ve accepted some &lt;em&gt;other&lt;/em&gt; value. We need a bigger fast quorum to know that we&amp;rsquo;ve chosen a unique fault-tolerant value.&lt;/p&gt;
&lt;p&gt;There are various quorum sizes that satisfy this property, and there are tradeoffs when you choose a quorum size. See &lt;a href=&#34;https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-2005-112.pdf&#34;&gt;Lamport&amp;rsquo;s Fast Paxos paper&lt;/a&gt; for details.&lt;/p&gt;
&lt;h1&gt;Nezha&lt;/h1&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/nezha-god.jpeg&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Now I can describe the Nezha paper. As author Jinkun Geng mentions in &lt;a href=&#34;https://disseminatepodcast.podcastpage.io/episode/jinkun-geng-nezha-deployable-and-high-performance-consensus-using-synchronized-clocks-42&#34;&gt;a podcast interview&lt;/a&gt;, Nezha is a Chinese diety with three heads and six arms, &amp;ldquo;so he has wonderful fault-tolerance.&amp;rdquo;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nezha bridges the gap between protocols such as MultiPaxos and Raft, which can be readily deployed, and protocols such as NOPaxos and Speculative Paxos, that provide better performance, but require access to technologies such as programmable switches and in-network prioritization, which cloud tenants do not have.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the authors&amp;rsquo; motivation is to make a high-performance consensus protocol that can be deployed by cloud customers in public clouds, without requiring special access to the hardware. Nezha improves performance using tightly-synchronized clocks, which increasingly &lt;em&gt;are&lt;/em&gt; available to cloud tenants, &lt;a href=&#34;https://aws.amazon.com/blogs/compute/its-about-time-microsecond-accurate-clocks-on-amazon-ec2-instances/&#34;&gt;especially on AWS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nezha is like Fast Paxos plus Deadline-Ordered Multicast, speculative execution, and a stateless proxy.&lt;/p&gt;
&lt;h2&gt;Deadline-Ordered Multicast (DOM)&lt;/h2&gt;

&lt;p&gt;Just like Fast Paxos, Nezha has a fast path and a slow path, and it&amp;rsquo;s crucial to take the fast path as often as possible. The authors say that message reordering is the most common reason for taking the slow path: a sequence of messages from the proxy take different network paths to the same server and arrive out of order. DOM reduces reordering thus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The sender attaches a deadline to each message: the sender&amp;rsquo;s clock time + one-way-latency estimate.&lt;/li&gt;
&lt;li&gt;The receiver rejects any message received after its deadline.&lt;/li&gt;
&lt;li&gt;The receiver executes each message after its deadline passes (according to the receiver&amp;rsquo;s clock).&lt;/li&gt;
&lt;li&gt;The receiver executes messages in deadline order.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The authors evaluate DOM in Google Cloud:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/reordering-score.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The reordering score is the percent of messages in a sequence that are not in the longest ordered subsequence. E.g., if there are 100 messages and the longest ordered subsequence is 80 messages long, the reordering score is 20. Higher is worse. Without DOM, they found in Google&amp;rsquo;s cloud, under high contention, that the reordering score was over 40%.&lt;/p&gt;
&lt;p&gt;DOM estimates the distribution of one-way latencies, using &lt;a href=&#34;https://www.usenix.org/system/files/conference/nsdi18/nsdi18-geng.pdf&#34;&gt;a technique called Huygens from another paper&lt;/a&gt;. Once it knows the distribution of one-way latencies, it knows the 50th-percentile one-way latency, the 75th, and so on. So the &amp;ldquo;50p&amp;rdquo; bar in the chart above means the authors configured DOM to set a deadline that was the sender&amp;rsquo;s clock plus the 50th-percentile one-way-latency. A longer delay further reduces reordering, but the receiver waits longer on average before executing each message. So the delay is a tunable parameter; there are tradeoffs and some optimum that you have to find.&lt;/p&gt;
&lt;p&gt;I was surprised to read that the Nezha authors choose the 50th percentile. This means half of messages arrive after their deadlines! However, not all late messages force Nezha to take the slow path, only messages that are late &lt;em&gt;and&lt;/em&gt; out of order. If a sequence of messages are all late, they can still be ordered. As the chart indicates, configuring DOM so that half of messages are late reduces the reordering score to barely 20%.&lt;/p&gt;
&lt;h2&gt;Speculative Execution&lt;/h2&gt;

&lt;p&gt;In regular Paxos, servers don&amp;rsquo;t execute a client&amp;rsquo;s command (they don&amp;rsquo;t update their state machines) until they know the command has been logged by a quorum. But the Nezha leader executes and acknowledges a command as soon as its deadline passes. The client accepts the execution result once it hears confirmation from a fast quorum. This reduces latency. If the leader is deposed before committing the command, the client rejects the result and retries the command.&lt;/p&gt;
&lt;h2&gt;Stateless Proxy&lt;/h2&gt;

&lt;p&gt;Nezha includes a proxy that encapsulates some Nezha logic. The proxy runs the Huygens protocol to estimate one-way latency and to tightly synchronize its clock with the other proxies and the servers. The proxy is basically stateless and horizontally scalable; it isn&amp;rsquo;t responsible for any ordering guarantees. Smart proxies permit dumb clients.&lt;/p&gt;
&lt;h2&gt;The Nezha Protocol&lt;/h2&gt;

&lt;h3&gt;Fast Path&lt;/h3&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/nezha-fast.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;First a request comes from a client.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The proxy assigns a deadline from the proxy&amp;rsquo;s reasonably-synchronized clock, plus a delay that&amp;rsquo;s a percentile of the one-way-latency estimate. The proxy sends the message to all the servers.&lt;/li&gt;
&lt;li&gt;The leader and followers get the message, and if its deadline hasn&amp;rsquo;t passed, the message goes in their &amp;ldquo;early&amp;rdquo; buffers to wait. This is the fast path, if the message is late we take the slow path, which I&amp;rsquo;ll describe later.&lt;/li&gt;
&lt;li&gt;Soon after the deadline, each server removes the message from the early buffer and logs it. Servers process messages in deadline order.&lt;/li&gt;
&lt;li&gt;The leader executes the command&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;and returns the result to the proxy. The followers send acknowledgments to the proxy without any result, because they don&amp;rsquo;t have state machines and they don&amp;rsquo;t execute commands, they only have logs. (So how can a follower become a leader? Read the paper.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each acknowledgment includes a hash of the whole log, so the proxy knows whether all the servers in the quorum have the same log. We know the messages are ordered correctly by timestamp, but some servers could be missing messages.&lt;/p&gt;
&lt;p&gt;If the proxy hears a fast quorum of replies with the same hash, including from the leader, it accepts the result. It knows it&amp;rsquo;s durable.&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s the fast path. It saves some latency, because the proxy sends the message to all servers at once, and all servers respond directly to the proxy, instead of routing messages through the leader like in MultiPaxos or Raft. It might incur some latency though, if you find that you have to configure the DOM delay to a high percentile.&lt;/p&gt;
&lt;p&gt;What about the slow path? Let&amp;rsquo;s look especially at messages that arrive too late.&lt;/p&gt;
&lt;h3&gt;Slow Path&lt;/h3&gt;


&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/nezha-slow.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;A request comes from a client again.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The proxy assigns a deadline and sends it to the servers, the same as before.&lt;/li&gt;
&lt;li&gt;This time the message arrives late and goes to the late buffer.&lt;/li&gt;
&lt;li&gt;The leader eventually modifies the message&amp;rsquo;s deadline, from a past time to a future time, and puts it in the early buffer!&lt;/li&gt;
&lt;li&gt;Once the new deadline passes, the leader logs&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;and executes the message&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip; and sends the result to the proxy.&lt;/li&gt;
&lt;li&gt;Concurrently, the leader also sends the message&amp;rsquo;s ID and its new deadline&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;to the followers. Note how this is slower than the fast path: it requires leader-follower communication, unlike the fast path.&lt;/li&gt;
&lt;li&gt;Luckily the followers don&amp;rsquo;t need the whole message, they have it in their late buffers, so they just retrieve it from there and log it in the proper position with its new deadline. If a follower didn&amp;rsquo;t receive the message at all, then it has to fetch it from another server, which is even slower.&lt;/li&gt;
&lt;li&gt;Finally the followers acknowledge the message&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;and the proxy hears from a &lt;em&gt;majority&lt;/em&gt; (a slow quorum) of servers, all with the same hash, and accepts the result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The authors claim that the slow path is still faster than some competing protocols like MultiPaxos, because of speculative execution at the leader. They say the slow path is only one message delay slower than the fast path.&lt;/p&gt;
&lt;p&gt;Of course, a message could arrive before its deadline on some servers, and late on other servers. There are worse cases, if messages are dropped between the proxy and the servers, or between the leader and the followers, or if a replica fails and rejoins, or there&amp;rsquo;s a new leader. The paper handles these scenarios and I will not.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll summarize everything so far: Typical consensus protocols route through the leader to guarantee ordering, but this costs some network hops and makes the leader a bottleneck. Nezha uses Deadline-Ordered Multicast and large quorums to guarantee ordering, so it can parallelize more.&lt;/p&gt;
&lt;h3&gt;Commutativity Optimization&lt;/h3&gt;

&lt;p&gt;Messages are &lt;em&gt;commutative&lt;/em&gt; if they contain commands operating on different keys. Nezha relaxes the rule for a message to enter the early buffer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Naïve rule: the message&amp;rsquo;s deadline must be after the last message released from the early buffer.&lt;/li&gt;
&lt;li&gt;Optimized rule: its deadline must be after the last non-commutative released message.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I see how this preserves per-key linearizability, but I think it violates whole-database linearizability, also known as strict serializability. That&amp;rsquo;s ok, I believe Nezha only promises per-key linearizability, and it&amp;rsquo;s the right choice for many users.&lt;/p&gt;
&lt;h2&gt;Their Evaluation&lt;/h2&gt;

&lt;p&gt;The authors ran experiments in Google Cloud with various configurations. I&amp;rsquo;ll concentrate on the open-loop workload with 3 replicas, 5 proxies, and 10 clients. The Huygens clock sync protocol is installed on the proxies and servers, it gets a p99 clock error of only 50 ns! They use a minimal application that processes messages with no command logic. They have Poisson arrivals, 50% reads/writes, and a somewhat skewed access pattern with some hot keys and some cold ones.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/open-loop-workload.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;As expected, Nezha outperforms everything, because this is a paper about Nezha. But this is an even more dramatic chart than usual in evaluation sections.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.usenix.org/system/files/conference/osdi16/osdi16-li.pdf&#34;&gt;NOPaxos&lt;/a&gt; (&amp;ldquo;Network-Order Paxos&amp;rdquo;) might be at a disadvantage here, because NOPaxos wants low-level access to the network and they don&amp;rsquo;t have it in Google&amp;rsquo;s public cloud. The Nezha authors say &amp;ldquo;we use the implementation from the NOPaxos repository with necessary modification: we change switch multicast into multiple unicasts because switch multicast is unavailable in cloud. We use a software sequencer with multi-threading for NOPaxos because tenant-programmable switches are not yet available in cloud.&amp;rdquo; NOPaxos-Optim is their enhancement of the published NOPaxos code; they relieved a bottleneck with multithreading. The authors also wrote a &lt;a href=&#34;https://arxiv.org/pdf/2206.03285.pdf&#34;&gt;technical report&lt;/a&gt; with more benchmarks; NOPaxos-Optim outperforms Nezha in one test there.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To measure latency, we use median latency because it is more robust to heavy tails. We attempted to measure tail latency at the 99th and 99.9th percentile. But we find it hard to reliably measure these tails because tail latencies within a cloud zone can exceed a millisecond.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I want to see Nezha&amp;rsquo;s tail latency, and I don&amp;rsquo;t understand this explanation for omitting it. &lt;a href=&#34;https://charap.co/cloudy-forecast-how-predictable-is-communication-latency-in-the-cloud/&#34;&gt;Cloud network latencies are indeed unpredictable&lt;/a&gt;, which is a big risk for a protocol like Nezha, which is optimistic and explicitly designed for public clouds. The authors&amp;rsquo; justification sounds to me like, &amp;ldquo;This problem is so bad we decided to ignore it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The paper includes an &amp;ldquo;ablation study&amp;rdquo;, a neat bit of jargon for studying the effects of removing optimizations individually.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/review-nezha/ablation.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Deadline-Ordered Multicast is obviously important. Without it (red line), Nezha is practically the same protocol as MultiPaxos and performs about the same: it&amp;rsquo;s usually on the slow path due to message reordering. &amp;ldquo;Quorum-Check Offloading&amp;rdquo; to the proxy or client is important, because it removes some work from the leader, which is otherwise a bottleneck (gray). It seems that the leader&amp;rsquo;s CPU is saturated and quorum-checking contributes to that. Commutativity is apparently not important for throughput and it only slightly improves latency (purple vs. green). Perhaps that&amp;rsquo;s because DOM is so good at message ordering that commutativity doesn&amp;rsquo;t help much, or perhaps it&amp;rsquo;s because their workload skewness means many messages are non-commutative?&lt;/p&gt;
&lt;h2&gt;My Evaluation&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The paper is written for users like me and &lt;a href=&#34;https://www.mongodb.com/atlas/database&#34;&gt;MongoDB Atlas&lt;/a&gt;: public cloud users without special hardware or network access. I appreciate this focus.&lt;/li&gt;
&lt;li&gt;I worry about performance variability in an optimistic protocol with fast and slow paths. How much does Nezha suffer when there&amp;rsquo;s contention and/or clock skew?&lt;/li&gt;
&lt;li&gt;The authors write, &amp;ldquo;Nezha does not assume the existence of a worst-case clock error bound&amp;rdquo;, but that&amp;rsquo;s just for safety. Performance does rely on tightly synchronized clocks. Ever since &lt;a href=&#34;https://sigops.org/s/conferences/hotos/2021/papers/hotos21-s11-bronson.pdf&#34;&gt;Metastable Failures in Distributed Systems&lt;/a&gt;, I dislike optimizations that work &lt;em&gt;sometimes&lt;/em&gt;. Therefore I wish they&amp;rsquo;d benchmarked tail latency, not just median latency, and tested more adverse conditions like workload skewness, clock skew, and network latency variability.&lt;/li&gt;
&lt;li&gt;Deadline-Ordered Multicast is slick, and useful. If it&amp;rsquo;s tuned, it should be nearly free. But how does DOM fare when latencies to different nodes differ?&lt;/li&gt;
&lt;li&gt;The commutativity optimization is clever, although its usefulness is overshadowed by DOM here.&lt;/li&gt;
&lt;li&gt;Some of my colleagues were annoyed that the Nezha authors don&amp;rsquo;t credit &lt;a href=&#34;https://arxiv.org/abs/2104.01142&#34;&gt;the Tempo paper&lt;/a&gt; and other precedents.&lt;/li&gt;
&lt;li&gt;Synced clocks in public clouds are real now. We can use them in distributed protocols. This is a superb example.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I learned a lot from this paper, especially since I presented it to the &lt;a href=&#34;https://charap.co/winter-2023-2024-reading-group-papers/&#34;&gt;DistSys Reading Group&lt;/a&gt; and wrote this review. I want to see more of this kind of research: the use of public cloud features for distributed protocols.&lt;/p&gt;
&lt;h1&gt;Further Reading&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;The Huygens paper: &lt;a href=&#34;https://www.usenix.org/system/files/conference/nsdi18/nsdi18-geng.pdf&#34;&gt;Exploiting a Natural Network Effect for Scalable, Finegrained Clock Synchronization&lt;/a&gt;. It&amp;rsquo;s cool, it uses machine learning to estimate minimum one-way latency and hence clock skew. Recommended. Unfortunately the implementation is closed-source (&lt;a href=&#34;https://www.clockwork.io/&#34;&gt;its authors have a startup&lt;/a&gt;), but see &lt;a href=&#34;https://www-users.cselabs.umn.edu/Fall-2021/csci8980-ec/papers/Time.pdf&#34;&gt;the Quartz paper&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/pdf/2206.03285.pdf&#34;&gt;The Nezha technical report&lt;/a&gt; (more benchmarks).&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://disseminatepodcast.podcastpage.io/episode/jinkun-geng-nezha-deployable-and-high-performance-consensus-using-synchronized-clocks-42&#34;&gt;Podcast interview with author Jinkun Geng&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/Steamgjk/Nezha&#34;&gt;Code&lt;/a&gt;, including TLA+ specification.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://muratbuffalo.blogspot.com/2023/12/nezha-deployable-and-high-performance.html&#34;&gt;Murat Demirbas&amp;rsquo;s summary&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nezha images are from &lt;a href=&#34;https://en.wikipedia.org/wiki/Nezha_Conquers_the_Dragon_King&#34;&gt;Nezha Conquers the Dragon King&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sojourner</title>
      <link>https://emptysqua.re/blog/sojourner/</link>
      <pubDate>Tue, 09 Jan 2024 15:18:24 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/sojourner/</guid>
      
      
      
      
      <description>&lt;p&gt;January 1, 2024. Our young Chinese dwarf hamster Sojourner Truth Armstrong Davis.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/sojourner/sojourner-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/sojourner/sojourner-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/sojourner/sojourner-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Bonticou Crag and Table Rocks</title>
      <link>https://emptysqua.re/blog/bonticou-and-table-rocks-2023/</link>
      <pubDate>Sun, 07 Jan 2024 12:42:40 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/bonticou-and-table-rocks-2023/</guid>
      
      
      
      
      <description>&lt;p&gt;November 25, 2023. Hiking the Bonticou Crag and Table Rocks loop in the Gunks, upstate New York. Portra 400 with my Mamiya 7.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-08.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/bonticou-and-table-rocks-2023/bonticou-crag-09.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Dickie Barre</title>
      <link>https://emptysqua.re/blog/dickie-barre-december-2023/</link>
      <pubDate>Sun, 07 Jan 2024 10:35:53 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/dickie-barre-december-2023/</guid>
      
      
      
      
      <description>&lt;p&gt;December 1, 2023. Climbing with Jennifer in the Dickie Barre area of Peter&amp;rsquo;s Kill, the Gunks. Portra 400, Mamiya 7.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dickie-barre-december-2023/dickie-barre-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dickie-barre-december-2023/dickie-barre-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/dickie-barre-december-2023/dickie-barre-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Village Zendo&#39;s Winter 2023 Meditation Retreat</title>
      <link>https://emptysqua.re/blog/winter-retreat-2023/</link>
      <pubDate>Sat, 06 Jan 2024 19:15:04 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/winter-retreat-2023/</guid>
      
      
      
      
      <description>&lt;p&gt;December 2023. The &lt;a href=&#34;https://villagezendo.org&#34;&gt;Village Zendo&lt;/a&gt;&amp;rsquo;s annual year-end Zen retreat, at the Garrison Institute (a former Catholic monastery) on the Hudson River. This year I was a monitor of the meditation hall, responsible for everyone staying awake, sitting up straight and silent and still, finding a comfortable posture, and keeping to the schedule. We had a lot of inexperienced meditators who were clearly in pain after the first day. I met with them and discussed options for sitting on a chair, or a seiza bench, or a cushion, and how to position their backs and shoulders. The meditation hall settled down noticeably for the rest of the week.&lt;/p&gt;
&lt;p&gt;I photographed some meditation sessions on Portra 400 with my Mamiya 7. The room was dark and the photos generally underexposed, so the colors are muddy in several of these. Next year I&amp;rsquo;ll bring ISO 800 film and a tripod.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-01.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-02.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-03.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-04.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-05.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-06.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/winter-retreat-2023/winter-sesshin-2023-07.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Java Modelling Tools</title>
      <link>https://emptysqua.re/blog/java-modelling-tools/</link>
      <pubDate>Thu, 04 Jan 2024 18:55:38 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/java-modelling-tools/</guid>
      
      
      
      
      <description>&lt;p&gt;In 2022, &lt;a href=&#34;https://brooker.co.za/blog/2022/06/02/formal.html&#34;&gt;Marc Brooker argued&lt;/a&gt; that formal methods like TLA+ can check distributed systems&amp;rsquo; correctness but not their performance. Since then, I&amp;rsquo;ve been searching for good performance modeling tools. Queue theory seems like a foundation for performance modeling, so I learned some queue theory, &lt;a href=&#34;https://emptysqua.re/blog/review-queue-theory-book/&#34;&gt;although I read the wrong book&lt;/a&gt;. That book tried to teach me to analyze queue networks by solving intricate equations, but for most queue networks the equations can&amp;rsquo;t be solved, and for the rest I can&amp;rsquo;t remember how to solve them. I concluded that equations aren&amp;rsquo;t practical for me, and &lt;em&gt;simulation&lt;/em&gt; is the right method. I looked for an off-the-shelf queue network simulator, and found the &lt;a href=&#34;https://sourceforge.net/p/jmt/&#34;&gt;Java Modelling Tools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;JMT is from Politecnico di Milano and Imperial College London. It was begun in 2002 and it&amp;rsquo;s still actively developed; &lt;a href=&#34;https://sourceforge.net/p/jmt/mailman/message/51238349/&#34;&gt;the last release was November 2023&lt;/a&gt;. The two main developers, Giuliano Casale and Giuseppe Serazzi, have written and maintained a thorough &lt;a href=&#34;https://jmt.sourceforge.net/Papers/JMT_users_Manual.pdf&#34;&gt;user manual&lt;/a&gt;, and when I asked a question on the project forum last year they &lt;em&gt;both&lt;/em&gt; responded quickly and in detail.&lt;/p&gt;
&lt;p&gt;JMT is a suite of Java applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSIM&lt;em&gt;wiz&lt;/em&gt;: Wizard interface for JSIM, a discrete event simulator, for modeling a queue network.&lt;/li&gt;
&lt;li&gt;JSIM&lt;em&gt;graph&lt;/em&gt;: Same, but you draw the queue network.&lt;/li&gt;
&lt;li&gt;JMVA: Exact and approximate analysis of queue networks with restricted features.&lt;/li&gt;
&lt;li&gt;JMCH: Simulates a single node, animates the Markov chain.&lt;/li&gt;
&lt;li&gt;JABA: Find bottlenecks in closed queue networks.&lt;/li&gt;
&lt;li&gt;JWAT: Reads log files, clusters customers into workload classes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I played with JMCH briefly and JSIM&lt;em&gt;graph&lt;/em&gt; for a couple days.&lt;/p&gt;
&lt;h1&gt;JMCH&lt;/h1&gt;

&lt;p&gt;JMCH displays the states and transition probabilities for a single-node queue network, represented as a Markov chain. It seems to be for teaching how Markov chains relate to queue theory.&lt;/p&gt;
&lt;video controls style=&#34;width: 100%; margin-bottom: 1em&#34;&gt;
  &lt;source src=&#34;https://emptysqua.re/blog/java-modelling-tools/JMCH.mp4&#34; type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;
&lt;p&gt;This is a simple &lt;a href=&#34;https://en.wikipedia.org/wiki/Kendall%27s_notation&#34;&gt;M/M/1 queue&lt;/a&gt;. The diagram in the middle is a Markov chain, where each node represents a state with a certain queue length: e.g., if the system is in state 3, that means there are 3 customers enqueued. Each state is labeled with its long-term probability. So in the long run, the system spends half its time in state 0 (empty queue). You can watch the blue queue grow and shrink at the bottom, and the system transition from state to state, as random events occur. If I&amp;rsquo;d had this available when I started reading my &lt;a href=&#34;https://emptysqua.re/blog/review-queue-theory-book/&#34;&gt;queue theory book&lt;/a&gt; it might&amp;rsquo;ve helped me understand Markov chain analysis.&lt;/p&gt;
&lt;h1&gt;JSIM&lt;em&gt;graph&lt;/em&gt;&lt;/h1&gt;

&lt;p&gt;This is the tool I want. I can draw a queue network, with nodes and sources and sinks connected by directed edges, and simulate the system, watching the animation and observing average values. Here&amp;rsquo;s an example included with JMT:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/raid.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;A network needs one or more classes of customers (or jobs or tasks or whatever). Classes were a niche topic in the queue theory book because we usually assumed one class, but classes are a big deal in JMT.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/raid-class.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Every class needs a reference station. I have read the manual&amp;rsquo;s explanation of reference stations five times and I don&amp;rsquo;t understand, perhaps I need a PhD.&lt;/p&gt;
&lt;p&gt;JMT offers a huge number of probability distributions for arrivals, service times, etc. You can choose the &amp;ldquo;Replayer&amp;rdquo; distribution which reads values from a file; that could be a trace from your real system or numbers generated by another program.&lt;/p&gt;
&lt;p&gt;The goal of running the simulation is to measure some performance indices. Here the indices are the response time for the whole system, and the utilization of each disk.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/raid-indices.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Let&amp;rsquo;s run the simulation and see those performance indices&amp;rsquo; average values:&lt;/p&gt;
&lt;video controls style=&#34;width: 100%; margin-bottom: 1em&#34;&gt;
  &lt;source src=&#34;https://emptysqua.re/blog/java-modelling-tools/raid-simulation.mp4&#34; type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;
&lt;p&gt;By default, JSIM runs until each performance index has converged to a stable value within some confidence interval (the red lines converge around the blue lines).&lt;/p&gt;
&lt;p&gt;When I showed this to some friends they wondered, &amp;ldquo;What if the Join0 node waited for a &lt;em&gt;majority&lt;/em&gt; of disks instead of &lt;em&gt;all&lt;/em&gt; of them?&amp;rdquo; This was satisfyingly easy to answer. I changed the &amp;ldquo;join strategy&amp;rdquo; from &amp;ldquo;standard&amp;rdquo; to &amp;ldquo;quorum&amp;rdquo;:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/raid-join-quorum.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;As expected, re-running the simulation shows the same disk utilization but much faster response time: 19 seconds average instead of 34.&lt;/p&gt;
&lt;h1&gt;Answering a queue theory question&lt;/h1&gt;

&lt;p&gt;Let&amp;rsquo;s take a queue theory problem from the book &amp;ldquo;Performance Modeling and Design of Computer Systems&amp;rdquo; and contrast the analytic approach to simulation. Here&amp;rsquo;s a figure from the book:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/figure-2.3.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;The book&amp;rsquo;s Exercise 2.1 asks,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Maximum Outside Arrival Rate&lt;/strong&gt;&lt;br&gt;
For the network-of-queues with probabilistic routing given in Figure 2.3, suppose that each server serves at an average rate of 10 jobs/sec; that is, &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;μ&lt;/span&gt;&lt;sub&gt;&lt;em&gt;i&lt;/em&gt;&lt;/sub&gt; = 10, &lt;span style=&#34;font-family: non-serif&#34;&gt;∀&lt;/span&gt;&lt;em&gt;i&lt;/em&gt;. Suppose that &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;2&lt;/sub&gt; = &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;3&lt;/sub&gt; = 1. Suppose that &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;p&lt;/span&gt;&lt;sub&gt;12&lt;/sub&gt; = &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;p&lt;/span&gt;&lt;sub&gt;2,out&lt;/sub&gt; = 0.8, &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;p&lt;/span&gt;&lt;sub&gt;23&lt;/sub&gt; = &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;p&lt;/span&gt;&lt;sub&gt;13&lt;/sub&gt; = 0.2, &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;p&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt;,out = 0, and &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;p&lt;/span&gt;&lt;sub&gt;31&lt;/sub&gt; = 1. What is the maximum allowable value of &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt; to keep this system stable?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s add those parameters to the figure:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;To answer the question, &amp;ldquo;what&amp;rsquo;s the max &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt;&amp;rdquo;, or &amp;ldquo;how fast can jobs arrive at Server 1 without overloading the system?&amp;rdquo;, the book taught me to solve a system of simultaneous equations. I arrived at &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt; ≤ 5.2.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-by-hand.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I also wrote &lt;a href=&#34;https://github.com/ajdavis/ajdavis-queue-theory-exercises/blob/master/perf-book/exercise-2.1/exercise-2.1.py&#34;&gt;a 72-line Python simulation&lt;/a&gt; (using just the standard library) to confirm this number.&lt;/p&gt;
&lt;p&gt;Now that I&amp;rsquo;ve forgotten how to answer this question with paper and pencil, can I use JMT instead? I drew this uglitude in JSIM&lt;em&gt;graph&lt;/em&gt;:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-jmt.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I tried to emulate the book&amp;rsquo;s figure, with a &amp;ldquo;source&amp;rdquo; node for &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt;, &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;2&lt;/sub&gt;, and &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;3&lt;/sub&gt;, but this produced strange behavior. Maybe I need to understand &amp;ldquo;reference stations&amp;rdquo;. Instead I made three classes of customer (each with a different arrival rate), generated them all at the same source, forked them to the three servers, and collected them in a &amp;ldquo;sink&amp;rdquo; node.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-classes.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;I set Class 1 to 10 arrivals per second, which we know is too high; it should be at most 5.2. The others are 1 per second as in Exercise 2.1. Since we have 3 classes, we have to configure how each server handles each class. First I set the service times to 0.1 for each class (since the exercise says they serve 10 jobs per second), then the routing rules.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-service.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-routing.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Configuring a queue network is a quickly-growing chore; I have to point and click and enter a number for all classes multiplied by servers multiplied by routes. Worse, there&amp;rsquo;s no single place to see all the parameters and verify them. I have to double-click each node to open its config dialog, and click among the tabs checking each number.&lt;/p&gt;
&lt;p&gt;To find the maximum &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt; value I configure a &amp;ldquo;What-If&amp;rdquo; analysis, trying a range of values:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-what-if.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;JMT&amp;rsquo;s simulator assumes by default that the system is stable and its performance indices will converge. In this case, if &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt; is too big the system is unstable and JMT runs until it&amp;rsquo;s out of memory. So I configure the simulation to run for a limited time:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-simulation.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;What performance indices will tell me whether the system is stable? If queues grow or a server is fully utilized, those are bad signs, so I&amp;rsquo;ll measure queueing time and utilization.&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-performance-indices.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;Finally, I actually run the analysis:&lt;/p&gt;

&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-queue-time.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;
&lt;img src=&#34;https://emptysqua.re/blog/java-modelling-tools/exercise-2.1-utilization.png&#34; style=&#34;display: block; max-width:100%&#34; alt=&#34;&#34; title=&#34;&#34;&gt;&lt;p&gt;As I had hoped, between the tested &lt;span style=&#34;font-family: non-serif; font-style: italic&#34;&gt;r&lt;/span&gt;&lt;sub&gt;1&lt;/sub&gt; values of 5.1 and 6.1, things go haywire. Queueing time takes off and Server 1&amp;rsquo;s utilization hits 100%. Configuring this was a lot of work, but it&amp;rsquo;s gratifying to see a clear result. JMT lets me easily measure more performance indices, or tweak the model and see the effects, more easily than in Python and &lt;em&gt;much&lt;/em&gt; more easily than paper and pencil.&lt;/p&gt;
&lt;h1&gt;My evaluation&lt;/h1&gt;

&lt;p&gt;Gripes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSIM&lt;em&gt;graph&lt;/em&gt; requires a bunch of pointing, clicking, and typing to set up a queue network with a few classes and nodes. The &lt;a href=&#34;https://github.com/imperial-qore/pyJMT#example&#34;&gt;Python interface&lt;/a&gt; looks like a slick way to construct really big networks, but medium networks are a chore.&lt;/li&gt;
&lt;li&gt;Since JSIM&lt;em&gt;graph&lt;/em&gt; expects stable systems by default, doing &amp;ldquo;what-if&amp;rdquo; analysis to determine whether a set of params is stable is tricky.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s no single place to see all configuration values, so I have difficulty trusting that I&amp;rsquo;ve set up a network correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But in JMT&amp;rsquo;s favor, if your needs fit its features, it&amp;rsquo;s more convenient than writing a Python simulation, and it overcomes the total impracticality of solving equations. I may play with it some more to learn its features better, and try answering some actual day-job questions about distributed systems.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>West Trapps Bouldering</title>
      <link>https://emptysqua.re/blog/trapps-bouldering/</link>
      <pubDate>Thu, 02 Nov 2023 11:50:27 +0000</pubDate>
      <author>jesse@emptysquare.net (A. Jesse Jiryu Davis)</author>
      <guid>https://emptysqua.re/blog/trapps-bouldering/</guid>
      
      
      
      
      <description>&lt;p&gt;October 11, 2023. Boulderers in the West Trapps area of the Gunks. Shot with a Mamiya 7 on Portra 400.&lt;/p&gt;





  
  
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/trapps-bouldering/ajdavis_20231102_000161030006.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/trapps-bouldering/ajdavis_20231102_000161030007.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  
    
      &lt;hr&gt;
    
    &lt;p&gt;&lt;img src=&#34;https://emptysqua.re/blog/trapps-bouldering/ajdavis_20231102_000161030010.jpg&#34; style=&#34;display: block; max-width:100%&#34;&gt;&lt;/p&gt;
  

  
  


&lt;p&gt;&lt;span style=&#34;color: gray&#34;&gt;Images © A. Jesse Jiryu Davis&lt;/span&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
