<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>sao.dev &#x2F; writing</title>
    <subtitle>Notes and essays on systems, correctness, tools — and the occasional salad.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://blog.sao.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.sao.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-08-08T00:00:00+00:00</updated>
    <id>https://blog.sao.dev/atom.xml</id>
    <entry xml:lang="en">
        <title>Software Annealing</title>
        <published>2026-08-08T00:00:00+00:00</published>
        <updated>2026-08-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/software-annealing/"/>
        <id>https://blog.sao.dev/software-annealing/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/software-annealing/">&lt;p&gt;One of the fundamental ideas behind &lt;a href=&quot;https:&#x2F;&#x2F;cpn.sao.dev&#x2F;&quot;&gt;the CPN project&#x27;s&lt;&#x2F;a&gt; development is &quot;Software Annealing&quot;. &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Annealing_(materials_science)&quot;&gt;Annealing&lt;&#x2F;a&gt;, oversimplified, is the heating and cooling of a material to align molecular structure and merge crystaline regions within the material, generally hardening and improving other desirable features. Software
annealing is philosophically similar, in higher dimension:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Establish or update the project&#x27;s heading or scope.&lt;&#x2F;li&gt;
&lt;li&gt;Establish applications&#x2F;use-cases of the project that stretch its current state.&lt;&#x2F;li&gt;
&lt;li&gt;Refactor or evolve the project into a new &quot;simple&quot; or &quot;solid&quot; state.&lt;&#x2F;li&gt;
&lt;li&gt;Go to 1.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;It sounds simple; and at a high level, it is. The process is very Agile™, which gives it great optionality, and it aligns well with the naturally evolving process of discovering the new, valuable, insightful form for the software. One of the risks of software projects is that software can be whatever you make it, which tragically also means it can be whatever you imagined it maybe should be at 1 AM one fateful night.&lt;&#x2F;p&gt;
&lt;p&gt;This brings up one of the often unsung hero&#x27;s of successful software: good applications of said software. This can be users, or other apps serving users, etc. These applications of the software-under-optimization are the neurons that propagate gradient from &quot;the market&quot; (the real world), and anchor the software in reality. It&#x27;s the &quot;personal software&quot; &lt;a href=&quot;https:&#x2F;&#x2F;blog.sao.dev&#x2F;software-annealing&#x2F;#workouts-app&quot;&gt;workouts app&lt;&#x2F;a&gt; that motivates flexibility and client-server capabilities, it&#x27;s the TPC-C benchmark that ensures performance, it&#x27;s the scraper that motivates complex binding semantics, etc.&lt;&#x2F;p&gt;
&lt;p&gt;The power of these applications is even higher when driven by the project author. By looking at the high level goals of the project, and finding gaps with what is being tested now, the author can pattern match those gaps to applications that would truly test them. By doing continuous narrative building (it&#x27;s all &lt;a href=&quot;&#x2F;narrative-and-better-bets&#x2F;&quot;&gt;narrative building&lt;&#x2F;a&gt;), you can measure the delta between opportunities identified and recorded in the past and see how they align in total. These notes on opportunities and frictions from the past are the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Dislocation&quot;&gt;crystal dislocations&lt;&#x2F;a&gt; waiting to be annealed. This is why I love &lt;a href=&quot;&#x2F;2025-threads&#x2F;&quot;&gt;threads&lt;&#x2F;a&gt; so much as a human &lt;strong&gt;and&lt;&#x2F;strong&gt; machine-read planning and execution journal. They make execution easy and retros&#x2F;insight mining almost free.&lt;&#x2F;p&gt;
&lt;p&gt;Oh yeah, LLMs! They make this whole process way faster. LLMs love a verifiable goal and a well described task. They are good foils for thinking through designs and problem framings (especially the latest models), and can then execute on the whole loop with some hand-holding. And maybe one of the easiest to overlook boons they bring is their surprisingly strong inductive capabilities. After enough &quot;data&quot; generated by way of applying the software-under-application, you can sometimes just point a sufficiently smart code agent at a set of threads with a vague &quot;I get a vague sense that our migration story is complex&quot; prompt and it will come back with some surprising insight.&lt;&#x2F;p&gt;
&lt;p&gt;Another practically valuable property of this strategy for solo developers is that it keeps the project interesting. It is very easy to park ideas that you capture, but don&#x27;t want to act on yet, without it just going off into the ticket abyss. You are essentially maintaining a portfolio of options, of threads to pull when you are inspired or the time is right. I&#x27;ve got a thread on net change data capture (so postgres&#x2F;etc can tail) on the shelf, while establishing identity capabilities that is more pressing. Very fun.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h1 id=&quot;appendix&quot;&gt;Appendix&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;workouts-app&quot;&gt;Workouts App&lt;&#x2F;h2&gt;
&lt;p&gt;Making software for yourself is great. The &lt;a href=&quot;https:&#x2F;&#x2F;workouts.val.cpn.sao.dev&#x2F;ui&quot;&gt;workouts app&lt;&#x2F;a&gt; is software I made to track workouts. Probably one of the most common personal projects, and honestly the second one I&#x27;ve made.&lt;&#x2F;p&gt;
&lt;p&gt;This one is pretty cool though, if you don&#x27;t mind me bragging:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It implements petri nets for both the front and and backend, with semantics for uncertain network transmission&lt;&#x2F;li&gt;
&lt;li&gt;It handles idempotence and reconciliation implicitly due to the foundational token movement concepts of CPNs
&lt;ul&gt;
&lt;li&gt;That means I don&#x27;t have to wave my phone around in the gym where the wifi is &lt;em&gt;always bad&lt;&#x2F;em&gt; to be confident my workout gets submitted&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I was also inspred by a recent &lt;a href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=49096439#49196107&quot;&gt;HN comment&lt;&#x2F;a&gt; about tracking strength training progress using &quot;Pareto Fronts&quot;: strength training and physical fitness in general is multi-facted and can be hard to be certain if you&#x27;re really progressing, and adding this to the workouts app should make it easier to suss out.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;beads-gastown-buzz-etc&quot;&gt;Beads, Gastown, Buzz, etc&lt;&#x2F;h2&gt;
&lt;p&gt;The vibe that I get when I hear about software factories is that they are kinda missing the point. They&#x27;re focused on &quot;coding isn&#x27;t the hard part&quot;, and betting that the thing to focus on is orchestration. Focusing on orchestration is kind of a bet against increasing LLM capabilities - if we start running Fable on ASICs, do we need things to be orchestrated? Or is a single threaded loop fine? It strikes me that these systems may become useful, but only briefly before &quot;Fable on ASICs&quot; makes them irrelevant (and probably destroys the fabric of capitalism).&lt;&#x2F;p&gt;
&lt;p&gt;Thinking about it through this lens, software annealing seems to be about enumerating and revising the bets of what is valuable over time. For things like the CPN project, its core bet is easy to describe (the arc graph can correctly encode all state transitions), but not quick to turn into complete software ready to replace postgres, temporal, or fastapi. You need to continuously think about what learnings to bake into the framework, about what requirements are ancillary. Claude would just &lt;em&gt;love&lt;&#x2F;em&gt; to add yet another concept to your codebase, and you have to have the taste to say no! No is the other side of the coin from deciding what is valuable.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A Vine in the Wind</title>
        <published>2026-07-28T00:00:00+00:00</published>
        <updated>2026-07-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/vine-in-the-wind/"/>
        <id>https://blog.sao.dev/vine-in-the-wind/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/vine-in-the-wind/">&lt;p&gt;Evolution can surprise you sometimes.&lt;&#x2F;p&gt;
&lt;p&gt;As I looked out my window at my neighbors yard, I saw a vine, outstretched into nothingness.&lt;&#x2F;p&gt;
&lt;p&gt;So clear it was that it was not growing toward another anchor point.&lt;&#x2F;p&gt;
&lt;p&gt;And then, a gust - the vine&#x27;s leaves, like sails, carried it up and around and the vine waved like an arm outstretched &lt;em&gt;grasping&lt;&#x2F;em&gt; for sustenance.&lt;&#x2F;p&gt;
&lt;p&gt;So clear it was that it could hook onto something nearby without even knowing it was there, or planning for any specific thing to be in any specific place or orientation.&lt;&#x2F;p&gt;
&lt;p&gt;Here was an arm grasping with no muscle, no sight, just environmental randomness doing the organism&#x27;s work for it.&lt;&#x2F;p&gt;
&lt;p&gt;They say you need to be open to new opportunity to truly grow, and I think that&#x27;s true.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe you also need to be movable to a place or perspective of opportunity, a second order effect.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe you need to be a vine in the wind.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Do we become our skills?</title>
        <published>2026-07-25T00:00:00+00:00</published>
        <updated>2026-07-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/becoming-our-skills/"/>
        <id>https://blog.sao.dev/becoming-our-skills/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/becoming-our-skills/">&lt;p&gt;As I&#x27;ve grown and aged, I&#x27;ve become more and more satisfied and self-assured. But in the wilds of modern life, is it justified? Or do we just become our skills, our opinions, our social groups, and the life arcs we paint in the moments of not knowing? And is that okay?&lt;&#x2F;p&gt;
&lt;p&gt;Fair warning, this post is probably mostly for my own benefit, but why not put it out on the internet for everyone :)&lt;&#x2F;p&gt;
&lt;p&gt;Here I am sitting in a café, studying Mandarin (traditional), when I&#x27;m struck by the thought (or maybe the vibe), am I just my skills? It was a moment where I felt the awareness of my current skillset, and thanks to the success of my company, awareness of the skill sets I don&#x27;t have or which are nascent. I&#x27;m a great engineer, that I know; but I&#x27;m not a great sales person, nor a great org builder, nor am I a great manager (though I think I do pretty well). There was a time, common among engineers of the last thirty+ years I&#x27;m sure, that I thought &quot;I don&#x27;t need to sell, I don&#x27;t need to market, I don&#x27;t need to politic - I can just pursue the latent truth between technology and the market&quot;. (&#x27;&quot;I can&quot; and what?&#x27; being an obvious follow up)&lt;&#x2F;p&gt;
&lt;p&gt;The closer you are to these other roles, and the longer the arc of the Thing you are building and shepherding, the more you realize the incredible value of synthesis and alignment across these domains. I hate to give a point to Elon, but he was right about the value of capable generalists: often the framing of the problem is half the battle, and so many of my former peers saw everything that wasn&#x27;t &quot;the code &#x2F; design&quot; as a begrudging chore of the job. They liked the problem solving.&lt;&#x2F;p&gt;
&lt;p&gt;I love the problem solving too, but I was fortunate enough to learn early how flexible the problem definition really is in the real world. I was &quot;lucky&quot; to quit my first job at 25 to jump into a solo founded software startup. It was a terrible financial bet at the time, but it showed me that people pay for and use for the value, not the solution, and the value was a function of the &lt;em&gt;problem&lt;&#x2F;em&gt; for &lt;em&gt;them&lt;&#x2F;em&gt;. Don&#x27;t mistake me - powerful solutions are also what reshape our value frontier, as they make previosuly ludicrous strategies or problem rotations &quot;an insightful idea, actually&quot;. (why you don&#x27;t settle for less than a 10x value increase when betting big on innovations)&lt;&#x2F;p&gt;
&lt;p&gt;I suppose you could say this gave me a small amount of &quot;worldliness&quot;: I had to do more than pay lip service to the other components of building a business. I and to understand those domains, and to construct a believable strategy, which forced me to also see the bounds of my chosen domain (engineering) and its interactions with the others. Unfortunately, I was also taken in by things like &quot;content marketing&quot; as an answer for building commercial interest, and luckily have now learned that nothing beats pavement-pounding cold-calling sales blood sweat and tears. (most of why I&#x27;m not starting something solo any time soon)&lt;&#x2F;p&gt;
&lt;p&gt;All that to say, these domains forced me to construct a narrative space that my profession lived within, that connected it to &quot;the way the world works&quot;. Now to land this plane: I think this largely explains why I am in the position I&#x27;m in now, compared to the positions of my peers at the time. I exposed myself to problems and growth-demands and perspectives that pointed the gradient of my growth here. Each challenge demanded growth, and the sum of those challenges plus &quot;just quit my first job&quot; me is now current me: my skills, my taste, my worldview, and yes, my shortcomings. And not in some &quot;if you squint&quot; way: my skillset and profession determine how I think about chores, and whether I hire cleaners or spend relationship conflict to solve it instead. It&#x27;s how I think about what problems I should solve myself or delegate to others. It&#x27;s how I prioritize what we plan to do, and what of those things later we defer based on new information. It&#x27;s how I think about fixing the &quot;god I need to get better at watering my plants&quot; problem. It&#x27;s what consumes &lt;a href=&quot;https:&#x2F;&#x2F;cpn.sao.dev&#x2F;&quot;&gt;my free time&lt;&#x2F;a&gt;. It&#x27;s what whole regions of my brain have been optimized over years to understand, embed, and operate with. It&#x27;s the topics of conversation that interest me most. You get the drift.&lt;&#x2F;p&gt;
&lt;p&gt;The easiest thing is to just keep going. There&#x27;s an inconvenient version of it that is &quot;go harder&quot;, leaning deeper into specific or more recent narratives. Something like &quot;become a great leader&quot;, or &quot;always do the right thing for your relationship and community&quot; (though figuring that out is hard). Then there&#x27;s the harder end of the space, truly questioning your mental model, incentives, and the opinions of those around you. There&#x27;s a reason the latter is hard, and we mostly know why. This thought is freeing, in a way: once you have it, you can now strategically choose to walk that dimension of your growth&#x2F;worldview. You can meta-policy your growth by picking a domain where you feel opportunity, and to decide to take more risks, inject more noise.&lt;&#x2F;p&gt;
&lt;p&gt;Oh, yeah - noise! Randomness. I should have mentioned that earlier. I think &quot;fortunate noise&quot; is what I was the beneficiary of in my &quot;let&#x27;s solo-found a company at 25!&quot; adventure. At the time I did not understand the world enough to truly see the value of the experience it would give me. But it was an extremely high variance bet whose influence on the challenges and problems I saw day to day, month to month forced me to grow in a way I benefited from. Tangent: I want to be extremely clear that it isn&#x27;t &quot;the&quot; way, and that there are likely &lt;em&gt;many&lt;&#x2F;em&gt; ways and directions that result in highly valuable growth. Almost the best thing you can have these days is &quot;a really good problem&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;I think this topic is on my mind these days because a big part of my identity is now under internal strain. Probably up until the pandemic, I held technology and software high: it was the literal lever we used to pull ourselves out of drudgery. I was ambivalent at the trash pile facebook had become, but software&#x27;s (even commercial) value very much eclipsed its societal costs and risks. These days, that is obviously in question, and the arc of humanity looks to be headed for incredible tragedy or revolution (or both) because of the maladaptive incentives and circumstances it creates. (unfortunately &quot;optimization&quot; is at the heart of many of these systems) I now have to reconcile what I find to be beautiful from what I find to be horrific. I would never work for a weapons manufacturer because of the obvious direct harm. Now the same is true for Meta, and a growing list of other companies.&lt;&#x2F;p&gt;
&lt;p&gt;I think I need noise in a domain or on a dimension I can&#x27;t yet predict. I fear naively becoming more &quot;my skills&quot;. I yearn for inspiration in something that feels more true for humanity than what we have. Vive randomness, I suppose!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Graph Structure of &quot;Things Taking a Long Time&quot;</title>
        <published>2026-07-15T00:00:00+00:00</published>
        <updated>2026-07-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/graph-structure-of-things-taking-a-long-time/"/>
        <id>https://blog.sao.dev/graph-structure-of-things-taking-a-long-time/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/graph-structure-of-things-taking-a-long-time/">&lt;p&gt;A recent project involved turning a manually bash-scripted loadsim into a CI-triggered deployment gate, which was parameterizable, handled system-under-test provisioning and cleanup, observable, and uploaded its results to block storage. You can probably tell where this is going: it took longer than expected. Why, though? By all accounts, the task is straight forward, and there are no &quot;wow I&#x27;m not even sure how we&#x27;d do that&quot; type subproblems involved. My take, though, is that graphs explain why, and particularly going from no graphs to many explicit graphs is the specific reason.&lt;&#x2F;p&gt;
&lt;p&gt;To set the stage, I&#x27;ll provide the before and after:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Before
&lt;ul&gt;
&lt;li&gt;Just a bash script you run on your computer
&lt;ul&gt;
&lt;li&gt;seed data &amp;gt; launch SUT locally &amp;gt; seed data &amp;gt; warm up SUT &amp;gt; run the load test &amp;gt; tear down SUT &amp;gt; upload results&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;If something goes wrong you just debug and update the bash script&lt;&#x2F;li&gt;
&lt;li&gt;You are responsible for interpreting the results&lt;&#x2F;li&gt;
&lt;li&gt;No tests - &quot;it continuing to work is the test&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;After
&lt;ul&gt;
&lt;li&gt;This thing needs tests
&lt;ul&gt;
&lt;li&gt;This thing needs to be factored to allow DI to allow good testing
&lt;ul&gt;
&lt;li&gt;This thing needs to run as a set of state machines to truly factor per-stage responsibilities&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;This thing needs to run in-cluster to point at the system-under-test-service directly
&lt;ul&gt;
&lt;li&gt;This thing needs to define its own k8s resources&lt;&#x2F;li&gt;
&lt;li&gt;This thing needs to define the container it will run in and runtime deps that need to be packaged with it&lt;&#x2F;li&gt;
&lt;li&gt;This thing needs to define the cluster resource relationships (taints etc) it relies upon to be isolated from other workloads&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;This thing needs to upload its results
&lt;ul&gt;
&lt;li&gt;This thing needs to be authenticated to block storage&lt;&#x2F;li&gt;
&lt;li&gt;This thing needs to establish explicit storage structure for reports&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;This thing needs to gate PR approval based on specific configured performance expectations
&lt;ul&gt;
&lt;li&gt;This thing needs to log its status etc etc&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ll stop there, but you can see how each of these things blows up. Were this &quot;implicit graph&quot; mode, we could just add lines to the bash script to do the marginal thing, and say &quot;we aren&#x27;t going to generalize this&quot;. It would be easy to break, as the dependencies between the concepts are completely implicit (manifest at runtime by what it does, not defined by and explicit model).&lt;&#x2F;p&gt;
&lt;p&gt;All of the pain and inaccuracy in estimation&#x2F;planning came from the turning of implicit relationships into explicit graphs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Creating the load sim state machine (and related concepts - oops we need a simulation state machine also!)&lt;&#x2F;li&gt;
&lt;li&gt;Making the type&#x2F;struct hierarchy that defines the config language of the loadsim process: before just a set of CLI args, now a modular collection of related config factors that couple loadsim profiles to literal application code interfaces. Higher optionality, but more complex also.&lt;&#x2F;li&gt;
&lt;li&gt;TODO&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Why did we have to pay down this debt now? Well, we&#x27;re going from &quot;thing we run on our laptops sometimes&quot; to &quot;thing that runs every time we want to merge a related PR&quot;, where the latter has much higher quality expectations.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Structural Correctness</title>
        <published>2026-06-27T00:00:00+00:00</published>
        <updated>2026-06-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/structural-correctness/"/>
        <id>https://blog.sao.dev/structural-correctness/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/structural-correctness/">&lt;p&gt;Type systems are great: they catch huge swaths of bugs at compile time, and contribute to a grammar&#x2F;vocab of forward development for your app. Like type systems, modern tools for verifying correctness leverage &lt;strong&gt;structural descriptions&lt;&#x2F;strong&gt; of the problem, via graph-structured model of the domain:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Type systems&lt;&#x2F;strong&gt; describe programs as a graph of types, functions, traits&#x2F;etc, and their relationships. Types are nodes, with relationships like &lt;code&gt;returns&lt;&#x2F;code&gt;, &lt;code&gt;accepts&lt;&#x2F;code&gt;, &lt;code&gt;implements&lt;&#x2F;code&gt;, etc as edges.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Build systems&lt;&#x2F;strong&gt; like bazel have build targets as nodes and dependencies as edges between them. Without explicit deps stated, unstated packages are not provided to build targets even if they are built by other means, fulfilling hermiticity and trivial deployability.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Interface description languages&lt;&#x2F;strong&gt; have messages types and services as nodes, with return types and parameter types as edges (a restricted type system).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Databases&lt;&#x2F;strong&gt; use foreign keys (edges) to describe the relationship between related table rows (nodes). However, databases also use check contraints to implement correctness: not structural, but useful predicates to express aspects can&#x27;t can&#x27;t be described additively.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;div class=&quot;figure-highlight&quot;&gt;&lt;div class=&quot;figure-highlight-header&quot;&gt;&lt;span class=&quot;figure-dot-filled&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;figure-highlight-title&quot;&gt;STRUCTURAL CORRECTNESS&lt;&#x2F;span&gt;&lt;&#x2F;div&gt;&lt;p class=&quot;figure-highlight-body&quot;&gt;Model your domain as a graph of typed nodes and edges. The best of these graphs are definitional: the structure that describes the system &lt;em&gt;is&lt;&#x2F;em&gt; the system. No edge, no capability.&lt;&#x2F;p&gt;&lt;&#x2F;div&gt;
&lt;p&gt;Each domain has its own vocab of node and edge types. Bazel is a nice example of the richness via different edge types: deps means &quot;link this code,&quot; srcs means &quot;compile these files,&quot; data means &quot;make available at runtime.&quot; Each is a different edge type in the target graph, and each constitutes a different capability. Elegant choice of node and edge types result in expressive and powerful tools. Many of these correctness tools leverage both structural correctness and predicates to describe valid configurations.&lt;&#x2F;p&gt;
&lt;p&gt;The best of these graph-structured systems are constitutive&#x2F;definitional: they both define &quot;what is valid&quot; and &quot;how outputs are computed&quot;. In bazel, a target can&#x27;t import a module it doesn&#x27;t &lt;code&gt;dep&lt;&#x2F;code&gt;end on. This provides two valuable features: a) the verifiable invariant must be stated for the system to function, so you end up with a verifiable system by default, and b) it provides a brevity in specificaiton thanks to stated attributes and relationships doing double duty for behavior and validation. Building a bazel project forces you to describe the explicit structure of your product, and leaves you with a means to verify it as you continue to develop.&lt;&#x2F;p&gt;
&lt;p&gt;These two points about &quot;each domain has its own nounds and verbs&quot; and &quot;definitional graph structured systems&quot; are why bazel is such a good substrate for describing your whole product in: Bazel lets you define arbitrary node types for your domain concepts (packages, services, datasets), constrain their relationships via typed edges, and then derive your entire build&#x2F;test&#x2F;deploy pipeline from that single structural description. A service, its metrics endpoint, and the Prometheus deployment that scrapes it can all be nodes in the same graph — their relationships declared once and used to compile, deploy, and verify the whole system.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;but-what-about-state&quot;&gt;But what about state?&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m glad you asked! Databases define what can be stored via schema-specified tables, foreign keys, and check constraints. But outside of those they do not specify valid state transitions. This is often left to the application layer, where HTTP or RPC endpoints are relied on to faithfully implement the business logic. However, this is often where things go wrong: e.g. two endpoints that both modify order status, one forgetting to check inventory before marking it &lt;code&gt;shipped&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Colored petri nets (CPNs) take the schema-specified collections (&lt;a href=&quot;https:&#x2F;&#x2F;cpn.sao.dev&#x2F;concepts&#x2F;places-and-tokens&quot;&gt;places&#x2F;tokens&lt;&#x2F;a&gt;) and adds strict &quot;next state&quot; definition via &lt;a href=&quot;https:&#x2F;&#x2F;cpn.sao.dev&#x2F;concepts&#x2F;transitions&quot;&gt;transitions&lt;&#x2F;a&gt;. In CPNs, state only changes via a specific &lt;a href=&quot;https:&#x2F;&#x2F;cpn.sao.dev&#x2F;concepts&#x2F;bindings&quot;&gt;binding&lt;&#x2F;a&gt; being selected and its transition firing, consuming, mutating, and producing tokens in specific places. This is an applicaiton of graph-structured systems to state: places and transitions are nodes, and the arcs that connect them are edges. Like bazel, the concepts that define the functioning of CPN systems do double duty as means of verifying said system more completely.&lt;&#x2F;p&gt;
&lt;p&gt;To demonstrate how CPNs do this, let&#x27;s walk through a fairly complex example: a web scraper. I implemented the web scraper I described as an application of interest in the &lt;a href=&quot;&#x2F;cpns-llms-distributed-apps&#x2F;&quot;&gt;original CPN post&lt;&#x2F;a&gt;. In this example, the scraper needs to use a rotating set of proxies, and to implement &quot;leasing&quot; semantics for domains, to prevent overloading of a site, etc. Traditionally, building a stateful system like this means scattering coordination across multiple layers: a database handles resource state (via &lt;code&gt;SELECT FOR UPDATE&lt;&#x2F;code&gt; and careful lock ordering), a task queue handles job scheduling, application code handles rate limiting and cooldown logic, and yet another component enforces domain concurrency limits. Each has its own incomplete view of correctness. And you increase reliability by writing check constraints, unit tests, and application-level predicates, and ensure they agree via trial and error.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Proxy conservation isn&#x27;t a property you test for, but a topological fact of the net.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;With a CPN, the scraper&#x27;s entire coordination model is declared as tokens and transitions. Claiming a resource for scraping is a single transition (&lt;code&gt;select_resource&lt;&#x2F;code&gt;) that binds across available resources, proxies, and domain states. It atomically leases a proxy, increments the domain&#x27;s active count, and creates a session all in the same firing. There&#x27;s no locking strategy to get right, because the binding system guarantees only valid combinations participate. Proxy conservation isn&#x27;t a property you test for because it&#x27;s a topological fact: a proxy token exists in exactly one place at any time (&lt;code&gt;available_proxies&lt;&#x2F;code&gt; or &lt;code&gt;leased_proxies&lt;&#x2F;code&gt;), and only declared transitions can move it. The resource lifecycle (&lt;code&gt;available&lt;&#x2F;code&gt; → &lt;code&gt;leased&lt;&#x2F;code&gt; → &lt;code&gt;cooling&lt;&#x2F;code&gt; → &lt;code&gt;available&lt;&#x2F;code&gt;) is the shape of the net itself, effectively a state machine.&lt;&#x2F;p&gt;
&lt;p&gt;Also, it provides you with a cool animation of the net to show off (feed readers strip the embed below — &lt;a href=&quot;https:&#x2F;&#x2F;viz.cpn.sao.dev&#x2F;scraper&#x2F;&quot;&gt;view it full size&lt;&#x2F;a&gt;):&lt;&#x2F;p&gt;
&lt;iframe class=&quot;viz-embed&quot; src=&quot;&#x2F;demos&#x2F;scraper&#x2F;&quot; frameborder=&quot;0&quot;&gt;&lt;&#x2F;iframe&gt;
&lt;p class=&quot;figure-label&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;viz.cpn.sao.dev&#x2F;scraper&#x2F;&quot;&gt;Full size&lt;&#x2F;a&gt; — the web scraper CPN, animated&lt;&#x2F;p&gt;
&lt;p&gt;Personally, I&#x27;m bullish on this unification of state and transition: databases generally only handle the data + type side of this, plus predicate tools like check constraints. Where existing systems distribute state-related invariants across application code, unit tests, and database constraints (each with separate verification), CPNs unify these concerns and use them to define app function.&lt;&#x2F;p&gt;
&lt;p&gt;As a new domain there&#x27;s a lot of learning ahead about what kinds of bugs could emerge from these systems, or what kind of operations and performance trade-offs need to be made. Type systems are famously not-free. But I see an even larger amount of opportunity to go with it.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>BECU OFX Connection</title>
        <published>2026-02-01T00:00:00+00:00</published>
        <updated>2026-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/becu-ofx-connect/"/>
        <id>https://blog.sao.dev/becu-ofx-connect/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/becu-ofx-connect/">&lt;p&gt;(Written 2026-02-01)&lt;&#x2F;p&gt;
&lt;p&gt;It sucks having to rely on Plaid or MasterCard Data Services to aggregate financial data, since they certainly sell this data in some way. I&#x27;ve been trying to get access to BECU programmatically, as they do expose &lt;a href=&quot;https:&#x2F;&#x2F;www.becu.org&#x2F;news&#x2F;online-banking-intuit-connection&quot;&gt;an OFX API&lt;&#x2F;a&gt;, and after significant effort, I&#x27;ve recently succeeded in getting access! This little post tells you how to do it. The hardest part so far has been just getting BECU to respond in a non-generic error manner, and get it to initiate a connection as described in the link.&lt;&#x2F;p&gt;
&lt;p&gt;You can initiate an app connection request using the python script below. Be aware that some details lie the version, app version, etc details may have changed since writing. And you&#x27;ll need to populate env vars referenced at the start of the script - I generated a UUIDv4 for my &lt;code&gt;BECU_CLIENTUID&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;After running this and receiving a &lt;code&gt;Please contact your Financial Institution. The CLIENTUID sent by the client was incorrect.&lt;&#x2F;code&gt; response, I was then able to find and authorize the OFX connection request in the &lt;a href=&quot;https:&#x2F;&#x2F;onlinebanking.becu.org&#x2F;BECUBankingWeb&#x2F;Ofx&#x2F;Index&quot;&gt;BECU online banking connected services&lt;&#x2F;a&gt; page, where it showed up as &lt;code&gt;Quicken for Windows&lt;&#x2F;code&gt; (wrong, but whatever). Now time to build out the full transaction and account state capture service!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;python&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-python &quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# main.py
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span&gt;urllib.request
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span&gt;urllib.error
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;from &lt;&#x2F;span&gt;&lt;span&gt;datetime &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span&gt;datetime, timezone
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span&gt;os
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BECU_USERNAME &lt;&#x2F;span&gt;&lt;span&gt;= os.environ[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;BECU_USERNAME&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BECU_PASSWORD &lt;&#x2F;span&gt;&lt;span&gt;= os.environ[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;BECU_PASSWORD&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BECU_CLIENTUID &lt;&#x2F;span&gt;&lt;span&gt;= os.environ[&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;BECU_CLIENTUID&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# Build raw OFX request
&lt;&#x2F;span&gt;&lt;span&gt;now = datetime.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;now&lt;&#x2F;span&gt;&lt;span&gt;(timezone.utc).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;strftime&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;%Y%m%d%H%M%S&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;ofx_request = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;OFXHEADER:100
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;DATA:OFXSGML
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;VERSION:103
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;SECURITY:NONE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ENCODING:USASCII
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;CHARSET:1252
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;COMPRESSION:NONE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;OLDFILEUID:NONE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;NEWFILEUID:NONE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;OFX&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;SIGNONMSGSRQV1&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;SONRQ&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;DTCLIENT&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;{now}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;USERID&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BECU_USERNAME&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;USERPASS&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BECU_PASSWORD&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;LANGUAGE&amp;gt;ENG
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;FI&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;ORG&amp;gt;BECU
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;FID&amp;gt;3670
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;FI&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;APPID&amp;gt;QWIN
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;APPVER&amp;gt;2700
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;CLIENTUID&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BECU_CLIENTUID&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;SONRQ&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;SIGNONMSGSRQV1&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;SIGNUPMSGSRQV1&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;ACCTINFOTRNRQ&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;TRNUID&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;{now}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;ACCTINFORQ&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;DTACCTUP&amp;gt;19700101
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;ACCTINFORQ&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;ACCTINFOTRNRQ&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;SIGNUPMSGSRQV1&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;&#x2F;OFX&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&amp;quot;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;=== REQUEST ===&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(ofx_request)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;req = urllib.request.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Request&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;https:&#x2F;&#x2F;onlinebanking.becu.org&#x2F;ofx&#x2F;ofxprocessor.asp&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;=ofx_request.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;encode&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ascii&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;={&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;application&#x2F;x-ofx&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;},
&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;try&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    resp = urllib.request.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;urlopen&lt;&#x2F;span&gt;&lt;span&gt;(req, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;30&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;=== RESPONSE ===&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(resp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;read&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;decode&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;except &lt;&#x2F;span&gt;&lt;span&gt;urllib.error.HTTPError &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span&gt;e:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;=== ERROR &lt;&#x2F;span&gt;&lt;span&gt;{e.code}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(e.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;read&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;decode&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Everyday Salad</title>
        <published>2026-01-08T00:00:00+00:00</published>
        <updated>2026-01-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/the-everyday-salad/"/>
        <id>https://blog.sao.dev/the-everyday-salad/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/the-everyday-salad/">&lt;p&gt;I am a creature of habit and vegetarian, and needed a consistent and simple meal-prep option to have in the fridge for the week. Enter the Everyday Salad - a delicious and nutricious pile of beans and greens proven by years of experience to please at a party as easily as it does every lunch for a week.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ingredients&quot;&gt;Ingredients&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Ingredient&lt;&#x2F;th&gt;&lt;th&gt;Amount&lt;&#x2F;th&gt;&lt;th&gt;Prep&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Baby spinach&lt;&#x2F;td&gt;&lt;td&gt;5 oz&lt;&#x2F;td&gt;&lt;td&gt;Chopped&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Baby arugula&lt;&#x2F;td&gt;&lt;td&gt;5 oz&lt;&#x2F;td&gt;&lt;td&gt;Chopped&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Mikumame&lt;&#x2F;td&gt;&lt;td&gt;16 oz&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Black beans&lt;&#x2F;td&gt;&lt;td&gt;15 oz&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Corn&lt;&#x2F;td&gt;&lt;td&gt;15 oz&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Grape tomatoes&lt;&#x2F;td&gt;&lt;td&gt;10 oz&lt;&#x2F;td&gt;&lt;td&gt;Halved&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Red onion&lt;&#x2F;td&gt;&lt;td&gt;1&#x2F;2 onion&lt;&#x2F;td&gt;&lt;td&gt;Thin julienne or small dice&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Shredded carrot&lt;&#x2F;td&gt;&lt;td&gt;5 oz&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Kalamata olive&lt;&#x2F;td&gt;&lt;td&gt;1 cup&lt;&#x2F;td&gt;&lt;td&gt;Halved&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Mozerella&lt;&#x2F;td&gt;&lt;td&gt;9 oz&lt;&#x2F;td&gt;&lt;td&gt;Cubed&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Sliced almonds&lt;&#x2F;td&gt;&lt;td&gt;2 oz&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Craisins (optional)&lt;&#x2F;td&gt;&lt;td&gt;Just a handful&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Bell pepper (optional)&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;Sliced&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;This should make about 8-10 individual servings, assuming servings are ~1&#x2F;2 quart.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;recommendations&quot;&gt;Recommendations&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Dress with olive oil and salt.&lt;&#x2F;li&gt;
&lt;li&gt;Highly recommend chopping the greens, IMO this salad is best eaten with a spoon!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;nutrition&quot;&gt;Nutrition&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Ingredient&lt;&#x2F;th&gt;&lt;th&gt;Amount&lt;&#x2F;th&gt;&lt;th&gt;Calories&lt;&#x2F;th&gt;&lt;th&gt;Protein (g)&lt;&#x2F;th&gt;&lt;th&gt;Net Carbs (g)&lt;&#x2F;th&gt;&lt;th&gt;Fat (g)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Baby spinach&lt;&#x2F;td&gt;&lt;td&gt;5 oz&lt;&#x2F;td&gt;&lt;td&gt;42&lt;&#x2F;td&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Baby arugula&lt;&#x2F;td&gt;&lt;td&gt;5 oz&lt;&#x2F;td&gt;&lt;td&gt;30&lt;&#x2F;td&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Mikumame&lt;&#x2F;td&gt;&lt;td&gt;16 oz&lt;&#x2F;td&gt;&lt;td&gt;450&lt;&#x2F;td&gt;&lt;td&gt;50&lt;&#x2F;td&gt;&lt;td&gt;10&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Black beans&lt;&#x2F;td&gt;&lt;td&gt;15 oz&lt;&#x2F;td&gt;&lt;td&gt;385&lt;&#x2F;td&gt;&lt;td&gt;25&lt;&#x2F;td&gt;&lt;td&gt;39&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Corn&lt;&#x2F;td&gt;&lt;td&gt;15 oz&lt;&#x2F;td&gt;&lt;td&gt;245&lt;&#x2F;td&gt;&lt;td&gt;7&lt;&#x2F;td&gt;&lt;td&gt;35&lt;&#x2F;td&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Grape tomatoes&lt;&#x2F;td&gt;&lt;td&gt;10 oz&lt;&#x2F;td&gt;&lt;td&gt;47&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;7&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Red onion&lt;&#x2F;td&gt;&lt;td&gt;1&#x2F;2 onion&lt;&#x2F;td&gt;&lt;td&gt;32&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;7&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Shredded carrot&lt;&#x2F;td&gt;&lt;td&gt;5 oz&lt;&#x2F;td&gt;&lt;td&gt;58&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;10&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Kalamata olive&lt;&#x2F;td&gt;&lt;td&gt;1 cup&lt;&#x2F;td&gt;&lt;td&gt;490&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;49&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Mozerella&lt;&#x2F;td&gt;&lt;td&gt;9 oz&lt;&#x2F;td&gt;&lt;td&gt;768&lt;&#x2F;td&gt;&lt;td&gt;63&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;td&gt;50&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Sliced almonds&lt;&#x2F;td&gt;&lt;td&gt;2 oz&lt;&#x2F;td&gt;&lt;td&gt;360&lt;&#x2F;td&gt;&lt;td&gt;12&lt;&#x2F;td&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;15&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Ingredient&lt;&#x2F;th&gt;&lt;th&gt;Calories&lt;&#x2F;th&gt;&lt;th&gt;Protein (g)&lt;&#x2F;th&gt;&lt;th&gt;Net Carbs (g)&lt;&#x2F;th&gt;&lt;th&gt;Fat (g)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Total&lt;&#x2F;td&gt;&lt;td&gt;2907&lt;&#x2F;td&gt;&lt;td&gt;168&lt;&#x2F;td&gt;&lt;td&gt;131&lt;&#x2F;td&gt;&lt;td&gt;134&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Per Serving&lt;&#x2F;td&gt;&lt;td&gt;363&lt;&#x2F;td&gt;&lt;td&gt;21&lt;&#x2F;td&gt;&lt;td&gt;16&lt;&#x2F;td&gt;&lt;td&gt;17&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;em&gt;Servings are 1&#x2F;2 quart&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Threads - A context strategy for humans and LLMs</title>
        <published>2025-12-29T00:00:00+00:00</published>
        <updated>2025-12-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/2025-threads/"/>
        <id>https://blog.sao.dev/2025-threads/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/2025-threads/">&lt;p&gt;You know what sucks? Coming back 2 months later to a project you started with a burning sense of purpose, and realizing you have no idea what the next step was, or even whether its still a good idea. (&lt;a href=&quot;&#x2F;narrative-and-better-bets&#x2F;&quot;&gt;narratives are important!&lt;&#x2F;a&gt;) A similar need: capturing context, relevant links, and the &quot;why&quot; for enabling code-authoring agents to go solve a problem for you with high autonomy.&lt;&#x2F;p&gt;
&lt;p&gt;Well, recently I&#x27;ve been reading &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Accelerando&quot;&gt;Accelerando&lt;&#x2F;a&gt; (surprisingly relevant these days...) and it has an interseting through line: characters can fork instances of themselves to go do research, tasks, etc. It&#x27;s a bit magical in the book, but very similar to typing &lt;code&gt;claude&lt;&#x2F;code&gt; in your terminal and firing off a request. &lt;code&gt;claude&lt;&#x2F;code&gt; is different from you in extremely important ways, but the analogy is good enough to be useful.&lt;&#x2F;p&gt;
&lt;p&gt;They say that effective vibe coding&#x2F;engineering is an exercise in context management. What I&#x27;ve found to be useful is using a document to manage this forking process, enabling re-entrance, and clear exit conditions - I call them &lt;code&gt;threads&lt;&#x2F;code&gt;. Inside, you put the core bet (what&#x27;s the hypothesis, the insight, inspiration, etc), relevant links, immediate next steps, the definition of done (to close the thread), and a log of significant decisions&#x2F;actions. See the &lt;a href=&quot;https:&#x2F;&#x2F;blog.sao.dev&#x2F;2025-threads&#x2F;#example-template&quot;&gt;example template&lt;&#x2F;a&gt; below. I&#x27;ve found this is enough information to solve both needs from paragraph 1:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;I can cycle back onto this later and get the blurb that inspired 2-months-ago me, see what immediate next steps are, and regain momentum. (see &lt;a href=&quot;https:&#x2F;&#x2F;combo.cc&#x2F;posts&#x2F;its-all-about-momentum-innit&#x2F;&quot;&gt;It&#x27;s all about momentum, innit?&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;I can essentially say &quot;Hey, take a look at &lt;a href=&quot;https:&#x2F;&#x2F;threads.sao.dev&#x2F;pub&#x2F;2025-dev-box&#x2F;&quot;&gt;@threads&#x2F;dev-box.md&lt;&#x2F;a&gt; - what solutions for declarative package installs should I consider?&quot; to an LLM and get useful output.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This process&#x2F;convention has proven useful in many situations:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Peeling off threads when you&#x27;re deep in another task, have discovered an important related task, and want to capture context you can come back to later without totally dumping your mental state.&lt;&#x2F;li&gt;
&lt;li&gt;Asking an LLM to capture significant decisions you&#x27;ve discussed (and their motivations) in its thread file during the agentic code authoring process.&lt;&#x2F;li&gt;
&lt;li&gt;As a place to accrue requirements for a soon-to-be-task to ensure you don&#x27;t forget them later.&lt;&#x2F;li&gt;
&lt;li&gt;A place to refer to when you wonder why you made the decision you did.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the code agent arena, this is a layer above the agent-generated plan. When done well, these should be high value density and be worth checking into your monorepo, allowing humans and code agents to utilize the narratives and decisions within as context. They seem to have a similar value to &lt;a href=&quot;https:&#x2F;&#x2F;adr.github.io&#x2F;&quot;&gt;architectural decision records&lt;&#x2F;a&gt;, though I am admittedly no expert in those.&lt;&#x2F;p&gt;
&lt;p&gt;Like all good things, the idea is simple and flexible: it&#x27;s just a file containing the most important information regarding a task. And the information within is (mostly) all you need to know to close said thread. And it doesn&#x27;t have to be coding related, I even have one for refactoring my physical desk to make it more modular!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h1 id=&quot;appendix&quot;&gt;Appendix&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;example-template&quot;&gt;Example Template&lt;&#x2F;h2&gt;
&lt;pre data-lang=&quot;markdown&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span style=&quot;background-color:#4f5b66;color:#c0c5ce;&quot;&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;title: Thread Template
&lt;&#x2F;span&gt;&lt;span&gt;created: 2025-12-15
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;---
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;# Bet
&lt;&#x2F;span&gt;&lt;span&gt;REPLACE ME! See &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;[motivating note](https:&#x2F;&#x2F;blog.sao.dev&#x2F;2025-threads&#x2F;)&lt;&#x2F;span&gt;&lt;span&gt;. Establishes a format for encoding bets into threads that can be easily spawned and re-entered. This file acts as a template, with some self-reference for context.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;# Context
&lt;&#x2F;span&gt;&lt;span&gt;REPLACE ME!
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;[Narratives and Better Bets](https:&#x2F;&#x2F;blog.sao.dev&#x2F;narrative-and-better-bets&#x2F;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;- Tokens are expensive, keep this succinct
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;# Next Steps
&lt;&#x2F;span&gt;&lt;span&gt;REPLACE ME! This section is technically optional, but should exist as it contains the distillation of how to &amp;quot;get started&amp;quot; again.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;# Definition of Done
&lt;&#x2F;span&gt;&lt;span&gt;REPLACE ME! This is more optional, but also important to add description of specific outcomes that represent payoff. Can be descriptions of test or use cases, or outcomes in the real world, etc.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;# Log
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;## 2025-12-29
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;- Drafted &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;[motivating note](..&#x2F;dev&#x2F;sao&#x2F;notes&#x2F;zt_2025-12-15_external-threads.md)&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;- Implemented this template.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>CPNs, LLMs, and Distributed Applications</title>
        <published>2025-12-19T00:00:00+00:00</published>
        <updated>2025-12-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/cpns-llms-distributed-apps/"/>
        <id>https://blog.sao.dev/cpns-llms-distributed-apps/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/cpns-llms-distributed-apps/">&lt;p&gt;A big theme in LLM-enabled software dev is that verifiable correctness makes it much easier to take bigger leaps with LLMs. E.g. tests, compilers, state machines, etc. While researching for databuild, I recently came across &lt;a href=&quot;https:&#x2F;&#x2F;informs-sim.org&#x2F;wsc19papers&#x2F;155.pdf&quot;&gt;colored petri nets&lt;&#x2F;a&gt;, and instantly saw opportunity.&lt;&#x2F;p&gt;
&lt;p&gt;Colored petri nets (CPNs) are an extension of petri nets. Petri nets are essentially directed bipartite graphs where places can contain tokens, and places are connected by transitions (where the side effects happen). In petri nets, a single token contains no data, and represents an identity-less tokens location in the net. Importantly, petri nets with transitions that have only one input and output and one token globally are equivalent to finite state machines. Colored petri nets extend this model, allowing individual tokens to have data associated with them. This allows CPNs to match closely to the &lt;a href=&quot;https:&#x2F;&#x2F;stanford-cs242.github.io&#x2F;f19&#x2F;lectures&#x2F;08-2-typestate.html&quot;&gt;Rust typestate pattern&lt;&#x2F;a&gt;, and suggests Rust may be able to implement CPN semantics easily.&lt;&#x2F;p&gt;
&lt;p&gt;CPNs are of particular interest because a) it is still hard to write concurrent applications and b) provide the potential for formally verifying concurrent programs at build time. Also, if we can implement a high performance datastore underneath, a CPN framework might be able to handle the hard parts of concurrent applications: state synchronization, conflict detection, deadlock avoidance, and coordinating access to shared resources. These are enabled via two other features of CPNs: guards and multi-token consumption&#x2F;production.&lt;&#x2F;p&gt;
&lt;p&gt;A guard is a list of boolean conditions that may apply to a transition: things that must be true for a token to take that transition. For instance, there need to be more than 0 connections available in the connection pool to claim one. Multi-token consumption&#x2F;production is just what it sounds like: a fork in the network via a transition, where P1 -&amp;gt; T1 -&amp;gt; (P2, P3), so T1 consuming a token from P1 produces a token in each of P2 and P3 simultaneously. Conversely, a join in the network via a transition, where (P1, P2) -&amp;gt; T1 -&amp;gt; P3, would require that both P1 and P2 to have tokens present that are able to transition via T1 into P3, which will also be simultaneous.&lt;&#x2F;p&gt;
&lt;p&gt;One application that has light concurrency involved is web scraping with leased proxies and scrape targets. You have a limited number of proxies to use to proxy your requests, and need to rate limit your usage of all of them to ensure they don&#x27;t over-request a given target. Also, you both want to avoid requesting the same target multiple times concurrently, and also avoid making requests to the domain too frequently, to be a responsible user. Traditionally, this is solved with leasing of resources via a central database, implemented via &lt;code&gt;select for update&lt;&#x2F;code&gt; style semantics in the database. We can imagine implementing this with CPN semantics as having the &lt;code&gt;scrape_target&lt;&#x2F;code&gt; transition being the join of the &lt;code&gt;available_proxies&lt;&#x2F;code&gt; and &lt;code&gt;prioritized_targets&lt;&#x2F;code&gt; places, with a scrape only starting when a proxy is available and a prioritized target is available. You can also imagine implementing other complex distributed scraper features with CPN semantics:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scrape target cooldowns&lt;&#x2F;strong&gt;: after being scraped, a target token transitions to a &lt;code&gt;cool_down&lt;&#x2F;code&gt; state, where using a timed petri net allows us to delay transitioning back to &lt;code&gt;prioritized_targets&lt;&#x2F;code&gt; after the delay period.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Domain-level rate limiting&lt;&#x2F;strong&gt;: add a separate &lt;code&gt;domains&lt;&#x2F;code&gt; token, so that the &lt;code&gt;scrape_target&lt;&#x2F;code&gt; transition is a 3-way join &lt;code&gt;domains x available_proxies x prioritized_targets&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Retry with backoff&lt;&#x2F;strong&gt;: Failed scrapes fork: one token to failed_log, one back to prioritized_targets with incremented retry count and a longer cooldown. Guard prevents retries beyond max attempts.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Result pipeline&lt;&#x2F;strong&gt; — Post-scrape, tokens flow through raw_html → parsed → validated → stored, each with its own concurrency limits, which naturally implements back pressure.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Another application is databuild, where the network is made up of partitions (which are effectively leased to job runs), wants, and job runs, and we would benefit from some self-organizing dynamic process to propagate the data deps and solve user stated partition wants in a safe, efficient, and fast manner. But more on that guy later.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m still figuring this part out, but it seems like there are a few sensible or interesting strategies for implementing CPNs:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;As a traditional app + postgres combo, with transactions implementing simultaneous token movement and state storage, and &lt;code&gt;select for update&lt;&#x2F;code&gt; enabling claiming of tokens for transitions to prevent double moves.&lt;&#x2F;li&gt;
&lt;li&gt;As a single-process Rust binary, with all CPN state stored in memory, enabling move semantics and the typestate pattern to implement the CPN semantics (this could be extremely fast, but need to figure out persistence - maybe snapshotted event log?)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;One thing I&#x27;m trying to figure out is how to solve the partitiong problem: if app state grows to be too large for 1 server&#x27;s memory, is there a way to automatically partition the network&#x2F;tokens to enable fearless concurrency and horizontal scalability? So far the answers seem to be either a) solve in application with archive place&#x2F;transition being part of the network or b) solving this at the database level. Or maybe something even crazier, where the whole network is made up of multiple CPN apps, which themselves expose query&#x2F;consume interfaces?&lt;&#x2F;p&gt;
&lt;p&gt;Tying it all back, if we can figure out a CPN app framework with sensible persistence, we could impose more compile-time constraints on agent-authored code (good for dev velocity) while also providing correctness guarantees and simulation capabilities out of the box that unconstrained computation apps don&#x27;t provide. Exciting!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h1 id=&quot;appendix&quot;&gt;Appendix&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;random-thoughts&quot;&gt;Random Thoughts&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;!! Is there an existing open source service that I can use the test suite from or benchmark against to validate this? Especially if it means just setting claude code off against it?&lt;&#x2F;li&gt;
&lt;li&gt;Timed petri nets - could we emit metrics by default and simulate the net impact of a change to the whole system?&lt;&#x2F;li&gt;
&lt;li&gt;Join transitions as literal joins in the database, with transition conditions implying indexes, and token transitions as inserts and deletes inside a transaction&lt;&#x2F;li&gt;
&lt;li&gt;What extent are we talking about just making a datastore or CPN database?&lt;&#x2F;li&gt;
&lt;li&gt;Distributed network of CPN apps? Or an app as a distributed network of CPN services? It&#x27;s all about partitioning?&lt;&#x2F;li&gt;
&lt;li&gt;CPNs don&#x27;t need explicit re-entrance? Every &quot;step&quot; is a movement of tokens, which is doable completely from the state of all tokens involved?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;potential-bets-llm-written&quot;&gt;Potential Bets (LLM Written)&lt;&#x2F;h2&gt;
&lt;p&gt;To validate this, we need a real hill to climb: reimplement something with CPN semantics and compare. The core question isn&#x27;t &quot;can CPNs be fast&#x2F;correct&quot;—of course they can. It&#x27;s: &lt;strong&gt;does the paradigm make writing simple, correct, and fast concurrent programs easier?&lt;&#x2F;strong&gt; Does the formalism pay for itself in reduced bugs and less bespoke coordination code?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The bet: scraper scheduler (spider-rs)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Implement the decision-making core of a scraper—URL prioritization, domain rate limiting, proxy assignment, retry scheduling—using CPNs. Mock the HTTP layer; benchmark decisions&#x2F;sec and compare coordination code complexity against the original.&lt;&#x2F;p&gt;
&lt;p&gt;Why scrapers? Traditional implementations are full of ad-hoc coordination: rate limit maps, cooldown trackers, retry counters, domain queues. This is exactly what CPNs formalize. Correctness matters (politeness violations get you banned), and timed transitions are central, not incidental. spider-rs is already Rust, so we can fork and compare directly.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Why not the others first?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Option&lt;&#x2F;th&gt;&lt;th&gt;Why not&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Job queue (Faktory)&lt;&#x2F;td&gt;&lt;td&gt;Coordination is incidental; well-understood pattern where CPN overhead may not pay off&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Connection pooler (pgcat)&lt;&#x2F;td&gt;&lt;td&gt;Too small—might not exercise enough CPN capabilities to be compelling&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;CI runner (Buildkite)&lt;&#x2F;td&gt;&lt;td&gt;Too large—incidental complexity (artifacts, logs, shell execution) obscures the coordination story&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;strong&gt;Implementation approach&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In-memory Rust with SQLite snapshots: single-threaded, move semantics apply, extremely fast. Partitioning = separate binaries with disjoint token ownership (enforceable at net-definition time via partition key requirements). Simulation = same CPN execution with mocked effects, enabling correctness testing, failure injection, and time fast-forwarding.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Differentiable Systems</title>
        <published>2025-11-11T00:00:00+00:00</published>
        <updated>2025-11-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/differentiable-systems/"/>
        <id>https://blog.sao.dev/differentiable-systems/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/differentiable-systems/">&lt;p&gt;Systems that are &quot;differentiable&quot;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Can have performance or quality attributed to specific components, so that those components can be improved&lt;&#x2F;li&gt;
&lt;li&gt;Components have literal measures of quality so that progress in their quality is measurable&lt;&#x2F;li&gt;
&lt;li&gt;You can infer impact from one component onto others&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;old-kernel-discussion&quot;&gt;OLD! &quot;Kernel&quot; Discussion&lt;&#x2F;h1&gt;
&lt;p&gt;What&#x27;s a kernel?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;General: fundamental, irreducible core&lt;&#x2F;li&gt;
&lt;li&gt;Operating systems: core program that mediates access to hardware&lt;&#x2F;li&gt;
&lt;li&gt;Machine learning: a mapping from the domain space to an easily separable space&lt;&#x2F;li&gt;
&lt;li&gt;Linear algebra: the set of vectors that a matrix maps to zero&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There is something tempting in this - to characterize a kernel as a mapping between domains or problem spaces. This is explicit in ML, but abstracted (or embedded in the domain) for software. In software, kernels mediate access to shared resources, and map the real constraints and interfaces of the hardware into a simpler interface and interaction model for applications and services.&lt;&#x2F;p&gt;
&lt;p&gt;Zooming out a little, it is satisfying to imagine a complete application through this lens. For example, let&#x27;s take Amazon retail: the incredible amounts of code that make it up are mappings of fractal-complex business situations and needs into interfaces that enable specific capabilities. At the highest level, this is turning the &quot;purchase click&quot; into a box on your doorstep. Breaking this down, its the sub components of the problem: checking that the item is still in stock, charging the card, routing delivery, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Individually this might not be too exciting. This is already explicit in ML, and a small hop from the &quot;abstraction&quot; concept already fundamental in software. I think the utility might be in the unification or synthesis - that we can consider different things (like&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Tension between general &quot;fundamental core&quot; definition and ML &quot;useful mapping&quot; definition?&lt;&#x2F;li&gt;
&lt;li&gt;The simpler your subspace, the simpler your kernel can be
&lt;ul&gt;
&lt;li&gt;Example: library code vs application code&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;What does &quot;kernel of truth&quot; mean? More similar to the math &quot;maps to zero&quot; definition? Neurons that fire together?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Japan Recs</title>
        <published>2025-10-28T00:00:00+00:00</published>
        <updated>2025-10-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/japan-recs/"/>
        <id>https://blog.sao.dev/japan-recs/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/japan-recs/">&lt;h1 id=&quot;meiji-jingu-to-shibuya-walk&quot;&gt;Meiji Jingu to Shibuya Walk&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;Go to &lt;a href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;ruRzLx1w3LCYyYRy9&quot;&gt;meiji jingu mae station&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;ivnX8hZQmUmM3gR39&quot;&gt;Harajuku station&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Optionally walk around &lt;a href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;mD3aE57s9ZqcNtKK9&quot;&gt;Yoyogi park&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Walk up to &lt;a href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;tJQp9ewBuGqSxwd6A&quot;&gt;Meiji Jingu&lt;&#x2F;a&gt; (temple)&lt;&#x2F;li&gt;
&lt;li&gt;Explore temple and surrounding area&lt;&#x2F;li&gt;
&lt;li&gt;Walk over to takeshita street (this family mart https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;TECsJANVNe8eF1pYA)&lt;&#x2F;li&gt;
&lt;li&gt;Walk down takeshita street to this point and take a left on harajuku st https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;epknc7h4eyDWRumS8&lt;&#x2F;li&gt;
&lt;li&gt;Walk down to here https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;xgFcCJp73XpGAzCZ7&lt;&#x2F;li&gt;
&lt;li&gt;Take a right towards cat street&lt;&#x2F;li&gt;
&lt;li&gt;Take a right onto cat street here https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;f1KUiJkhyYrCJxhL8&lt;&#x2F;li&gt;
&lt;li&gt;Keep walking down cat street and sight seeing (good second hand stores!) https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;1vBgdoGBGZ1HYRjr5&lt;&#x2F;li&gt;
&lt;li&gt;Continue down to the north end of shibuya here https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;zLCUbuUftP8B5S9u8&lt;&#x2F;li&gt;
&lt;li&gt;Check out the view at shibuya sky https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;95hrd94RPgn4JsEv8&lt;&#x2F;li&gt;
&lt;li&gt;Go give the goodest boi hachiko a pet https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;2DXPfUKbc56dufsL8&lt;&#x2F;li&gt;
&lt;li&gt;Check out shopping around shibuya&lt;&#x2F;li&gt;
&lt;li&gt;Experience the shibuya scramble https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;JF55FkaG5ym1LTci8&lt;&#x2F;li&gt;
&lt;li&gt;Check out Loft in shibuya https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;heE9Bw83rneudqfu7&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For google directions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;ddzyfb4dFBvBE2Cj7&quot;&gt;Walking route&lt;&#x2F;a&gt; after exploring yoyogi &#x2F; meiji jingu&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Narrative and Better Bets</title>
        <published>2025-06-22T00:00:00+00:00</published>
        <updated>2025-06-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/narrative-and-better-bets/"/>
        <id>https://blog.sao.dev/narrative-and-better-bets/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/narrative-and-better-bets/">&lt;p&gt;One thing I love about &lt;a href=&quot;https:&#x2F;&#x2F;www.yobi.ai&quot;&gt;Yobi&lt;&#x2F;a&gt; (my job) is one of its core values: make smart bets. It&#x27;s one of those kinda reductive statements that requires a lot of context to be practically instructive, but fulfills its role in creating rhetorical focus. Everything we do is a bet. bets have risks, upsides, and downsides. Different bets interact and inform future bets. Given your portfolio of bets, what is your expected distribution of outcomes? Etc. These aren&#x27;t the capped ludic bets of the casino, where all probabilities are known and there are no inter-bet interactions. They are the bets of the natural world, where bets build on each other and can lead to compounding results. Some bets have catastrophic downside (mostly bad bets), and some have incredible upside (some good bets). Narrative has a critical role in bet making: most good options are hidden to almost everyone - imagine the startup opportunity, which relies on key recent tech advancements, changes in market dynamics, a coalescing of talent, a unique framing of the problem, etc. Predicting success (binary or degree) for this venture relies on a lot of information that simply hasn&#x27;t happened yet.&lt;&#x2F;p&gt;
&lt;p&gt;However, narrative allows you to abstract and achieve much higher leverage over the problem&#x2F;world, like our evolved brains allow pattern recognition in our senses &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. Every step up the intelligence ladder represents another filter to remove irrelevant information, enabling efficient and effective planning. It allows you to take a hopelessly detailed and complex situation, and recognize and distill its core risks or opportunities. It helps you draw a (fuzzy) arc through time to the vague but potentially very beneficial outcome. And practically, it lets you recognize a good bet when you stumble upon it. Narratives and their pursuit are themselves bet generating processes, making the intentional cultivation of narrative a precursor for doing things of substantial significance. I think this lens is also useful for separating narrative from its close relative understanding. Understanding is also a noble pursuit, but appears more passive in its nature and effect. Narrative is about drawing the line into the unobserved space, and updating it when you get there. Narrative is instructive about where to focus, about what matters, and it is anti-fragile &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; to new information that disconfirms it, as we either discard it in favor of a new narrative, or build an even stronger narrative incorporating said new information.&lt;&#x2F;p&gt;
&lt;p&gt;Narrative is required for rising above random chance, in terms of achieving significant outcomes. In fact, back at the start of 2023 before joining Yobi, many other companies wanted me to join to help them build LLM-powered applications. ChatGPT was starting to make real waves outside of &quot;good enough&quot; copy writing, and I even had significant experience in language modeling. But in the end I didn&#x27;t, largely due to the narrative I had cultivated. The underlying technology was not differentiated, and my opportunity to differentiably impact any company that was more than a barnacle on the back of the LLM whales was minimal. Yobi, on the other hand, aligned almost completely with my views on ML and its relationship with the market, and was still early. Yobi bet that we could achieve privacy preserving personalization with manifold learning, allowing said information (not data) to be brought to bear in many applications currently served by fragmented offerings. Insightful, high leverage, and not compelling to those that aren&#x27;t in the know. By comparison, LLMs and their derivatives? Obvious, high competition, with everyone and their mom&#x27;s racing to the gold rush.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Max Bennett, &lt;a href=&quot;https:&#x2F;&#x2F;www.abriefhistoryofintelligence.com&#x2F;&quot;&gt;A Brief History of Intelligence&lt;&#x2F;a&gt; - A retelling of the development of intelligence via critical capability steps bouncing between neuroscience and machine learning.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Nassim Taleb, &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Antifragile_(book)&quot;&gt;Antifragile&lt;&#x2F;a&gt; - Really incredible book. I have read it yearly ever since 2022.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Why Write</title>
        <published>2025-06-21T00:00:00+00:00</published>
        <updated>2025-06-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Stuart Axelbrooke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.sao.dev/why-write/"/>
        <id>https://blog.sao.dev/why-write/</id>
        
        <content type="html" xml:base="https://blog.sao.dev/why-write/">&lt;h1 id=&quot;why-write&quot;&gt;Why Write&lt;&#x2F;h1&gt;
&lt;p&gt;Hey all, it&#x27;s Stuart, probably showing up in your inbox. I&#x27;m back, writing my little thoughts, and would love if you&#x27;d lend an ear. What is it, a blog? A substack? Not exactly - I&#x27;m not building an audience, I&#x27;m not promoting anything - but I am hoping to inspire conversation and build meaning &#x2F; crystalize ideas that are meaningful to me. Practically, I&#x27;ll just be sending out emails whenever I write something or put together a photo album of some life experience, and want to use a direct channel to share it not mediated by capitalism.&lt;&#x2F;p&gt;
&lt;p&gt;But why write? Well, it&#x27;s obvious that being online isn&#x27;t cool anymore. But really - there is a lot of meaning to make. You need to build narratives to understand, to make bets, and build community. I think a lot of the poverty of meaning we&#x27;re experiencing in the last 5 years is from the systematic transfer of attention away to naively capitalist content platforms, away from flawed institutions (like journalists, movie makers, etc) that at least believed in their role in making meaning. This is a personal attempt to wrest that time and attention back, and if nothing else internalize responsibility for narrative building of my life and its relationship with others. And I hope you&#x27;ll take part in this with me, please respond with your thoughts so we can build a narrative together. And be warned, it won&#x27;t be polished - all that&#x27;s real and natural isn&#x27;t - but it will be true.&lt;&#x2F;p&gt;
&lt;p&gt;In case you want to read the first real piece:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;[[zt_2025-06-22_narrative-and-better-bets]]&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
</feed>
