<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Brandon Lucas</title>
<link>https://blu.cx/writing/</link>
<description>Brandon Lucas writes about software, history and philosophy, and builds Kai and Lyceum.</description>
<language>en</language>
<atom:link href="https://blu.cx/rss.xml" rel="self" type="application/rss+xml"/>
<item><title>Software Builder Economics with AI</title><link>https://blu.cx/posts/journal/2026-10-12-ai-like-internet/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2026-10-12-ai-like-internet/</guid><pubDate>Mon, 12 Oct 2026 12:00:00 +0000</pubDate><description>It’s strange in that it feels economically wasteful to keep building if your tokens are spent or if the models are being throttled that day. Like if the internet went out before. Switching to or is…</description><content:encoded><![CDATA[<p>It’s strange in that it feels economically wasteful to keep building if your tokens are spent or if the models are being throttled that day. Like if the internet went out before. Switching to higher-level thinking or reading something useful is probably the best thing to do while you wait for them to refresh.</p>
]]></content:encoded></item>
<item><title>Captured Email</title><link>https://blu.cx/posts/journal/2026-10-12-costs/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2026-10-12-costs/</guid><pubDate>Mon, 12 Oct 2026 12:00:00 +0000</pubDate><description>Email being un-self-hostable and captured yet universal for auth online is really sad. Seems to me sites should move toward deprecating them in favor of passkeys or SSO and just asking for them for we…</description><content:encoded><![CDATA[<p>Email being un-self-hostable and captured yet universal for auth online is really sad. Seems to me sites should move toward deprecating them in favor of passkeys or SSO and just asking for them for account recovery purposes until we figure out a better solution.</p>
]]></content:encoded></item>
<item><title>Schopenhauer</title><link>https://blu.cx/posts/journal/2026-10-08-schopenhauer/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2026-10-08-schopenhauer/</guid><pubDate>Thu, 08 Oct 2026 12:00:00 +0000</pubDate><description>Schopenhauer on dead languages: they are deterministic!</description><content:encoded><![CDATA[<p>Schopenhauer on dead languages: they are deterministic!</p>
]]></content:encoded></item>
<item><title>Kai Devlog #6: κατακλυσμός!</title><link>https://blu.cx/posts/blog/2026-09-19-kai-devlog-6/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-09-19-kai-devlog-6/</guid><pubDate>Sat, 19 Sep 2026 12:00:00 +0000</pubDate><description>Refactoring Kai's plugin schema while adding better errors, system management, deployment, ISO creation, SOPS secrets, and backend escape hatches.</description><content:encoded><![CDATA[<blockquote>
<p>Are we being good ancestors?</p>
<p>— <a href="https://longnow.org/clock/">Jonas Salk, referenced from <em>The 10,000-year clock</em> below</a></p>
</blockquote>
<!-- Keep adjacent quotations as separate blockquotes. -->
<blockquote>
<p>The 10,000-year clock will mark time with astronomic and calendric displays and a chime generator designed with the help of Brian Eno that can produce over 3.5 million unique bell chime sequences — one for every day the clock is visited for the next 10,000 years.</p>
</blockquote>
<!-- Keep adjacent quotations as separate blockquotes. -->
<blockquote>
<p>Ten thousand years is about the age of modern civilization, so the clock will measure out a future of civilization equal to its past. This assumes our civilization is in the middle of whatever journey we are on — an implicit statement of optimism.</p>
</blockquote>
<p><a href="https://longnow.org/clock/">The 10,000-year clock</a></p>
<p><img src="/images/blog/kai-devlog-6/clock.png" alt="The 10,000-year clock mechanism" /></p>
<p>How do you build a clock that will last 10,000 years? Look at the list given in the article above:</p>
<blockquote>
<p>Longevity</p>
<p>10,000-year clocks should display the correct time for the next 10,000 years</p>
<ul>
<li>Go slow</li>
<li>Minimize sliding friction</li>
<li>Stay clean and dry</li>
<li>Expect bad weather and earthquakes</li>
<li>Expect non-malicious human interaction</li>
<li>Don’t tempt thieves</li>
</ul>
<p>Maintainability</p>
<p>It should be possible to maintain a 10,000-year clock with little maintenance, using bronze-age technology</p>
<ul>
<li>Use familiar materials</li>
<li>Make it easy to build parts</li>
<li>Include the manual</li>
</ul>
<p>Transparency</p>
<p>It should be possible to determine the operational principles of a 10,000-year clock with close inspection</p>
<ul>
<li>Allow inspection</li>
<li>Allow rehearsed motions</li>
<li>Expect restarts</li>
</ul>
<p>Evolvability</p>
<p>It should be possible to improve a 10,000-year clock over time</p>
<ul>
<li>Separate functions</li>
<li>Provide simple interfaces</li>
</ul>
<p>Scalability</p>
<p>It should be possible to build working models of a 10,000-year clock from table top to monumental size using the same design</p>
<ul>
<li>Make all parts similar size</li>
</ul>
</blockquote>
<p>Some of these sound similar to software, no?</p>
<blockquote>
<ul>
<li>Make it easy to build parts</li>
<li>Include the manual</li>
<li>Separate functions</li>
<li>Provide simple interfaces</li>
</ul>
</blockquote>
<p>Indeed, many fundamental principles of nature are true across domains.</p>
<p>This latest release is called <a href="https://github.com/thebrandonlucas/kai/releases/tag/v0.0.7"><em>κατακλυσμός</em></a>, and for the savvy transliterator, you can see that means <em>cataclysm</em>!</p>
<p>But its real meaning in <a href="https://en.wikipedia.org/wiki/Koine_Greek">Koine</a> Greek is more like <em>flood</em>, which is a better description for this release. The biggest <em>single</em> change, though, is once again another plugin refactor — and we’ll probably do it again. Getting those abstractions right will allow Kai to be designed for the Long Now, and hopefully grow organically into something great that can stand the test of time.</p>
<p><strong>Please note: SOPS/system-level commands modify system state and that and the ISO command is very experimental. Use them with caution on disposable machines as this is still a prototype!</strong></p>
<p>Highlights:</p>
<ul>
<li>Refactored the plugin schema (again)</li>
<li>Lots of ergonomics and UX infrastructure</li>
<li>Tests for every command implementation!</li>
<li>Better errors and Nix error bubbling</li>
<li><code>system</code>-level commands to control your Nix config!
<ul>
<li><code>generations</code>/<code>rollback</code>/<code>switch</code></li>
</ul>
</li>
<li>ISO create command (for an eventual KaiOS…?)</li>
<li><a href="https://github.com/getsops/sops">SOPS</a> secrets</li>
<li>backend-scoping and passthrough</li>
</ul>
<h2 id="refactoring-the-plugin-schema-again">Refactoring the plugin schema (again)<a href="#refactoring-the-plugin-schema-again" aria-label="Link to heading 'Refactoring the plugin schema (again)'" data-heading-content="Refactoring the plugin schema (again)" class="anchor"></a></h2>
<p>Kai is built via plugins similar to how <a href="https://caddyserver.com/">Caddy</a> works. The standard (and currently only useful) plugin, <code>std</code>, is compiled using <code>xkai</code>, which, like <a href="https://github.com/caddyserver/xcaddy"><code>xcaddy</code></a>, just bundles the <code>std</code> package together and provides a generic CLI skeleton around it. As noted in previous posts, getting this plugin structure right is crucial to Kai’s essential goals of being <em>generic</em>, <em>flexible</em>, and <em>modular</em>. The single biggest refactor is <a href="https://github.com/thebrandonlucas/kai/pull/86">changing the schema design</a> to more accurately reflect the separation between commands, blocks, and backend implementations.</p>
<h2 id="ergonomics">Ergonomics<a href="#ergonomics" aria-label="Link to heading 'Ergonomics'" data-heading-content="Ergonomics" class="anchor"></a></h2>
<p>Kai didn’t have much in the way of a help menu, and errors literally just spit out raw Roc constructs (gross!) which made them difficult for humans to read.</p>
<h3 id="help-menu">Help Menu<a href="#help-menu" aria-label="Link to heading 'Help Menu'" data-heading-content="Help Menu" class="anchor"></a></h3>
<p>Finally, a proper help menu!</p>
<pre><code class="language-sh">kai
A friendly frontend for determinate computing

Usage:
  kai [OPTIONS] &lt;COMMAND&gt; [ARGUMENTS] [--json]

Kai is a tool for providing a simplified interface on top of determinate
systems (mainly Nix) for ease of use. Commands often correspond with a
Kaifile block defining their behavior:

  $ kai shell
  $ cowsay &quot;Hello from Kai!&quot;

  # Kaifile
  shell {
      packages: [&quot;cowsay&quot;]
  }

Commands:
  build     Build an artifact declared in the Kaifile.
  deploy    Build and activate a machine on its declared target.
  image     Build an image from a machine declared in the Kaifile.
  iso       Build a bootable ISO from a machine declared in the Kaifile.
  machine   Build a machine declared in the Kaifile.
  run       Run a task declared in the Kaifile.
  service   Build a service declared in the Kaifile.
  shell     Enter an inline or declared developer environment.
  system    Manage machine-wide state.
  update    Update and lock project dependencies.
  workflow  Run a workflow declared in the Kaifile.
  version   Print version information.

Options:
  -f, --file &lt;PATH&gt;  Use the Kaifile at PATH
      --json         Output JSON Lines
      --no-color     Disable colored output
  -y, --yes          Assume yes for confirmation prompts.
  -h, --help         Print help

Environment:
  KAI_DIR             Project-local workspace directory (default: .kai)

More information: https://github.com/thebrandonlucas/kai
</code></pre>
<h4 id="cli-examples-on-each-help-menu">CLI examples on each help menu<a href="#cli-examples-on-each-help-menu" aria-label="Link to heading 'CLI examples on each help menu'" data-heading-content="CLI examples on each help menu" class="anchor"></a></h4>
<p>Each subcommand has a help menu with its equivalent <code>Kaifile</code> block example if applicable:</p>
<pre><code class="language-sh">kai help build
Build an artifact declared in the Kaifile.

Usage:
  kai [OPTIONS] build [BACKEND] &lt;ARTIFACT&gt; [--json]

Examples:
  kai build &lt;my-artifact&gt;

Kaifile block:
  build my-artifact {
    environment: dev
    run: [&quot;make&quot;]
    output: &quot;result&quot;
  }

Arguments:
  BACKEND  Optional backend name
  ARTIFACT  Artifact name from the Kaifile

Options:
  -f, --file &lt;PATH&gt;  Use the Kaifile at PATH
      --json         Output JSON Lines
  -y, --yes          Assume yes for confirmation prompts.
  -h, --help         Print help
</code></pre>
<h3 id="--json-outputs"><code>--json</code> Outputs<a href="#--json-outputs" aria-label="Link to heading '--json Outputs'" data-heading-content="--json Outputs" class="anchor"></a></h3>
<p><a href="https://clig.dev/#output">clig.dev</a> has a great entry on how you should design everything for humans <em>and</em> machines (so tools can be composable), so we have a <code>--json</code> flag that should work for each command:</p>
<pre><code class="language-sh">kai help build --json
{&quot;type&quot;:&quot;help&quot;,&quot;message&quot;:&quot;Build an artifact declared in the Kaifile.\n\nUsage:\n  kai [OPTIONS] build [BACKEND] &lt;ARTIFACT&gt; [--json]\n\nExamples:\n  kai build &lt;my-artifact&gt;\n\nKaifile block:\n  build my-artifact {\n    environment: dev\n    run: [\&quot;make\&quot;]\n    output: \&quot;result\&quot;\n  }\n\nArguments:\n  BACKEND  Optional backend name\n  ARTIFACT  Artifact name from the Kaifile\n\nOptions:\n  -f, --file &lt;PATH&gt;  Use the Kaifile at PATH\n      --json         Output JSON Lines\n  -y, --yes          Assume yes for confirmation prompts.\n  -h, --help         Print help&quot;}
</code></pre>
<p>I should probably add a <code>--pretty</code> flag or something for humans to be able to use the JSON version if they’d like, but we’ll wait and see if that use case gets justified.</p>
<h3 id="ansi">ANSI<a href="#ansi" aria-label="Link to heading 'ANSI'" data-heading-content="ANSI" class="anchor"></a></h3>
<p>We added ANSI coloring infrastructure. Just the very basics, <strong>bold</strong> for headers, red for errors, etc. The base is there and we can easily add/modify as needed.</p>
<h3 id="errors">Errors<a href="#errors" aria-label="Link to heading 'Errors'" data-heading-content="Errors" class="anchor"></a></h3>
<h4 id="kai-errors">Kai Errors<a href="#kai-errors" aria-label="Link to heading 'Kai Errors'" data-heading-content="Kai Errors" class="anchor"></a></h4>
<p>I think the correct philosophy around error message design is <a href="https://clig.dev/#errors">clig.dev’s</a>:</p>
<blockquote>
<p>One of the most common reasons to consult documentation is to fix errors. If you can make errors into documentation, then this will save the user loads of time.</p>
<p><strong>Catch errors and rewrite them for humans.</strong> If you’re expecting an error to happen, catch it and rewrite the error message to be useful. Think of it like a conversation, where the user has done something wrong and the program is guiding them in the right direction. Example: “Can’t write to file.txt. You might need to make it writable by running ‘chmod +w file.txt’.”</p>
<p><strong>Signal-to-noise ratio is crucial.</strong> The more irrelevant output you produce, the longer it’s going to take the user to figure out what they did wrong. If your program produces multiple errors of the same type, consider grouping them under a single explanatory header instead of printing many similar-looking lines.</p>
<p><strong>Consider where the user will look first.</strong> Put the most important information at the end of the output. The eye will be drawn to red text, so use it intentionally and sparingly.</p>
<p><strong>If there is an unexpected or unexplainable error, provide debug and traceback information, and instructions on how to submit a bug.</strong> That said, don’t forget about the signal-to-noise ratio: you don’t want to overwhelm the user with information they don’t understand. Consider writing the debug log to a file instead of printing it to the terminal.</p>
<p>Make it effortless to submit bug reports. One nice thing you can do is provide a URL and have it pre-populate as much information as possible.</p>
</blockquote>
<p>That last one wasn’t considered in this release, but I do love the idea and will add it in the future.</p>
<p>Now, errors that used to look like this:</p>
<pre><code class="language-text">Program exited with error: PlanningFailed({ backend: &quot;nix&quot;, command:
&quot;build&quot;, location: None, message: &quot;build requires exactly one config
name&quot;, plugin: &quot;std&quot; })
</code></pre>
<p>Now, in the new release, look like this:</p>
<pre><code class="language-text">error: build requires exactly one artifact argument
usage: kai build &lt;ARTIFACT&gt;
example: kai build &lt;my-artifact&gt;
</code></pre>
<p>Much better!</p>
<h4 id="nix-error-bubbling">Nix Error Bubbling<a href="#nix-error-bubbling" aria-label="Link to heading 'Nix Error Bubbling'" data-heading-content="Nix Error Bubbling" class="anchor"></a></h4>
<p>Since the <code>std</code> plugin in Kai sits on top of Nix, there will always be a subset of errors that Kai won’t be able to capture itself, since there can always be drift between how Kai mirrors the underlying implementation and we’ll never be able to have a perfect one-to-one error map. For example, if the user defines a package <code>foo</code> that is not in <code>nixpkgs</code>, Kai will presently still just write the <code>flake.nix</code> file and try to evaluate it under the hood, resulting in one of those ugly gargantuan Nix errors like:</p>
<pre><code class="language-text">error:
       … while calling the 'derivationStrict' builtin
         at «nix-internal»/derivation-internal.nix:37:12:
           36|
           37|   strict = derivationStrict drvAttrs;
             |            ^
           38|

       … while evaluating derivation 'nix-shell'

       … while evaluating attribute 'nativeBuildInputs' of derivation 'nix-shell'

       (stack trace truncated; use '--show-trace' to show the full, detailed trace)

       error: attribute 'foo' missing
       at /nix/store/…-source/flake.nix:7:15:
            6|       packages = [
            7|               nixpkgs.&quot;legacyPackages&quot;.&quot;x86_64-linux&quot;.&quot;foo&quot;
             |               ^
            8|       ];
       Did you mean one of fio, fjo, folo, foot or fop?
</code></pre>
<p>This violates the rules above by showing so much information <em>internal to Nix</em> itself rather than the user’s code, where the violation actually lies. That should be up front and immediately obvious.</p>
<p>The real error is just this:</p>
<pre><code class="language-text">error: attribute 'foo' missing
at /nix/store/…-source/flake.nix:7:15:
    6|       packages = [
    7|               nixpkgs.&quot;legacyPackages&quot;.&quot;x86_64-linux&quot;.&quot;foo&quot;
     |               ^
    8|       ];
Did you mean one of fio, fjo, folo, foot or fop?
</code></pre>
<p>So we attempt to strip this out, but more work/experimentation will need to be done.</p>
<h2 id="features">Features<a href="#features" aria-label="Link to heading 'Features'" data-heading-content="Features" class="anchor"></a></h2>
<h3 id="import-blocks"><code>import</code> blocks<a href="#import-blocks" aria-label="Link to heading 'import blocks'" data-heading-content="import blocks" class="anchor"></a></h3>
<p><code>Kaifile</code>s can now import each other! This is not a whole module system like a programming language, but rather basically just string concatenation under the hood similar to <code>Caddy</code>. We really just needed a way for large <code>Kaifile</code>s to be expanded.</p>
<p><code>Kaifile</code> imports work like this:</p>
<pre><code class="language-Kaifile"># parts/environment.kai
environment dev {
  packages: [&quot;cowsay&quot;, &quot;fortune&quot;]
}
</code></pre>
<p>Then, <code>Kaifile</code>:</p>
<pre><code class="language-Kaifile">import &quot;parts/environment.kai&quot;
shell {
  environment: dev
}
</code></pre>
<h3 id="system-command-and-subcommand-groups"><code>system</code> Command and Subcommand Groups<a href="#system-command-and-subcommand-groups" aria-label="Link to heading 'system Command and Subcommand Groups'" data-heading-content="system Command and Subcommand Groups" class="anchor"></a></h3>
<p>Why should Kai only be used at the project level? What if you wanted to use it to configure your whole system like NixOS does? What if we could start working on combining the UX and flash of <a href="https://omarchy.org/">Omarchy</a> with the power of NixOS? The first step toward this is to group commands which do this under the <code>system</code> command (i.e. command which manipulate the system). This is the first command which has a few subcommands:</p>
<pre><code class="language-text">WARNING: These commands are experimental and can modify your system! Advised to use only on throwaway machines.
</code></pre>
<pre><code class="language-sh">kai system
Manage machine-wide state.

Usage:
  kai system &lt;COMMAND&gt;

Commands:
  generations  List local system generations.
  rollback     Roll back the current host one system generation.
  switch       Build and activate the declared machine.
</code></pre>
<p>One of the most powerful features of NixOS over other operating systems is the ability to rollback your machine’s state. Since most things in NixOS are declarative in a version-controlled file, this provides an enormous advantage over other operating systems which quickly become a tangled layered mess descending into dependency hell. Nix’s <code>rollback</code> allows you to switch between <code>generations</code> of your system’s state if anything goes wrong, or you just liked how one of your previous versions worked better. Paired with a solid UX, I expect this concept to unlock many novel use cases for people that aren’t fully utilizing their NixOS systems. At present, our <code>rollback</code> just goes back to the previous generation, the most useful command to recover from brokenness caused by an upgrade.</p>
<p>The other command added, <code>switch</code>, is what actually does the activation of your Nix derivations to change the state of the software. Under the hood this is <code>nixos-rebuild switch</code> and is probably the most essential and commonly used NixOS command.</p>
<h3 id="deploy">Deploy<a href="#deploy" aria-label="Link to heading 'Deploy'" data-heading-content="Deploy" class="anchor"></a></h3>
<p>Another big use-case unlock. If you have a machine in the cloud, it’s extremely convenient to be able to deploy a new config at will. I’ll be adding this to my personal websites — as soon as I can configure them completely using Kai on NixOS machines (more to come!):</p>
<pre><code class="language-sh">kai help deploy
Build and activate a machine on its declared target.

Usage:
  kai [OPTIONS] deploy [BACKEND] &lt;MACHINE&gt; [--json]

Examples:
  kai deploy &lt;machine&gt;
  kai deploy -y &lt;machine&gt;

Kaifile block:
  machine &lt;machine&gt; {
        environment: server
        system: &quot;x86_64-linux&quot;
        target: &quot;root@example.com&quot;
  }

Arguments:
  BACKEND  Optional backend name
  MACHINE  Machine name from the Kaifile

Options:
  -f, --file &lt;PATH&gt;  Use the Kaifile at PATH
      --json         Output JSON Lines
  -y, --yes          Assume yes for confirmation prompts.
  -h, --help         Print help
</code></pre>
<h3 id="iso">ISO<a href="#iso" aria-label="Link to heading 'ISO'" data-heading-content="ISO" class="anchor"></a></h3>
<blockquote>
<p><strong>WARNING! Experimental, please don’t use on any important machines yet.</strong></p>
</blockquote>
<p>Another path to an Omarchy-but-with-Kai is an ISO. With a valid machine config, you should be able to boot into a valid NixOS machine from a <code>Kaifile</code> with this ISO on a flash drive! Eventually, this’ll be spruced up to give a much better UX.</p>
<h3 id="sops-secrets">SOPS Secrets<a href="#sops-secrets" aria-label="Link to heading 'SOPS Secrets'" data-heading-content="SOPS Secrets" class="anchor"></a></h3>
<blockquote>
<p><strong>WARNING! Experimental, please don’t use on any important machines yet.</strong></p>
</blockquote>
<p>We added <a href="https://github.com/Mic92/sops-nix"><code>sops-nix</code></a>, a standard tool for safely integrating encrypted secrets in Nix/Git. This is a bit limited as at the moment <em>you</em> have to encrypt the secret — Kai only accepts already-SOPS encrypted secrets and doesn’t handle that itself. However, this unlocks being able to deploy machines with predefined secrets on them.</p>
<h3 id="backend-escape-hatch">Backend Escape Hatch<a href="#backend-escape-hatch" aria-label="Link to heading 'Backend Escape Hatch'" data-heading-content="Backend Escape Hatch" class="anchor"></a></h3>
<p>I wanted to allow the NixOS options assignments in flakes resulting from <code>Kaifile</code>s. This is a tricky problem because Nix has a <em>lot</em> of custom options keys for tons of services. Obviously Kai can’t re-hardcode all these. Further, there are some concepts which Kai is tying itself directly to Nix for, like overlays, which <a href="https://guix.gnu.org/">Guix</a> doesn’t have (though it does have analogous concepts).</p>
<p>At present, I’m unaware of a good way around the “non-overlapping concepts” problem for backends which should allow the Kai frontend to remain seamlessly usable. So the best approach I could find is to provide an escape hatch for these scenarios:</p>
<pre><code class="language-Kaifile">machine server {
 environment: server
 system: &quot;x86_64-linux&quot;
 users: [&quot;agent&quot;]
 services: [&quot;openssh&quot;]

 backend nix {
   networking.hostName: &quot;kai-server&quot;
   services.fail2ban.enable: true
   networking.firewall.allowedTCPPorts: [22, 443]
 }
}
</code></pre>
<p>Everything under <code>backend nix</code> is a restricted set of typed assignments that “lowers” Kaifile code into <code>flake.nix</code>. This is a big flexibility leap forward for Kai which will allow the base cases to remain separately abstracted from the bespoke code in the short term. Long-term, we’ll probably have to find something better.</p>
<h3 id="global-backend-config">Global Backend Config<a href="#global-backend-config" aria-label="Link to heading 'Global Backend Config'" data-heading-content="Global Backend Config" class="anchor"></a></h3>
<p>One repo I was attempting to add Kai to did not use the standard <code>nixpkgs</code> branch but rather <code>nixos-unstable-small</code>, a use case I hadn’t accounted for yet. While I’d ultimately like a large set of decisions like this to be configurable by the Kai binary, I think it’s also useful to have it available as a declarative config option. This is of course backend-specific, so we also scope this by its backend:</p>
<pre><code class="language-Kaifile">backend nix {
 packages: &quot;github:NixOS/nixpkgs/nixos-unstable-small&quot;
}
</code></pre>
<h3 id="scoped-architecture-predicates">Scoped Architecture Predicates<a href="#scoped-architecture-predicates" aria-label="Link to heading 'Scoped Architecture Predicates'" data-heading-content="Scoped Architecture Predicates" class="anchor"></a></h3>
<p>Similar to the above, this repo also required architecture-specific config. We already had <em>OS</em>-level config e.g.:</p>
<pre><code class="language-Kaifile">on linux {
    ..
}

on macos {
    ..
}
</code></pre>
<p>But this doesn’t provide the full host picture, as you also have architectural variation:</p>
<pre><code class="language-Kaifile">on linux x86_64 {
    ..
}

on macos aarch64 {
    ..
}
</code></pre>
<h2 id="future">Future<a href="#future" aria-label="Link to heading 'Future'" data-heading-content="Future" class="anchor"></a></h2>
<p>With all these features, Kai’s surface area is getting bigger. There are a growing number of commands that do very different things. <a href="https://en.wikipedia.org/wiki/Unix_philosophy">Unix Philosophy</a> says that tools should do one thing well. But the legendary Unix designers had an early-mover advantage in that they got to define much the set of tools that we use today, and therefore they didn’t have to operate as strictly <em>within</em> the set of available tooling. If Kai took the approach of splitting up every concern into sub-binaries, we would both have to ship a binary for every little tool <code>rollback</code>, <code>deploy</code>, <code>switch</code>, <code>generations</code> etc. <em>and</em> you’d have to know that each one of these things was Kai-specific. Nix does some of this with <code>nixos-rebuild</code> etc., but I think in the modern world we don’t want to have to redefine entire systems if we can get the majority of benefits without doing it. Swiss Army knives are closer to the mark for largely capable tools like this.</p>
<p>So I do think packaging things together with the plugin approach is the right one — different plugins can ship different sets of features: we could have a <code>kai-slim</code>, <code>kai-full</code>, one for project-level configs only, one for full OS management, one for Nix, one for Guix, etc.</p>
<p>That said, the plugin system is not being fully <em>utilized</em>: We are neither building out the Guix plugin system nor generalizing <code>std</code> to allow switching between Nix and Guix seamlessly.</p>
<p>I think this will be essential to avoid overfitting ourselves to Nix-only backends. While tempting in the short term, an agnostic design will pay huge dividends over time.</p>
<p>I’m also realizing that Nix can do <em>lots</em> of things and is very flexible. As such, people use it in a thousand different ways, which is something some love and others hate. This directly conflicts with the “one canonical way to do things” approach, which makes it far easier to create standards and form community (which, case in point, Nix really struggles with!).</p>
<p>This is an early canary — and one that we’ll have to pay close attention to mitigating in the future.</p>
<p>Alan Kay’s pithy phrase remains evergreen in my head such that I’m tempted to make it the philosophical motto of the project:</p>
<blockquote>
<p>Simple things should be simple, complex things should be possible.</p>
<p>— <a href="https://www.quora.com/What-is-the-story-behind-Alan-Kay-s-adage-Simple-things-should-be-simple-complex-things-should-be-possible">Alan Kay</a></p>
</blockquote>
<p>Achieving both, though, ain’t easy!</p>
<p>But what great things worth doing ever were?</p>
<p>Onward!</p>
]]></content:encoded></item>
<item><title>Kai Devlog #5: Conceptual Integrity and Being a Better Programmer</title><link>https://blu.cx/posts/blog/2026-09-02-kai-devlog-5-conceptual-integrity-better-programmer/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-09-02-kai-devlog-5-conceptual-integrity-better-programmer/</guid><pubDate>Wed, 02 Sep 2026 12:00:00 +0000</pubDate><description>Building Kai for the long term through conceptual integrity, better tools, deliberate practice, and dogfooding.</description><content:encoded><![CDATA[<blockquote>
<p>In the beginning all you want are results, in the end all you want is control.</p>
<p><a href="https://www.youtube.com/@eskilsteenberg">Eskil Steenberg</a>, <a href="https://www.youtube.com/watch?v=443UNeGrFoM">How I Program in C</a></p>
</blockquote>
<!-- Keep adjacent quotations as separate blockquotes. -->
<blockquote>
<p>Life is long enough, and it has been given in sufficiently generous measure to allow the accomplishment of the very greatest things if the whole of it is well invested.</p>
<p>Seneca, <a href="https://standardebooks.org/ebooks/seneca/dialogues/aubrey-stewart"><em>On the Shortness of Life</em></a></p>
</blockquote>
<!-- Keep adjacent quotations as separate blockquotes. -->
<blockquote>
<p>Most European cathedrals show differences in plan or architectural style between parts built in different generations by different builders. The later builders were tempted to “improve” upon the designs of the earlier ones, to reflect both changes in fashion and differences in individual taste. So the peaceful Norman transept abuts and contradicts the soaring Gothic nave, and the result proclaims the pridefulness of the builders as much as the glory of God.
Against these, the architectural unity of Reims stands in glorious contrast. The joy that stirs the beholder comes as much from the integrity of the design as from any particular excellences. As the guidebook tells, this integrity was achieved by the self-abnegation of eight generations of builders, each of whom sacrificed some of his ideas so that the whole might be of pure design.
Even though they have not taken centuries to build, most programming systems reflect conceptual disunity far worse than that of cathedrals […] I will contend that conceptual integrity is <em>the</em> most important consideration in system design. It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas.</p>
<p>Fred Brooks, <a href="https://en.wikipedia.org/wiki/The_Mythical_Man-Month"><em>The Mythical Man-Month</em></a></p>
</blockquote>
<p><img src="/images/blog/kai-devlog-5/reims-cathedral.jpg" alt="Reims Cathedral" /></p>
<p>This update might be a bit unusual, as I want to write an update but also some a little more personal about my experience of programming.</p>
<p>There are two things that are true of Kai that were never true of a programming project for me before:</p>
<ol>
<li>I’m building something from scratch as its progenitor, that</li>
<li>Is completely open source, that</li>
<li>I want to last a long time</li>
</ol>
<p>I’ve taken serious aim at all but (3) before, but the longer I live the more Karl Popper’s maxim of working on long-term problems speaks to me:</p>
<blockquote>
<p>I think that there is only one way to science - or to philosophy, for that matter: to meet a problem, to see its beauty and fall in love with it; to get married to it and to live with it happily, till death do ye part - unless you should meet another and even more fascinating problem or unless, indeed, you should obtain a solution. But even if you do obtain a solution, you may then discover, to your delight, the existence of a whole family of enchanting, though perhaps difficult, problem children, for whose welfare you may work, with a purpose, to the end of your days.</p>
<p>Karl Popper, <a href="https://www.routledge.com/Realism-and-the-Aim-of-Science-From-the-Postscript-to-The-Logic-of-Scientific-Discovery/BartleyIII-Popper/p/book/9780415084000">Realism and the Aim of Science</a></p>
</blockquote>
<p>By taking Kai <em>seriously</em>, by striving to make something that provides as close to a <em>strictly better</em> experience in using computers (to the extent achievable, there’s <a href="https://www.cgl.ucsf.edu/Outreach/pc204/NoSilverBullet.html"><em>No Silver Bullet</em></a>), I am forced to think harder than I ever have about <em>what</em> I’m building and <em>why</em>, the alignment between my highest-level end-goals and day to day tasks, how to make incremental progress every day which is aligned with these goals, how to maintain motivation, etc.</p>
<p>The genuine attempt to build something great is a forcing function for practices which lead to mastery.</p>
<p>As such, I’ve been doing a lot of reading (and more writing than I’ve ever done before!).</p>
<p>Sometimes I make mistakes or take some shortcuts of course. I think it can be very helpful to make something badly at first and just <em>get it out there</em>, share it, and get feedback from other really smart people in the community.</p>
<p>Kai is already starting to get <em>fun</em>, <em>real</em>, and <em>useful</em>, so I’m working to ensure it stays that way for a long time.</p>
<p>There’s been two releases since the <a href="/posts/blog/2026-08-17-kai-devlog-4-environment-task-build-workflow">last update</a>: <a href="https://github.com/thebrandonlucas/kai/releases/tag/v0.0.5">προιεναι - 0.0.5</a> (means “to proceed”) and <a href="https://github.com/thebrandonlucas/kai/releases/tag/v0.0.6">ετυγχανε - 0.0.6</a> (means “it happened”), and a lot of changes in them.</p>
<p>So many changes, where to start? Let’s just follow <a href="https://stephenking.com/works/nonfiction/on-writing-a-memoir-of-the-craft.html">Stephen King’s</a> advice for novels on this one, and just <em>skip the boring stuff</em>.</p>
<h2 id="external-features">External Features!<a href="#external-features" aria-label="Link to heading 'External Features!'" data-heading-content="External Features!" class="anchor"></a></h2>
<p>Let’s start with the fun stuff relevant to <em>you</em>, dear reader. Here are the new Kai commands added to the <code>StdPlugin</code> since the last update.</p>
<h3 id="source">Source<a href="#source" aria-label="Link to heading 'Source'" data-heading-content="Source" class="anchor"></a></h3>
<p>Not all things that a project needs are always in <code>nixpkgs</code>. Sometimes you need to declare an external link to a URL <code>tar</code> or something, as was the case for me with <code>aion</code> (also built in Roc), where I had to declare <code>bitcoin-qr</code> (a QR code NPM package I wrote for adding bitcoin payments to your site) URL as a source as this isn’t on <code>nixpkgs</code>. This is really useful and makes Kai far more flexible!</p>
<pre><code class="language-Kaifile">source bitcoin-qr {
  url: &quot;tarball+https://registry.npmjs.org/bitcoin-qr/-/bitcoin-qr-1.4.1.tgz&quot;
}

build aion {
  environment: cli
  source: &quot;src/aion.roc&quot;
  output: &quot;aion&quot;
}
</code></pre>
<h3 id="machine">Machine<a href="#machine" aria-label="Link to heading 'Machine'" data-heading-content="Machine" class="anchor"></a></h3>
<p>The <code>machine</code> block allows us to define full NixOS closures which we can then deploy or activate (for already-extant NixOS machines), which when combined with Kai can run services on startup or have particular packages installed.</p>
<p>Invoke with <code>kai machine &lt;name&gt;</code></p>
<pre><code class="language-Kaifile"># Build a typed NixOS deployment closure with `kai machine agent`.
environment server {
  packages: [&quot;curl&quot;]
}

machine &lt;name&gt; {
  environment: server
  system: &quot;x86_64-linux&quot;
  users: [&quot;&lt;name&gt;&quot;]
  services: [&quot;openssh&quot;]
}
</code></pre>
<h3 id="image">Image<a href="#image" aria-label="Link to heading 'Image'" data-heading-content="Image" class="anchor"></a></h3>
<p>That’s great, but what if we don’t already have a machine and want to create a <em>bootable</em> NixOS image? You can do that too via the <code>image</code> command! Which builds a generic QCOW2 image from a <code>machine</code> definition. For example, <code>kai image agent</code> turns the <code>agent</code> machine in your <code>Kaifile</code> into a portable image that a cloud provider can import.</p>
<p>I was able to test and verify this works on Digital Ocean via <code>aion</code> (below). The definition looks the same as machine, but it is invoked via <code>kai image &lt;name&gt;</code>:</p>
<pre><code class="language-Kaifile"># Build a generic QCOW2 image from the same machine definition.
environment server {
  packages: [&quot;curl&quot;]
}

machine &lt;name&gt; {
  environment: server
  system: &quot;x86_64-linux&quot;
  users: [&quot;&lt;name&gt;&quot;]
  services: [&quot;openssh&quot;]
}
</code></pre>
<h3 id="service">Service<a href="#service" aria-label="Link to heading 'Service'" data-heading-content="Service" class="anchor"></a></h3>
<p>Services are basically programs that run on startup and just continue running in the background. Caddy is a good example: You just want a web server that’s always on and routing requests to your sites. A crucial prerequisite if Kai is going to be useful in spinning up NixOS machines on desktop or in the cloud.</p>
<p>Example:</p>
<pre><code class="language-Kaifile"># A service turns an executable build artifact into a NixOS systemd service.
environment cow {
  packages: [&quot;cowsay&quot;]
}

build cow {
  environment: cow
  run: [&quot;sh&quot;, &quot;-c&quot;, &quot;cp \&quot;$(command -v cowsay)\&quot; cow&quot;]
  output: &quot;cow&quot;
}

service cow {
  artifact: &quot;cow&quot;
  secrets: []
  restart: on-failure
}
</code></pre>
<h3 id="secret">Secret<a href="#secret" aria-label="Link to heading 'Secret'" data-heading-content="Secret" class="anchor"></a></h3>
<p>A secret is basically an environment variable in either your shell, or machine, etc. This allows people to have the same machine but have behavior change based on the environment variables they set or have them set API keys or whatnot that allow them to use services. For example, I used <code>aion</code> to spin up a machine that ran <a href="https://pi.dev"><code>pi</code></a> coding agent connected to my <a href="https://ppq.ai">ppq.ai</a> API key. So in one command I could spin up a NixOS machine based on a <code>Kaifile</code> that had an agent running on it!</p>
<p>Example:</p>
<pre><code class="language-Kaifile">environment cow {
  packages: [&quot;cowsay&quot;]
}

# Build the &quot;cow&quot; program artifact
build cow {
  environment: cow
  run: [&quot;sh&quot;, &quot;-c&quot;, &quot;printf '#!%s\\ntest -s \&quot;$CREDENTIALS_DIRECTORY/message\&quot;\\nexec %s \&quot;secret loaded\&quot;\\n' \&quot;$(command -v sh)\&quot; \&quot;$(command -v cowsay)\&quot; &gt; cow &amp;&amp; chmod +x cow&quot;]
  output: &quot;cow&quot;
}

# Secret values stay outside the Kaifile. For this example, put
# &quot;classified moo&quot; in /run/kai/secrets/message on the machine.
secret message {
  provision: runtime
}

# Systemd passes the file to the service without exposing its contents.
service cow {
  artifact: &quot;cow&quot;
  secrets: [&quot;message&quot;]
  restart: on-failure
}
</code></pre>
<h2 id="internal-updates">Internal Updates<a href="#internal-updates" aria-label="Link to heading 'Internal Updates'" data-heading-content="Internal Updates" class="anchor"></a></h2>
<h2 id="plugin-overhaul-again">Plugin Overhaul (again)<a href="#plugin-overhaul-again" aria-label="Link to heading 'Plugin Overhaul (again)'" data-heading-content="Plugin Overhaul (again)" class="anchor"></a></h2>
<p>I once more redid the Plugin system, and it still isn’t quite where I want it to be. Getting this right is really important because it’s the gateway to others adding plugins for Kai to create their own plugins and features outside of <code>std</code>. I won’t yet go into the details because it isn’t <em>quite</em> there yet, but suffice to say that I’ve spent a lot of time making this much more self-documenting better abstracted. My goal is to get to the point where new users can “just dive in” making and experimenting with their own plugins.</p>
<h2 id="tidyroc">Tidy.roc<a href="#tidyroc" aria-label="Link to heading 'Tidy.roc'" data-heading-content="Tidy.roc" class="anchor"></a></h2>
<p><a href="https://matklad.github.io/">matklad</a> has a wonderful <a href="https://matklad.github.io/2025/12/06/mechanical-habits.html#Tidy-Script">article</a> on automating desired patterns of behavior for your codebase via a <a href="https://github.com/tigerbeetle/tigerbeetle/blob/main/src/tidy.zig">tidy</a> script they made at <a href="https://tigerbeetle.com/">TigerBeetle</a>. This is a really cool concept and one I wished I’d considered sooner. Basically, imagine you just wrote your own linter, not merely to enforce generic code style rules, but also to enforce <em>invariants</em> that you seek to ensure your custom rules are followed with every commit. This is essentially another additional layer of ensuring conceptual integrity for the project, for whatever invariants you care about.</p>
<p>I started simple in Kai, just adding a <code>Tidy.roc</code> which enforces:</p>
<ol>
<li>Every code module has a top-level explainer comment</li>
<li>Limit line length to 80 (some really long strings were getting hard to read)</li>
</ol>
<p>Tigerbeetle’s <code>tidy.zig</code> does so much more, of course, and some things I’d like to do, like ensuring large blobs don’t get committed (once they’re merged in that history, they’re hard to remove!).</p>
<p>But the real value is that I now have a script that I can edit to analyze my codebase on every commit and ensure nothing has violated a growing list specific list of invariants!</p>
<h2 id="guix-shell">Guix Shell<a href="#guix-shell" aria-label="Link to heading 'Guix Shell'" data-heading-content="Guix Shell" class="anchor"></a></h2>
<p><a href="https://en.wikipedia.org/wiki/The_Mythical_Man-Month"><em>The Mythical Man-Month</em></a> talks a lot about the importance of maintaining Conceptual Integrity in a program, and much of my research and thinking around Kai has evolved around this concept. Conceptual Integrity is the idea that your program should be <em>aligned</em> conceptually around what it’s supposed to be doing, and that this drifts as programs grow over time and abstractions get leakier.</p>
<p>One suggestion he has for maintaining conceptual integrity is having multiple implementations as a means to do ensure the design does what you want and is not too tied to your specific implementation. After all, one thing we’re trying to explore with Kai is the bounds of a <em>generic</em> determinate system – we need not and should not repeat Nix’s mistakes or tie ourselves too closely to their architectural mistakes.</p>
<p>So, just as a proof of concept for now that Kai will indeed work with two systems, I added just a single Guix shell command, which proved out that the <code>command</code>/<code>backend</code>/<code>implementation</code> architecture works for both Nix and Guix!</p>
<p>You can compile it with <code>nix run .#xkai -- build plugins/guix/GuixPlugin.roc</code>.</p>
<p>Then run <code>./kai shell guix</code>.</p>
<p>One day, I hope this flexibility will allow us to surgically replace problematic parts of Nix, in an incremental and backward compatible way, with our own implementation of a determinate system!</p>
<h2 id="fuzzer">Fuzzer<a href="#fuzzer" aria-label="Link to heading 'Fuzzer'" data-heading-content="Fuzzer" class="anchor"></a></h2>
<p>I’ve never fuzzed code before, and I’ll admit to ignorance on a lot of the theory and practice, especially with regards to practical benefit. But with <a href="https://github.com/lukewilliamboswell">Luke Boswell’s</a> new <a href="https://github.com/lukewilliamboswell/roc-fuzz">Roc fuzzer library</a>, and given that it was just a small bit of code surface to run for my parser (which fuzzers are apparently pretty good for), I decided to give it a try. Right now it’s only implemented for a portion of the parsing code, but it’s nice to know I have a way of easily and automatically testing thousands of inputs to the parser!</p>
<h2 id="dogfooding-kai">Dogfooding Kai<a href="#dogfooding-kai" aria-label="Link to heading 'Dogfooding Kai'" data-heading-content="Dogfooding Kai" class="anchor"></a></h2>
<p>So far I’ve created two in-progress greenfield projects with Kai with the robot friend. This has proven a lot of the use case for Kai. But I’ll also be adding it to extant projects like <a href="https://lyceum.quest/">lyceum.quest</a>, <a href="https://conllu.lyceum.quest/">conllu.lyceum.quest</a>, and this personal website, to prove that Kai can be used both for hosting and deployments, as well as developer environments and builds. Essentially, Kai should be able eventually to cover most common use cases for things that happen <em>around</em> the code, so that you can spend more time doing what you want to do – focus on the code itself.</p>
<h3 id="aion">Aion<a href="#aion" aria-label="Link to heading 'Aion'" data-heading-content="Aion" class="anchor"></a></h3>
<p>I built a <em>very</em> vibe coded (do not use yet!) tool to dogfood Kai with that I wanted for myself, a way for me to spin up NixOS servers on <a href="https://www.digitalocean.com/">DigitalOcean</a> at-will which spin up machines according to their Kai configuration. With this I was able, for example, to spin up an agent that “just works” and allows me to build things on that box with environment secret variables, services, packages, and just declarative computer setups in general, via just a single <code>Kaifile</code>!</p>
<p>This feels like a huge milestone, because one of the goals of Kai is to be able to augment/replace the traditional interface of using NixOS. If you can easily spin up simple to read declarative, version-controllable operating systems, it’s a massive game-changer for the headache of maintaining and remembering how you set up software environments. Your operating system setup becomes self-documenting, portable, shareable, trivially modifiable, trivially rollback-able, and now, because it’s not written in pure Nix, understandable!</p>
<p>The greater eventual ambition with this will be to allow people to use something like a <code>KaiOS</code> as a <em>desktop</em> as well, just like you can with NixOS. Then, maybe we can steal some of the Omarchy mindshare and prove to DHH that Nix-for-Desktop <em>can</em> be made easy ;)</p>
<p>There are a lot of knobs to think about before doing this in earnest, but we’ve already come a long way.</p>
<p>It’s completely open source and you can use it with your own Digital Ocean account (they’re one of the few that allow you to spin up custom NixOS servers).</p>
<h3 id="gump">Gump<a href="#gump" aria-label="Link to heading 'Gump'" data-heading-content="Gump" class="anchor"></a></h3>
<p>With <a href="https://github.blog/news-insights/company-news/addressing-githubs-recent-availability-issues/">all</a> <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability/">the</a> <a href="https://github.blog/news-insights/company-news/github-availability-report-july-2026/">recent</a> <a href="https://blog.incidenthub.cloud/github-actions-pages-outage-aug-6-2026">problems</a> <a href="https://github.blog/news-insights/company-news/the-august-17-outage-and-the-work-ahead">with</a> <a href="https://www.theverge.com/news/757461/microsoft-github-thomas-dohmke-resignation-coreai-team-transition">Github</a> <a href="https://mitchellh.com/writing/ghostty-leaving-github">lately</a>, I figured I’d try my hand at a (again, <em>very vibecoded</em>) version-control system with a few twists.</p>
<p>I’m calling it <a href="https://github.com/thebrandonlucas/gump">Gump</a> (short for <code>git dump</code>!). The goals are:</p>
<ol>
<li>Self-hostable alternative to Github.</li>
<li>Allows for portable <em>projects</em>, not just portable <em>code</em>. i.e., issues, pull requests, wiki updates, etc. should all be concepts built into and portable and them selves version-controlled, like <a href="https://fossil-scm.org/">Fossil</a> but for decentralized work like <code>git</code>.</li>
<li>Treats <code>jj</code> concepts as first-class as opposed to <code>git</code>. Like Nix, most people who’ve spent the time to figure out <code>jj</code> seem to far prefer it as a major innovation over <code>git</code>. Plus, unlike Nix but very much like Kai, <code>jj</code> is completely backward compatible with <code>git</code> and your teammates don’t even need to know you’re using it.</li>
<li>Easy to use CLI, usable by agents with configurable permissions.</li>
</ol>
<p>The main goal, of course, is simply to work on Kai. It’s going to be extremely opinionated and bespoke to my wants, if I keep working on it at all. It’s going to do weird stuff like probably only use public key encryption for auth (the fact that most websites still do “check your inbox for an email code” is insane and backward). Or take <a href="https://matklad.github.io/">matklad’s</a> advice of <a href="https://matklad.github.io/2023/06/18/GitHub-merge-queue.html">queueing PR merges by default</a> instead of waiting for the merge button to be green, then reviewing, then clicking merge.</p>
<p>Who knows? Maybe this will even lead to building a <code>jj</code>-based generic <code>magit</code> style editing experience too. There’s so much to improve upon.</p>
<p>There are a lot of <a href="https://www.scattered-thoughts.net/writing/pain-we-forgot">problems we forgot</a> because we were all just using <code>git</code> and Github for so long that we forgot to question many basic assumptions.</p>
<p>There are of course a whole host of things Github does really well that would be extremely difficult to replicate, but it’s also been really bad lately and the coding world is waking up to the idea that maybe it was not-so-great idea to make all of open source code in the world dependent on a single company.</p>
<p>I don’t want to distract too much from Kai though, and if I actually start using/depending on this I’ll need to throw the slop code away and rebuild something proper from scratch, but Kai development comes first and foremost and I need to stay focused on the bigger goal, but still, this is a fun idea to pursue.</p>
<h2 id="bootstrapping-and-playing-with-kai">Bootstrapping and Playing with Kai<a href="#bootstrapping-and-playing-with-kai" aria-label="Link to heading 'Bootstrapping and Playing with Kai'" data-heading-content="Bootstrapping and Playing with Kai" class="anchor"></a></h2>
<p>You can try Kai without installing it by running <code>nix run github:thebrandonlucas/kai -- version</code>. That is how Aion and Gump keep their custom Kai plugins reproducible without requiring a global installation, while this package is not yet on <code>nixpkgs</code>.</p>
<h2 id="notes-and-learnings">Notes and Learnings<a href="#notes-and-learnings" aria-label="Link to heading 'Notes and Learnings'" data-heading-content="Notes and Learnings" class="anchor"></a></h2>
<h3 id="making-code-smaller">Making Code Smaller<a href="#making-code-smaller" aria-label="Link to heading 'Making Code Smaller'" data-heading-content="Making Code Smaller" class="anchor"></a></h3>
<p><a href="https://www.youtube.com/watch?v=JjDsP5n2kSM">Jonathan Blow</a> has a talk where he has an interesting section about how “deleting code is always a good thing”. I’m not sure I agree completely, but a common theme I’m noticing in all these investigations is that conceptual integrity, and an understanding of the entire system is paramount to maintaining good code.</p>
<p>One aspect of understanding things well is having less things to understand. Deleting code while keeping it readable is one way to do this. This also means deleting anything that feels superfluous, inessential, etc. It means careful abstraction when helpful, and no abstraction when not.</p>
<p>Lines of code I think can generally be a helpful metric for whether code is getting smaller but it’s not the full story. If your code is very DRY but opaque, the true workings of it buried under layers and layers of indirection, what was the point?</p>
<h2 id="testing-must-be-done-up-front">Testing Must Be Done Up Front<a href="#testing-must-be-done-up-front" aria-label="Link to heading 'Testing Must Be Done Up Front'" data-heading-content="Testing Must Be Done Up Front" class="anchor"></a></h2>
<p>SQLite creator <a href="https://www.sqlite.org/crew.html">Richard Hipp</a> says in his excellent <a href="https://www.youtube.com/watch?v=V_qzqY1bb7I">Software Should Work</a> talk that the secrets to making SQLite <em>probably</em> the most-used piece of software in the world are:</p>
<ol>
<li>Extreme portability. As Hipp says, “it’s just a file you can email to your friends!”</li>
<li>It just works. He primarily attributes this to an <em>extreme</em> approach to testing. In fact, there is far more test code in SQLite than code being tested!</li>
</ol>
<p>I will admit with some shame that I do not have any test yet other than a single integration test that doesn’t block CI. At first, I just blindly told the bot to add tests “in the style of <a href="https://matklad.github.io/2021/05/31/how-to-test.html">‘How to Test’</a>” which was silly and lazy and didn’t solve my essential problem: Helping to know <em>what</em> my code did (free documentation) and more importantly give me <em>confidence</em> that it worked.</p>
<p>The main reason I don’t have much testing is that I’m heavily debating and considering my strategy. <em>How to Test</em> says that tests should be cheap, operate at the <a href="https://www.destroyallsoftware.com/talks/boundaries">boundaries</a> of your interface, operate only on data (i.e. only do CPU work, no I/O), and therefore be <em>fast</em>, that they should be very easy to maintain (or else you won’t want to do them), and more.</p>
<p>That’s a lot of things to think about! Roc thankfully makes much of that easy by having a <code>!</code> marker to declare effect<em>ful</em> and effect<em>free</em> (pure) code, and so if you just trivially separate the code which performs effects from the code which just operates on pure data (same input always results in same output). This means if you just test the pure functions your tests will run fast. But I was finding that my tests were adding a lot of surface area, that I didn’t understand how they worked or what they were doing, and that they therefore didn’t give me much confidence and in fact hampered my understanding of the code.</p>
<p>So, in the meantime, I wrote a <code>zig build kaifiles</code> command which just runs all the Kaifiles in the <code>examples</code> folder end-to-end. I figured that since the examples demonstrate and run through basically all syntax using the simple <code>cowsay</code> program, having those work and actually seeing their output gives me a lot of confidence that at least the happy path is consistently working. I didn’t block <code>zig build ci</code> with these since they’re neither pure nor fast, which may be a mistake, and that has bitten me (while writing this, I realized the command failed because I hadn’t run it in awhile!), so I may change that despite it being effectful.</p>
<p>But a major next step for me is figuring out and working hard on the right fast test boundary so I can start building more confidence moving forward that Kai doesn’t have many bugs.</p>
<p>I should probably also add an invariant to <code>tidy.roc</code> to verify that every block of Kai’s <code>StdPlugin</code> <code>Kaifiles</code> are used. As we add new blocks, the examples won’t include them all automatically without some rule.</p>
<h2 id="dependency-cultures">Dependency Cultures<a href="#dependency-cultures" aria-label="Link to heading 'Dependency Cultures'" data-heading-content="Dependency Cultures" class="anchor"></a></h2>
<p><a href="https://github.com/rtfeldman">Richard Feldman’s</a> <a href="https://www.youtube.com/watch?v=E82ly38YEEQ"><em>Dependency Cultures</em></a> talk at <a href="https://softwareshould.work/">Software Should Work</a> really took me out of my Javascript bubble and got me asking deeper questions about what tools I should be using.</p>
<p>There are real downsides to the Tigerbeetle approach of manually maintaining <em>all</em> of your own tools and needing to understand <em>everything</em> about your program. I don’t have a great grasp of assembly or the theory of electricity and yet I can still write code to make computers do things. Tools and dependencies do exist for a reason.</p>
<p><em>But</em>.</p>
<p>There are two edges to this sword. If you take a short enough view, never having sought or gained a deeper understanding of how the things you use work, you’ll always be spending your time learning things which don’t last and are always changing.</p>
<p>The more time I’ve spent in the room with the creators of great things, they tend to have a deep understanding of the foundational structures of the tools they use and are building on, and as a result are much more versatile and capable. As new technologies slowly die in favor of newer technologies, they are less and less affected, because the deeper down into the foundations you go the less the knowledge you gained becomes irrelevant.</p>
<p>There’s a line between the practical utility of leveraging new technologies and the foundational stability of learning the old ones, and I think in tech by our very nature we’re drawn to the former more than we generally should be.</p>
<p>Often, if you think about the software you love the most that’s lasted the longest, it’s usually by people who spend a lot of time learning <em>deeply</em>.</p>
<h2 id="the-tools-youre-using-are-enough">The Tools You’re Using Are Enough<a href="#the-tools-youre-using-are-enough" aria-label="Link to heading 'The Tools You’re Using Are Enough'" data-heading-content="The Tools You’re Using Are Enough" class="anchor"></a></h2>
<p>Relatedly, one of matklad’s articles linked to a <a href="https://twitter.com/id_aa_carmack/status/989951283900514304">John Carmack</a> discussion about “just using the tools you already have”. Since this is being built in a language for which the compiler is being rebuilt, I run into a lot of bugs. So, because:</p>
<p>a) I’m excited to help in some small way contribute to this new language.
b) I want to use more reliable, fast tools (who doesn’t?) and Roc’s model fits my concerns well.
c) There’s less mental overhead and more conceptual integrity in using one tool for me.
d) It’s fun and I get to do a bunch of mini-projects in the language.
e) Not all code is equally important. The code that checks your code is less important than the code it’s checking, and I don’t need to anguish over every tiny detail.</p>
<h2 id="trunk-based-development">Trunk-based Development<a href="#trunk-based-development" aria-label="Link to heading 'Trunk-based Development'" data-heading-content="Trunk-based Development" class="anchor"></a></h2>
<p><a href="https://web.archive.org/web/20220517144332/https://ourmachinery.com/post/step-by-step-programming-incrementally/">This article</a> struck an idea in my mind that I can’t get out: every commit should add value to the codebase. This is <em>hard</em> to think about how to do properly, since features rarely lend themselves to single-commit completion! But there are tricks to do so for bigger features as noted in the article, and even in reviewing agent constructed code the idea of constantly merging things to <code>master</code> has been helping tremendously in avoiding not only cumbersome merge conflicts but just keeping the full program in my head, which is becoming more important as features keep getting added. The rapid development speed increases in adding more features provided by agents means that a lot of discipline is required to monitor them, reign them in, maintain control over what the code should actually be doing, and simplifying it. Agents have a huge bias in favor of bloat, and I’ve actually found it pretty hard to have them keep things <em>simple</em>, <em>small</em>, and <em>easy to understand</em>. Trunk-based development helps by reducing the number of concepts I’m having to keep up with at once.</p>
<h2 id="thoughts-on-commits">Thoughts on Commits<a href="#thoughts-on-commits" aria-label="Link to heading 'Thoughts on Commits'" data-heading-content="Thoughts on Commits" class="anchor"></a></h2>
<p>Different projects have different philosophies on what a commit is, it seems, which has consequences for how your repo history is written and what history you allow to be merged. Mitchell Hashimoto, for example, thinks 1 PR equals 1 commit. Therefore a commit is the mergeable, PRable unit of change. So, the process would be something like: work on your feature locally, adding WIP commits or whatever as you go, then, amending, rebasing, fixup’ing, etc. when you’re done into a single commit that you push and merge.</p>
<p><a href="https://mitchellh.com/writing/contributing-to-complex-projects">According</a> to Hashimoto, that final mergeable product should be +50/-50 ideally (easy to review, small incremental change) but no greater than +500/-500 (beyond that it starts getting difficult for a human to review).</p>
<p>Matklad has a looser <a href="https://matklad.github.io/2023/12/31/git-things.html">definition</a>, in that commit history can show the <em>progress</em> of how the final idea came to be. So a commit doesn’t need to necessarily be an isolated independent unit of work, the merge commit can be that, but it should tell a cohesive, easy to understand <em>story</em> about how you got there (and therefore you should still rebase/amend/fixup your WIP or exploration commits into something comprehensible by later review). He finds this useful as you can more easily trace the <em>why</em> and <em>how</em> for features and bugs when doing <code>git blame</code>, and if you have the merge commits, you already have the “final commit” at which that feature got merged.</p>
<p>I’ve been tending to favor Matklad’s approach on this, and trying to balance/reconcile that with <a href="https://web.archive.org/web/20220517144332/https://ourmachinery.com/post/step-by-step-programming-incrementally/">this article</a>’s trunk-based development approach where each commit can still provide independent value.</p>
<p>Also, I just started using <a href="https://jj-vcs.github.io/jj/latest/"><code>jj</code></a>, the new darling VCS of most who take the time to use it, it seems, and that also requires a huge rethinking of how to do all these things.</p>
<p>Regardless, I’m trying to pay much more attention now to the utility of the history.</p>
<h2 id="ai-use">AI Use<a href="#ai-use" aria-label="Link to heading 'AI Use'" data-heading-content="AI Use" class="anchor"></a></h2>
<h3 id="matklads-loop-closing-strategy">Matklad’s “Loop Closing” Strategy<a href="#matklads-loop-closing-strategy" aria-label="Link to heading 'Matklad’s “Loop Closing” Strategy'" data-heading-content="Matklad’s “Loop Closing” Strategy" class="anchor"></a></h3>
<p>Vibe coding has been both great and terrible for me. It’s difficult to find the right middle ground between having the robot do everything for you and doing everything yourself old-school. Obviously typing every <code>for</code> loop yourself should no longer be necessary. But I do think I should at least read every loop, and if I can’t understand the code (which I frequently can’t) that’s a red flag for me. This is becoming increasingly important and increasingly my bar: if I can’t understand the code as I read through it, it’s not good enough. This means that I either need to add comments (I plan on adding <em>many</em> more comments as part of cleaning up this codebase) or name variables something better, or either abstract more or even sometimes abstract less (abstracting can <a href="https://matklad.github.io/2020/08/15/concrete-abstraction.html">make internals unnecessarily opaque</a>).</p>
<p>But one strategy I’ve been trying that’s been working as a nice middle ground (though I still need a bit more discipline) is:</p>
<ol>
<li>Create a <code>.gitignore</code>’d directory called <code>plans</code>.</li>
<li>Inside <code>plans</code>, create a folder for <code>my-feature-plan</code> or <code>my-bug-fix</code>.</li>
<li>Inside <code>my-feature-plan</code>, create a <code>spec.md</code>.</li>
<li>Describe, in as much detail with as many real and specific examples you can of how you’d like the thing to work, the feature, fix, or refactor.</li>
<li>Tell the AI to create a <code>plan.md</code> which follows repository rules and splits the work into small, reviewable, independently value-adding chunks (Using <a href="https://mitchellh.com/writing/contributing-to-complex-projects">Mitchell Hashimoto’s</a> rule of +50/-50, no more than +500/-500 – a human (me!) has to read all the code after all!). Tell it to give examples of what it intends to do.</li>
<li>Review and refine that plan (I always find I underspecify and the AI didn’t get what I meant exactly).</li>
<li>Tell it “Do first chunk”, review that chunk, merge into <code>master</code>. Sometimes if I’ve got multiple things going I’ll be lazy and just have it do all the chunks at once but even with all the invariants, CI checks on every commit, and commit rules, I find this still bites me and I have to end up having it redo things. So I’m becoming more convinced that smaller chunks, each reviewed by a human before moving on, is still the way.</li>
</ol>
<h2 id="focus-motivation-mistakes">Focus, Motivation, Mistakes<a href="#focus-motivation-mistakes" aria-label="Link to heading 'Focus, Motivation, Mistakes'" data-heading-content="Focus, Motivation, Mistakes" class="anchor"></a></h2>
<p>Some final thoughts.</p>
<h3 id="focus-focus-focus">Focus Focus Focus!<a href="#focus-focus-focus" aria-label="Link to heading 'Focus Focus Focus!'" data-heading-content="Focus Focus Focus!" class="anchor"></a></h3>
<p>Focus is extremely important yet really hard. I have to play all sorts of tricks on myself because the internet as it stands is adversarial by default: that is, more helpful than harmful to my overall happiness and effectiveness unless I actively take steps to make it a positive. I shouldn’t have to <em>work</em> to make a computer a net positive for me (which is a big part of what I want Kai to help with). This universal constant in my life should be a net positive by default, or at least trivial to make it one.</p>
<h3 id="maintaining-motivation">Maintaining Motivation<a href="#maintaining-motivation" aria-label="Link to heading 'Maintaining Motivation'" data-heading-content="Maintaining Motivation" class="anchor"></a></h3>
<p>Read <a href="https://www.scattered-thoughts.net/writing/setting-goals">this</a> fantastic blog on programming and just a lot of good general life advice.</p>
<p>Sometimes, I’ll find that reading and re-reading my code with the purpose of refactoring just doesn’t sound fun. Or thinking about testing strategy or whatever. And then I don’t want to do it. And then I find myself trying to reach for Yet Another Side Project instead of just making the software I want most (Kai) great.</p>
<p>I’m trying to employ several tricks to get around this, such as:</p>
<ul>
<li>Work in smaller and smaller chunks</li>
<li>Walk away from the computer when tired and, if I still want to feel like I’m making progress, read a book on programming that would help (e.g. <em>The Mythical Man-Month</em> or <em>The Cathedral and the Bazaar</em>) read a blog article, or watch a talk.</li>
<li>Sharing! Writing blog posts takes a lot of effort, even relatively simple or sloppy, plain-English ones like this one. But even just slight positive feedback from real humans who would like to use this is uber-motivating. And, it helps me think through things at a high-level again and remind myself what I’m really trying to do and forces me to understand the system I built better.</li>
<li>Frequent releases (Mechanical Habits).</li>
<li>Working on automating <em>processes</em>. E.g. creating a release script which makes releasing as simple as <code>zig build release ...</code>.</li>
<li>Releasing frequently. Doing “the whole vertical” from top to bottom for every small change or small set of changes, that is, writing code, running it, testing it, releasing it, and writing about it, all frequently, makes the whole process run smoother and be less stressful. If you deploy every small change to prod every day, prod deploys accumulate less cruft, become muscle memory, and each one has a lower surface area for problems. <a href="http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/">Many eyes make all bugs shallow</a> much faster in that case. Plus, it’s more exciting for both you and people who like your project!</li>
<li>Dogfood your project into another project. I can’t tell you how exciting it was to just say to an agent: “Build thing X. For anything that isn’t code itself, you are only allowed to use <code>kai</code>. No bash scripts, no <code>python</code> helpers, no <code>nix</code>. Just <code>kai</code>” and have it work! It’s a bit clunky, but in Aion and Gump, you really can do everything you need to do, from develop to deploy, using Kai!</li>
</ul>
<p>Some other great sources I’m borrowing from are:</p>
<ul>
<li><a href="https://matklad.github.io/2021/05/31/how-to-test.html">matklad’s How to Test</a></li>
<li><a href="https://mitchellh.com/">Mitchell Hashimoto</a></li>
</ul>
<h3 id="mistakes">Mistakes<a href="#mistakes" aria-label="Link to heading 'Mistakes'" data-heading-content="Mistakes" class="anchor"></a></h3>
<p>I’ve already found myself making a lot of mistakes with Kai:</p>
<ul>
<li>Overreliance on AI. It takes a lot of discipline to not just keep building and instead fixing what you already have and trying to make it great. That said, if I spend a whole year working on Kai in isolation, just to rip it apart and throw it away in favor of some new implementation when someone points out a completely better architecture, I won’t feel bad about not trying to achieve <em>perfection</em> on the first go before even sharing it.</li>
<li>Not reading code enough (<a href="https://www.teamten.com/lawrence/programming/dont-write-bugs.html">Don’t write bugs!</a>). And, as a result, having some of the internals of things like the parser or assembler remain a bit fuzzy in my brain.</li>
<li>Getting distracted. I think it was an overall positive to vibe code <a href="https://github.com/thebrandonlucas/aion">Aion</a> and <a href="https://github.com/thebrandonlucas/gump">Gump</a> (and vibing was <em>absolutely</em> the correct decision in their case – anything more would have meant spending an unjustifiable amount of time on two projects I may might 1. throw away or 2. distract from Kai too much).</li>
</ul>
]]></content:encoded></item>
<item><title>Kai Devlog #4: Environment, Task, Build, Workflow</title><link>https://blu.cx/posts/blog/2026-08-17-kai-devlog-4-environment-task-build-workflow/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-08-17-kai-devlog-4-environment-task-build-workflow/</guid><pubDate>Mon, 17 Aug 2026 12:00:00 +0000</pubDate><description>Expanding Kai with reusable environments, runnable tasks, composable workflows, portable builds, and overlays.</description><content:encoded><![CDATA[<p>This release of <code>kai</code> is all about new features!</p>
<p>A lot of upfront work went into ensuring modular design, especially so that new features could be added relatively cleanly under a contract that I liked. The abstractions aren’t perfect yet by any means, and it may still be a little confusing for a newcomer writing their own plugin. We’ll work more on that in some future release.</p>
<p>But I did feel it was good enough to finally build more of the features that would put <code>kai</code> to good use. That said, here are the features currently available in <code>kai</code>. These examples are all available in the latest code release:</p>
<p>As before, you could enter shells with certain programs:</p>
<pre><code class="language-Kaifile"># The simplest possible Kaifile.
#
# Run `kai shell` to enter this environment on Linux or macOS.
shell {
  packages: [&quot;cowsay&quot;, &quot;fortune&quot;]
}
</code></pre>
<p>But now we have a more generic <code>environment</code> builtin which can be used to describe the same package set across the other new keys. For example: you can now create <code>task</code>s and run them via <code>kai run &lt;task name&gt;</code></p>
<pre><code class="language-Kaifile"># Run `kai run moo` to make the cow say a pithy quote!
environment cow {
  packages: [&quot;cowsay&quot;, &quot;fortune&quot;]
}

task moo {
  environment: &quot;cow&quot;
  run: [&quot;sh&quot;, &quot;-c&quot;, &quot;fortune | cowsay&quot;]
}
</code></pre>
<p>Try running this with <code>kai run moo</code>!</p>
<p>You can also run <code>task</code>s together as part of a <code>workflow</code> (think CI). Run the below via <code>kai run cowvolution</code>:</p>
<pre><code class="language-Kaifile"># Workflows compose tasks and builds into one repeatable command.
# Run this one with `kai workflow cowvolution`.
environment cow {
  packages: [&quot;cowsay&quot;]
}

task normalcow {
  environment: &quot;cow&quot;
  run: [&quot;cowsay&quot;, &quot;moo&quot;]
}

task antiquitycow {
  environment: &quot;cow&quot;
  run: [&quot;cowsay&quot;, &quot;Non nobis solum nati sumus.&quot;]
}

task victoriancow {
  environment: &quot;cow&quot;
  run: [&quot;cowsay&quot;, &quot;doth mother know you weareth her drapes?&quot;]
}

workflow cowvolution {
  steps: [
    &quot;run normalcow&quot;,
    &quot;run antiquitycow&quot;,
    &quot;run victoriancow&quot;,
  ]
}
</code></pre>
<p>What if you want to package things together into a portably <code>build</code>-able derivation? You can! Here’s a little shell script which will combine <code>cowsay</code> and <code>fortune</code> to have the <code>fortune</code> piped into whatever <code>cowsay</code> has to say. Run via <code>kai build &lt;output-name&gt;</code> (in this case <code>wisecow</code>)</p>
<pre><code class="language-Kaifile"># You can build artifacts using the build command!
#
# This one combines `cowsay` and `fortune` to give you a
# `wisecow` binary.

environment cow {
  packages: [&quot;cowsay&quot;, &quot;fortune&quot;]
}

build wisecow {
  environment: cow
  run: [
    &quot;sh&quot;,
    &quot;-c&quot;,
    &quot;printf '#!%s\\n%s | %s\\n' \&quot;$(command -v sh)\&quot; \&quot;$(command -v fortune)\&quot; \&quot;$(command -v cowsay)\&quot; &gt; wisecow &amp;&amp; chmod +x wisecow&quot;
  ]
  output: &quot;wisecow&quot;
}
</code></pre>
<p>Finally, we have <code>overlay</code>s, to help people run things that may not be in <code>nixpkgs</code> (<code>kai</code> itself needs this ability)</p>
<pre><code class="language-Kaifile"># Overlays add packages that are not provided directly by nixpkgs.
shell {
  packages: [&quot;rocpkgs.nightly&quot;]
  overlays: [&quot;github:thebrandonlucas/roc-overlay&quot;]
}
</code></pre>
<p>That’s it for now! Stay tuned!</p>
]]></content:encoded></item>
<item><title>Kai Devlog #3: Custom DSL &amp; Plugin Modules</title><link>https://blu.cx/posts/blog/2026-08-11-kai-devlog-3-custom-dsl-plugin-modules/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-08-11-kai-devlog-3-custom-dsl-plugin-modules/</guid><pubDate>Tue, 11 Aug 2026 12:00:00 +0000</pubDate><description>Replacing Kai's embedded Roc config with a custom DSL and making plugin modules simpler, reusable, and validated.</description><content:encoded><![CDATA[<p>Something that is bred into <code>kai</code>‘s bones is the idea that if something is hard to use, it won’t be used unless it <em>must</em> be. In the last devlog we made <code>kai</code> modular in that you could build your own plugins, but there were several problems:</p>
<ul>
<li>You had to write custom parsing logic (terrible!)</li>
<li>They weren’t strict definitionally and the concept of a <code>Backend</code> was unclear and vague (does that just mean <code>nix</code>? What if there is more than one requirement?)</li>
<li>They were unvalidated. You could “do whatever you wanted” and received no help from <code>xkai</code> when you did something illogical</li>
<li>Plugin modules themselves were not modular/reusable/easy to make</li>
</ul>
<p>This release now updates that so the plugins should be much simpler to write, parsing is handled for you, a few validations are done by <code>xkai</code> to help ensure you built it right, and <code>Backend</code> was given a much stronger definition that represented the system much better.</p>
<p>Additionally, I made a custom DSL! Even though the previous <em>embedded</em> <code>roc</code> DSL was pretty thin, I realized I could make things much simpler if I made a custom one. The primary motivation to do it in an embedded language was to inherit things like conditional logic, but I really only needed it for switching between systems (e.g. <code>if system == linux then install ...</code>) and that alone did not justify the extra complexity. Plus, it would require every user to have both a determinate system installed like <code>nix</code> <em>and</em> the <code>roc</code> compiler. Instead, we remove that extra 30MB or so and the final <code>kai</code> binary comes out to about 600KB! This does come at the cost of a lot of gutsy config parsing and validation, which is annoying, but I think the tradeoff is worth it.</p>
<p>But the best part is now, the simplest <code>config.kai</code> (which I’ll soon rename <code>Kaifile</code>) looks like this:</p>
<pre><code class="language-Kaifile">shell {
    pkgs: [&quot;cowsay&quot;]
}
</code></pre>
<p>With just this, you’ll be able to define reproducible dev environments! For changing behavior on different systems:</p>
<pre><code class="language-Kaifile">on linux {
    shell {
        pkgs: [&quot;cowsay&quot;]
    }
}

on macos {
    shell {
        pkgs: [&quot;pokemonsay&quot;]
    }
}
</code></pre>
<p>Much simpler and straightforward than a <code>flake.nix</code>, that’s for sure!</p>
<p>Onward!</p>
]]></content:encoded></item>
<item><title>Kai Devlog #2: Modularity</title><link>https://blu.cx/posts/blog/2026-08-04-kai-devlog-2-modularity/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-08-04-kai-devlog-2-modularity/</guid><pubDate>Tue, 04 Aug 2026 12:00:00 +0000</pubDate><description>Building Kai around replaceable commands, extensible plugins, and custom determinate backends.</description><content:encoded><![CDATA[<p>Read any classic book about software, and you’ll find that it’s supposed to be modular. Why? Because modular software that does something useful is software that will <em>last</em>, roughly in proportion to:</p>
<ul>
<li>Its usefulness</li>
<li>Its modularity</li>
<li>Its evangelistic efforts</li>
<li>Its ease of use</li>
</ul>
<p>I believe this has become orders of magnitude more important in the Age of AI, as <em>code</em> (which was never the most expensive piece of the software development lifecycle anyway) has become incredibly cheap to produce. I also believe, perhaps controversially, that the principles of software development as established by its progenitors are <em>even more relevant</em>, with perhaps some tweaks and modifications.</p>
<p>A recurring theme, whether it be in such classics as the <a href="https://shop.elsevier.com/books/the-unix-philosophy/gancarz/978-0-08-094819-5">Unix Philosophy</a> or <a href="https://www.informit.com/store/mythical-man-month-essays-on-software-engineering-anniversary-9780201835953">The Mythical Man-Month</a> or <a href="https://www.oreilly.com/library/view/the-cathedral/0596001088/">The Cathedral and the Bazaar</a> or modern works like <a href="https://press.stripe.com/working-in-public">Working in Public</a>, is that good software should be modular. Think of the success of <a href="https://www.lego.com/en-us/history/articles/lego-system-in-play">LEGO</a>. A wonderful toy that embodies the spirit of open source software. LEGO’s have outlived many a toy because their design allows them endless reconfigurability and modifiability. If grandpa builds a castle from LEGO’s, then dad can add a dragon, and son, having slain it, can finally give the freed princess’ tower a long-overdue remodeling. This means that the efforts of the tower have not been lost, from grandpa to father to son, that there is a deeply human <em>history</em> and <em>personalization</em> to that tower.</p>
<p>Code works the same way. Linux is the undisputed king of infrastructure OS, and will probably overcome Windows and Apple: as the latter continue to get worse, piling on more and more new bloatware on already inferior foundations, their software will become more and more intractable to fix while the former continues to get more featureful, more useable, more battle-tested.</p>
<p>Thus, a modular approach will allow the code to be more flexible, interoperable, changeable, robust, and last much longer.</p>
<h2 id="making-kai-modular">Making Kai Modular<a href="#making-kai-modular" aria-label="Link to heading 'Making Kai Modular'" data-heading-content="Making Kai Modular" class="anchor"></a></h2>
<p>The three concerns for <code>kai</code> modularity outlined in previous articles are:</p>
<ul>
<li>Allowing replacement of any standard command with custom logic</li>
<li>Allowing commands to be added since we can’t anticipate every use case</li>
<li>Allowing piecemeal replacement of underlying <code>nix</code> or <code>guix</code> pain points (such as the famously slow <code>nix</code> evaluator) with custom interoperable solutions</li>
</ul>
<p>I modeled the design after <a href="https://caddyserver.com/"><code>caddy</code></a>, the best web server technology I’ve ever used, which has great defaults, is fantastically easy to use and configure, and is extremely <a href="https://caddyserver.com/docs/architecture">modular</a>.</p>
<p>I created a separate binary, called <code>xkai</code> (analagous to <code>caddy</code>’s <code>xcaddy</code>) which creates custom <code>kai</code> binaries which execute the behavior specified in a custom <code>Plugin.roc</code> file. For example, the “core library” of <code>kai</code> is <code>StdPlugin</code> which looks like this:</p>
<pre><code class="language-roc">import kai.Plugin as PluginApi

# The standard plugin is pure. kai.roc imports its typed configuration
# constructor; only the generic executor performs the resulting actions.
# TODO: it's explicitly limited to nix for now. This might be long-term
# desirable behavior, but we may want to consider providing guix support
# out of the box as a fallback. Or keep it as a separate Guix plugin/binary.
StdPlugin := [].{
    ShellConfig : { pkgs : List(Str) }

    nix : PluginApi.Backend
    nix = PluginApi.Backend.{ name: &quot;nix&quot; }

  # Only support the nix backend for now
    backends = [nix]

  # We have one command, shell, which just creates a nix dev shell
  # with a flake and enters it.
    shell_command : PluginApi.Command
    shell_command = PluginApi.Command.{
        argv: [],
        backends,
        name: &quot;shell&quot;,
    }

  # Whereas `shell` is a portable `Command` variable, `nix_shell`
  # is the actual implementation that tells the `kai` binary resulting
  # from `xkai build` on this plugin which side-effects to perform.
  #
  # Separating these two concepts is important - if we supported, say,
  # `guix shell`, then we could have one command in kai which would run
  # `guix shell` or `nix shell` depending on which system the user wanted
  # to use. So it's very flexible!
    nix_shell : PluginApi.Implementation
    nix_shell = PluginApi.Implementation.{
        actions: [
            WriteConfigUtf8({ path: &quot;.kai/flake.nix&quot; }),
            Exec({
                args: [&quot;develop&quot;, &quot;path:.kai#default&quot;],
                command: nix.name,
            }),
        ],
        backend: nix,
        command: shell_command,
        requirement: Program(nix.name),
    }

    shell : ShellConfig -&gt; {
        implementation : PluginApi.Implementation,
        rendered_config : Str,
    }
    shell = |shell_config| {
        implementation: nix_shell,
        rendered_config: StdPlugin.render_nix(
            shell_config.pkgs,
        ),
    }

  # Render the actual flake, which will be written to a managed
  # directory called `.kai`
    render_nix : List(Str) -&gt; Str
    render_nix = |pkgs| {
        package_lines = pkgs.map(
            |pkg| &quot;              nixpkgs.\&quot;legacyPackages\&quot;.\&quot;x86_64-linux\&quot;.\&quot;${pkg}\&quot;&quot;,
        )
        lines = [
            &quot;{&quot;,
            &quot;  inputs.nixpkgs.url = \&quot;github:NixOS/nixpkgs/nixos-unstable\&quot;;&quot;,
            &quot;  outputs = { nixpkgs, ... }: {&quot;,
            &quot;    devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {&quot;,
            &quot;      packages = [&quot;,
        ].concat(package_lines).concat([
            &quot;      ];&quot;,
            &quot;    };&quot;,
            &quot;  };&quot;,
            &quot;}&quot;,
        ])
        Str.join_with(lines, &quot;\n&quot;)
    }
}

# Plugins are pure, so we can do cheap, no I/O tests!
expect {
    module_config = StdPlugin.shell({ pkgs: [&quot;hello&quot;] })
    module_config.implementation == StdPlugin.nix_shell and
        module_config.rendered_config.contains(&quot;.\&quot;hello\&quot;&quot;)
}
</code></pre>
<p>This means that plugin creators need only write this plugin file, run <code>xkai build</code> on it, and they have their own custom <code>kai</code> binary! Which is, of course, how I will also be developing <code>kai</code>.</p>
<p>Thinking through this design has been by far the most difficult aspect of this project so far, and required a lot of research up front. But I am profoundly satisfied with this architecture.</p>
<p>Although <code>kai</code> is still very much a prototype which needs much better testing, interface, and of course, feature work, this is beginning to feel like a very solid and extensible foundation which will allow not just me, but <em>anyone</em>, to easily contribute meaningfully to this in the future.</p>
<p>Onward!</p>
]]></content:encoded></item>
<item><title>Kai Devlog #1: Boundaries</title><link>https://blu.cx/posts/blog/2026-07-28-kai-devlog-1-boundaries/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-07-28-kai-devlog-1-boundaries/</guid><pubDate>Tue, 28 Jul 2026 12:00:00 +0000</pubDate><description>Finding the right boundary between Kai's simple configuration and Nix's power.</description><content:encoded><![CDATA[<p>I’ve released a tiny prototype of <a href="https://github.com/thebrandonlucas/kai">Kai</a>. It has one useful command: <code>kai shell</code>. It does one thing: reads the contents of a <code>kai.roc</code> file, creates a <code>.kai/flake.nix</code> in the current directory, then calls <code>nix</code> under the hood to put you into a shell with the programs defined in the flake.</p>
<p>A <em>lot</em> of work went into doing that very basic thing that is a near-zero improvement over <code>nix</code> itself, because so much went into the underlying design that I’m hoping will make Kai a <a href="https://www.ebay.com/itm/254467919121?_skw=zero+to+one&amp;epid=167621430&amp;itmmeta=01KYK8B5T2R1700BNT92NH7B4W&amp;hash=item3b3f784d11:g:weYAAOSwbKJglu9o&amp;itmprp=enc%3AAQALAAAA0GfYFPkwiKCW4ZNSs2u11xDO%2BTPzFgfgppO%2BGxK2WxXD0LFr--%2BcpcQnEL22eIxi0ffSConw%2FSboIzOyd8hE4smQE70cDmIK8G78BzAURQbg9eZcUC7ZAdsCaPYikvzMLwRoHkuNnEivxx6Og4KrJKzsRTdRybMhlykiTo%2FC5ecL7v6rwXk2dkkApAy4lKvoYaddnkmabN3VKGjnAcrr1nlRSUSKrG3B64JejIarC4lbFgsUHquCv8rfbgYxEiA99DDGpvcjXIz718Bj03aFQag%3D%7Ctkp%3ABk9SR6DdrOj0Zw"><em>Zero to One</em></a> tool: an upgrade to the interface of deterministic computers so delectable and powerful that it will make the human experience, tooling interop, and agentic efficiency orders of magnitude better.</p>
<h2 id="finding-a-boundary">Finding a Boundary<a href="#finding-a-boundary" aria-label="Link to heading 'Finding a Boundary'" data-heading-content="Finding a Boundary" class="anchor"></a></h2>
<p>One of the main ideas behind Kai is for the user to manage a simple config file, <code>kai.roc</code>, which can serve as a simplified interface for interacting with your determinate system (for the vast majority, Nix under the hood). This is hard, because:</p>
<ul>
<li>A) Nix has so many layers of the stack, e.g.:
<ul>
<li>Nix the language, used by:</li>
<li>Nix the package manager, used by:</li>
<li>NixOS, along with:</li>
<li>The nixpkgs package repository</li>
</ul>
</li>
</ul>
<p>That’s a <em>lot</em> of layers interoperating in a “closed” way because:</p>
<ul>
<li>B) Nix built these monolithically, such that it’s <a href="https://tvl.fyi/blog/rewriting-nix">very hard</a> to take one piece of that stack and use it for anything useful outside of the broader ecosystem. This means two things:
<ul>
<li>Boundaries are difficult to isolate at the conceptual level (essential complexity baked into the Nix ecosystem).</li>
<li>Boundaries are difficult to isolate at the code level (accidental complexity due to years upon years of cruft, ad-hoc development, and no real competition leading to a lack of need for interop).</li>
</ul>
</li>
</ul>
<p>All of this together forms a natural walled garden: unlike Apple, which <em>intentionally</em> fights its users by hiding code for the sake of (Apple believes) reduced complexity and financial incentives, Nix seems to have created one by accident. While explicitly encoding preferences for open software (via, for example, needing to manually modify configs to set a flag <code>allowUnfree = true</code>) and a philosophy which advocates maximal user control over their systems, in practice it is so complex and abstruse for the average daily user, with so many layers, that anyone who wishes to improve upon or isolate a single piece faces a very effective barrier to general adoption because, in sum:</p>
<ul>
<li>A) Users can’t figure out how to use it, and:</li>
<li>B) Developers don’t have a middle ground between improving on the current ecosystem (thus adopting its current problems and working <em>backward</em> to solve a limited subset of things it can), or creating new systems (which, despite the profundity of the idea, is so difficult to do—see all the many layers above!—that only one has even tried: <a href="https://guix.gnu.org/">Guix</a>, and its community is very small).</li>
</ul>
<h2 id="enter-kai">Enter Kai<a href="#enter-kai" aria-label="Link to heading 'Enter Kai'" data-heading-content="Enter Kai" class="anchor"></a></h2>
<p>Kai proposes a middle way. Kai acknowledges two fundamental facts:</p>
<ol>
<li>Nix dominance</li>
<li>Nix “Accidental Complexity”</li>
</ol>
<p>Nix has the <a href="https://www.reddit.com/r/NixOS/comments/1n5dgpb/why_nixos_has_one_of_the_largest_repository_un/">largest package ecosystem</a> in the world. A stunning achievement for a package manager which isn’t the most well used, and an easy testament to the power of how reproducible software allows you to <em>ship</em>. Aside from that, it has seen slow increased adoption over the years despite continually fracturing communities and continuously deprecating APIs: even frontier LLMs get frequently confused about the best way to use Nix, because so much of the documentation teaches deprecated practices! Finally, there are no <em>real</em> competitors: imagine trying to build a <em>completely unique</em> package manager which uses a custom language and build a custom operating system from it while maintaining the largest package ecosystem on Earth, <em>without</em> being a for-profit company. Despite valid criticisms of Nix, this is a staggering achievement! One that is <em>extremely</em> difficult to replicate.</p>
<p>On the other hand, Nix suffers from so many problems which don’t need to exist. I sometimes wonder if its creator, <a href="https://edolstra.github.io/">Eelco Dolstra</a>, thought that some enterprisers would take his idea and carry it out to permanently improve the world by eliminating the whole class of <a href="https://edolstra.github.io/pubs/nspfssd-lisa2004-final.pdf">software deployment problems</a>, in the way that, say, Rust did for programmers. Maybe he, like Benjamin Franklin when he named the poles of electricity positive and negative, thought that someone would take his discovery and formalize it more appropriately and give them better names, whereas instead, we are just stuck with these terms, and we are still stuck with Franklin’s conceptual mistake of inverting the logical poles. And thus we are still stuck with many of the tradeoffs of Nix that are probably not worth having, despite the scope of the downstream improvements of using it over our current imperative systems being enormous and fundamental. Here we are over twenty years later, still building on top of inferior foundations. A huge portion of this I think is to be blamed on Nix’s design at basically every level.</p>
<p>Taking these into account, Kai proposes to do the following:</p>
<ol>
<li>Be backward compatible with Nix. Indeed, to simply <em>use</em> Nix directly under the hood at first.</li>
<li>Remove as much accidental complexity from using Nix as possible. Primarily through simplified UX and useful missing features or abstractions.</li>
</ol>
<p>Alan Kay’s motto is my North Star here: “Simple things should be simple; complex things should be possible.” That is, make Nix (via Kai) as easy as possible to use while allowing users to do anything they’d normally be able to do in Nix (even modify/create raw Nix files where they find they can’t do something in pure Kai).</p>
<p>This has resulted in me spending a lot of time seeking the appropriate <a href="https://www.destroyallsoftware.com/talks/boundaries">boundaries</a> to expose. After settling on <a href="https://roc-lang.org/">Roc</a> as the config language to use, I posted my idea on the <a href="https://roc.zulipchat.com/">Roc forum</a>, and to my amazement, Luke Boswell created a working prototype of my idea, <a href="https://github.com/lukewilliamboswell/roc-blueprint">roc-blueprint</a>, overnight!</p>
<p>This was great, as <code>roc-blueprint</code> provided an example of a general, pure, typed, and portable backend which could then be <em>lowered</em> into a compatible backend of choice (i.e. any reproducible system). In reality there are only two: Nix and Guix. Then this lowered Roc representation (itself another platform) could be used to instantiate and run actual respective commands. For example, here’s a program that provides a portable representation of a Nix shell which contains the <code>pokemonsay</code> program:</p>
<pre><code class="language-roc">app [main!] {
    pf: platform &quot;https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/1.0.0/AnZoxzoGPtSGQ15EQh6pBeeaHJ7aizP9MQhK81dES3Uq.tar.zst&quot;,
    blueprint: &quot;../../blueprint/package.roc&quot;,
}

import pf.Stdout
import blueprint.Blueprint
import blueprint.Requirement
import blueprint.Target
import blueprint.Environment
import blueprint.Nix

pokemonsay : Requirement
pokemonsay = Requirement.new({
    id: &quot;pokemonsay&quot;,
    display_name: &quot;Pokemonsay Program&quot;,
})

workspace : Blueprint.Draft
workspace = Blueprint.workspace({
    name: &quot;pokemonsay shell&quot;,
    target_systems: [Target.X86_64Linux],
    envs: [
        Environment.new({
            name: &quot;default&quot;,
            requirements: [pokemonsay],
        }),
    ],
})

nix_config : Nix.Config
nix_config = Nix.config({
    nixpkgs: Nix.github_input(
        &quot;nixpkgs&quot;,
        &quot;NixOS&quot;,
        &quot;nixpkgs&quot;,
        &quot;nixos-unstable&quot;,
    ),
    bindings: [
        Nix.bind(
            pokemonsay,
            &quot;nixpkgs&quot;,
            [&quot;pokemonsay&quot;],
        ),
    ],
})

main! : List(Str) =&gt; Try({}, _)
main! = |_args| {
    valid = Blueprint.validate(workspace) ? |errors| BlueprintInvalid(errors)
    source = Nix.render(valid, nix_config) ? |errors| NixInvalid(errors)
    Stdout.line!(source)?
    Ok({})
}
</code></pre>
<p>Cool! But notice any problems?</p>
<ol>
<li>Boilerplate-galore! The actual <code>flake.nix</code> this renders is fewer lines than the config! (21 vs. 54)</li>
</ol>
<pre><code class="language-nix"># Generated by roc-blueprint and roc-blueprint-nix. Do not edit.
{
  &quot;description&quot; = &quot;Development environments for pokemonsay shell&quot;;
  &quot;inputs&quot; = {
    &quot;nixpkgs&quot; = {
      &quot;url&quot; = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;
    };
  };
  &quot;outputs&quot; = { nixpkgs, ... }:
    {
      &quot;devShells&quot; = {
        &quot;x86_64-linux&quot; = {
          &quot;default&quot; = nixpkgs.&quot;legacyPackages&quot;.&quot;x86_64-linux&quot;.&quot;mkShell&quot; {
            &quot;packages&quot; = [
              nixpkgs.&quot;legacyPackages&quot;.&quot;x86_64-linux&quot;.&quot;pokemonsay&quot;
            ];
          };
        };
      };
    };
}
</code></pre>
<p>So the resulting config file is more complex than what it is simplifying, with this caveat: we define the portable (<code>blueprint</code> platform) and the non-portable (<code>blueprint_nix</code> platform) in the same file, the benefit being we can reuse <code>blueprint</code> programmatically and theoretically have it port to any number of determinate system protocols under the hood (eventually, one config could boil down to a Nix <em>or</em> Guix system). Another benefit is we get strong typing: unlike <code>flake.nix</code>, the individual pieces are strongly typed and thus linters and checkers could be built upon this to much greater effect. So these are some great benefits, the strong typing and portability alone we may even call <em>Zero to One</em> benefits, but we have only achieved goal (1) above:</p>
<blockquote>
<p>Be backward compatible with Nix. Indeed, to simply <em>use</em> Nix directly under the hood at first.</p>
</blockquote>
<p>But we’ve made (2) worse, adding complexity for arguably marginal gains for the average user:</p>
<blockquote>
<p>Remove as much accidental complexity from using Nix as possible. Primarily through simplified UX and useful missing features or abstractions.</p>
</blockquote>
<p>There are also other problems: for example the user must have a compatible <a href="https://roc-lang.org/">Roc</a> compiler installed, and they have to do <code>roc main.roc</code> to run it.</p>
<p>And even if we solve the above, this is an <em>incomplete</em> solution: the platform design is great, but must we really expect every user to just import this into their own programs, and write the logic to lower it?</p>
<p>I thought hard about what to do here, because the pros of this design are real, and I wanted to keep it for the time being as programmers might find this level of control useful, but for the vast majority of people a simpler config is in order. So I wrote one abstraction layer on top of it. Where the previous design looked like this:</p>
<pre><code>[portable blueprint] -&gt; [nix blueprint] -&gt; [nix files]
</code></pre>
<p>The current one adds:</p>
<pre><code>[kai.roc config] -&gt; [portable blueprint] -&gt; [nix blueprint] -&gt; [nix files]
</code></pre>
<p>The <code>kai.roc</code> sacrifices a bit more detail and control for simplicity and looks like this:</p>
<pre><code class="language-roc">app [config] {
    kai: platform &quot;../../platform/config.roc&quot;,
}

import kai.Kai

config : Kai.Config
config = {
    name: &quot;pokemonsay shell&quot;,
    shell: {
        pkgs: [&quot;pokemonsay&quot;],
    },
}
</code></pre>
<p>Much better! 13 total lines, and only 5 are config that the user needs to consider in the base case! But the idea is that the user will be able to move down and use the tool at any layer of abstraction as suits their needs. I’m not sure whether I’ll keep <code>roc-blueprint</code> at all yet, as the benefits of an intermediate representation between high-level abstraction and low-level control must be weighed against the need for maintenance simplicity, and I’m not sure yet of the value proposition when the simpler model exists.</p>
<p>To achieve this simplicity over <code>roc-blueprint</code>, we make some assumptions on:</p>
<ul>
<li>The distinction between a validated <code>blueprint</code> and a <code>Draft</code> workspace</li>
<li>The <code>Environment</code> concept</li>
<li>The machine architecture (we remove <code>Target.X86_64Linux</code> from above, for example)</li>
</ul>
<p>But I think many of these decisions can appropriately be outsourced to under-the-hood management by a CLI tool, which is the second part of the solution.</p>
<p>For example, we don’t have to encode the architecture in the config mandatorily if the CLI tool <code>kai</code> can just save those details in the config (or just default to building for all default/common systems).</p>
<p>Right now, the CLI can only do <code>kai shell</code>. It is dependent on your machine having Nix (and Nix is still hardcoded in) and most egregiously on being x86_64 Linux (sorry!), but this will be modularized away later.</p>
]]></content:encoded></item>
<item><title>Packaging Roc Nightlies with a Nix Flake and Overlay</title><link>https://blu.cx/posts/blog/2026-07-16-packaging-roc-nightlies/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2026-07-16-packaging-roc-nightlies/</guid><pubDate>Thu, 16 Jul 2026 12:00:00 +0000</pubDate><description>Packaging official Roc nightly compiler binaries reproducibly with a Nix flake and overlay.</description><content:encoded><![CDATA[<h2 id="packaging-roc-nightlies-with-a-nix-flake-and-overlay">Packaging Roc Nightlies with a Nix Flake and Overlay<a href="#packaging-roc-nightlies-with-a-nix-flake-and-overlay" aria-label="Link to heading 'Packaging Roc Nightlies with a Nix Flake and Overlay'" data-heading-content="Packaging Roc Nightlies with a Nix Flake and Overlay" class="anchor"></a></h2>
<p><a href="https://mitchellh.com/">Mitchell Hashimoto</a> built an excellent <a href="https://github.com/mitchellh/zig-overlay">Zig overlay flake</a> which allows you to get the nightly version of the Zig compiler with a single Nix command. At the time of this writing, Zig 0.16.0 is the latest stable release. Some projects intentionally track Zig’s rapidly changing development branch instead. <a href="https://codeberg.org/ziglings/exercises">Ziglings</a>, for example, currently requires a sufficiently recent 0.17.0 development build and does not compile with Zig <code>0.16.0</code>! Although Zig’s official binary can run on NixOS, integrating it reproducibly and ensuring host linking works correctly requires additional Nix setup. If <code>zig-overlay</code> didn’t exist, I’d have had to roll a custom solution just for myself. An overlay/flake is thus useful for any project not considered stable to allow anyone to have a reproducible/pinnable or just bleeding-edge environment for any version indexed by the overlay. It makes compatibility and environment setup orders of magnitude easier to achieve. No more hunting down binaries! (or hunting down and patching if you’re on NixOS).</p>
<p>The newer and more in flux the project, the more useful this is. And with the proven utility of the <code>zig-overlay</code> template, I decided to build one for an even newer programming language, <a href="https://roc-lang.org/">Roc</a>. At present, Roc releases <a href="https://github.com/roc-lang/nightlies/releases">nightly</a> binaries of the compiler. They are currently replacing the Rust compiler with a new scratch Zig rewrite, which has breaking changes to Roc’s design such that code and platforms targeting the old compiler are usually incompatible with the new compiler, and have not made a stable release of the Zig compiler <a href="https://github.com/roc-lang/roc/releases">release</a> yet. Thus, there’s a lot of incompatibility between apps which were built using the old and new Roc compilers, since the new compiler introduces a lot of backward-incompatible language features. So, being a nix enthusiast, I thought it would be helpful to create a <code>roc-overlay</code> modeled after <code>zig-overlay</code>, allowing tracked versions of the nightly compiler to be selected by a developer, or even better, declared as an input in <code>flake.nix</code> and pinned to an exact repository revision in <code>flake.lock</code> for given projects so the developer doesn’t even have to think about which nightly to use.</p>
<h3 id="roc-overlay-contents"><code>roc-overlay</code> Contents<a href="#roc-overlay-contents" aria-label="Link to heading 'roc-overlay Contents'" data-heading-content="roc-overlay Contents" class="anchor"></a></h3>
<p><a href="https://github.com/thebrandonlucas/roc-overlay"><code>roc-overlay</code></a> provides a Nix flake for the official Roc new-compiler binaries. It installs prebuilt archives and does not build from source. It mirrors <code>zig-overlay</code> in overall structure. Use it like this:</p>
<pre><code class="language-bash"># To get the latest nightly as seen by the repo,
# run one of these:

# Create a shell with the compiler.
nix shell github:thebrandonlucas/roc-overlay
roc version

# Run one-off commands directly from the flake.
nix run github:thebrandonlucas/roc-overlay -- version
</code></pre>
<p>Including the overlay in your <code>flake.nix</code>:</p>
<pre><code class="language-nix">   {
     inputs = {
       nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;
       roc-overlay.url = &quot;github:thebrandonlucas/roc-overlay&quot;;
     };

     outputs = { nixpkgs, roc-overlay, ... }:
       let
         system = &quot;x86_64-linux&quot;;
         pkgs = import nixpkgs {
           inherit system;
           overlays = [ roc-overlay.overlays.default ];
         };
       in
       {
         devShells.${system}.default = pkgs.mkShell {
           packages = [ pkgs.rocpkgs.nightly ];
         };
       };
   }
</code></pre>
<p>Hopefully you find this overlay useful. Please <a href="https://github.com/thebrandonlucas/roc-overlay/issues/new">open an issue</a> if you find anything that needs improvement.</p>
]]></content:encoded></item>
<item><title>Prototyping a friendlier frontend for determinate computing</title><link>https://blu.cx/posts/articles/2026-07-13-kai-friendly-frontend/</link><guid isPermaLink="true">https://blu.cx/posts/articles/2026-07-13-kai-friendly-frontend/</guid><pubDate>Mon, 13 Jul 2026 12:00:00 +0000</pubDate><description>Making determinate computing friendly by building a modular frontend for Nix, Guix, and future reproducible systems.</description><content:encoded><![CDATA[<blockquote>
<p>Simple things should be simple, complex things should be possible.</p>
<ul>
<li><a href="https://www.quora.com/What-is-the-story-behind-Alan-Kay-s-adage-Simple-things-should-be-simple-complex-things-should-be-possible">Alan Kay</a></li>
</ul>
</blockquote>
<p><a href="https://docs.determinate.systems/determinate-nix"><code>nix</code></a>, and the whole ecosystem surrounding it, is the only major attempt at a top-to-bottom solution to the problem of reproducible software. There is one other that has implemented this (<a href="https://guix.gnu.org/">guix</a>), but its mindshare is dwarfed by Nix’s, which is in turn dwarfed by other package managers and Linux distros, which are in turn dwarfed by proprietary programs and operating systems like macOS and Windows.</p>
<p>Nix is, most fundamentally, a means of getting software from machine or environment A to machine or environment B in a deterministic way: that is, as long as you can use <code>nix</code> on both machines to perform the transfer and the target machine has the resources to run the program, you can guarantee that the software will be transferred completely and have all the dependencies it needs to run. No other major package manager does this.</p>
<p>Technically this is a big leap forward, but, given how few use <code>nix</code>, we can’t yet call it that. It’s no secret that <a href="https://docs.determinate.systems/determinate-nix"><code>nix</code></a> is <a href="https://bmcgee.ie/posts/2023/01/nix-and-nixos-a-retrospective/">notoriously difficult</a> to use. But it seems to me that many of these problems are problems of <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet#Accidental_complexity">accidental complexity</a> (problems engineers create through suboptimal design and can therefore fix) rather than <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet#Essential_complexity">essential complexity</a> (problems inherent to the problem itself). Since Nix is very old (older than <code>git</code>!), there has been a lot of churn on tooling, which has resulted in a <em>lot</em> of tooling, a <a href="https://tvl.fyi/blog/rewriting-nix">monolithic architecture</a>, <a href="https://nixos.org/community/">split</a> <a href="https://determinate.systems/">communities</a>, <a href="https://nixos.org/guides/nix-pills/03-enter-environment.html#install-something">outdated documentation</a>, and <a href="https://nickel-lang.org/">many</a> <a href="https://devenv.sh/">many</a> <a href="https://www.jetify.com/devbox">many</a> third party solutions aiming to solve (and re-solve) the same accidents of complexity.</p>
<p><code>nix</code> does not do <a href="https://cscie2x.dce.harvard.edu/hw/ch01s06.html">one thing well</a> nor is there <a href="https://ziglang.org/download/0.1.1/release-notes.html">one obvious way to do any given thing</a>: <a href="https://www.quora.com/What-is-the-story-behind-Alan-Kay-s-adage-Simple-things-should-be-simple-complex-things-should-be-possible">simple things are complex and complex things are complex</a>.</p>
<p>That said, at its base, <code>nix</code> is a beautiful idea, and an elegant solution to the software distribution problem. It enables so many positive downstream effects. It makes your <em>package management and operating system declarative</em>, and brings the deterministic, mathematical certainty of a functional language to your computer-using experience. <em>That</em> is power.</p>
<p>Thus, we have a revolutionary tool that basically no one uses because it is very difficult and the ecosystem is fragmented. Therefore I believe that the consequences of making a determinate system like <code>nix</code> easy to use will have a profound impact on how we interact with computers for the better.</p>
<p>As such, this problem is begging for a solution.</p>
<h2 id="kai---prototyping-a-friendlier-frontend-for-determinate-computing">Kai - Prototyping a friendlier frontend for determinate computing<a href="#kai---prototyping-a-friendlier-frontend-for-determinate-computing" aria-label="Link to heading 'Kai - Prototyping a friendlier frontend for determinate computing'" data-heading-content="Kai - Prototyping a friendlier frontend for determinate computing" class="anchor"></a></h2>
<p>I’ve begun working on one such solution which I’m calling <a href="https://github.com/thebrandonlucas/kai"><code>kai</code></a>, short for the <a href="https://en.wikipedia.org/wiki/Kairos">Ancient Greek καιρός</a> or “the right time”. At first glance, it is just lipstick on <code>nix</code>, which alone would be great. But with the proper design we can architect it to be much more.</p>
<p>The goal is to:</p>
<ol>
<li>Define a generic protocol which enumerates a set of capabilities any determinate system should have, such as:
<ul>
<li>Reproducible developer shells for teams and projects (probably the most common <code>nix</code> use case now)</li>
<li>Ad-hoc shells for testing out software on the fly</li>
<li>Build targets allowing you to create virtual machines, or deployments with the specified software and configurations preinstalled, or the ability to create custom ISOs to roll your own operating system configuration!</li>
<li>Rollback to previous generations if a new dependency breaks the system</li>
<li>Automated garbage collection for dangling dependencies no longer in use</li>
<li>etc.</li>
</ul>
</li>
<li>Implement the protocol in a CLI tool and config file such that it is compatible with the two full-fledged determinate systems (<code>nix</code> and <code>guix</code>) and any others which later implement the generic protocol, eliminating as much accidental complexity as possible and making them <em>friendly</em> and <em>fun</em> to use. The mappings of the generic protocol to the concrete implementations we call <a href="https://github.com/lukewilliamboswell/roc-blueprint">blueprints</a> (thanks <a href="https://github.com/lukewilliamboswell">Luke Boswell</a>!)</li>
<li>Make the tool modular such that users can:
<ul>
<li>Take only those parts of the determinate system which they want (some may only be interested in reproducible shells while others want build systems – there should of course be a “default” option which has everything the average user wants).</li>
<li>Modify or create their own implementation of a given tool. For example, <a href="https://tvl.fyi/blog/rewriting-nix">tvix</a> spawned out of a desire to make a <em>modular</em> <code>nix</code> implementation since the primary implementation is <em>monolithic</em>, the problem being that if you don’t like the way a given piece works or believe it is inefficient, you cannot easily swap it out for a different implementation and must satisfy yourself with the monolith.</li>
<li>Add new modules and commands as desired (for example one may want a <code>kai deploy</code> which handles deploying to their VPS of choice).</li>
</ul>
</li>
</ol>
<p>This clearly goes beyond “make a better <code>nix</code> frontend”. The motivation is that this design will allow us to experiment and play with designs for a <em>better</em> system while being backward compatible with existing ones. And further, replace <em>components</em> of one of the existing systems (e.g. replacing one of the <code>nix</code> components with a <code>tvix</code> one, but still having the total package management solution be <code>nix</code>-compatible).</p>
<p>It is built in the <a href="https://www.roc-lang.org/"><code>roc</code></a> programming language, a high-level functional language which allows us to create platforms (in a lower-level language, <code>zig</code>, in this case) which define behavior tailored to specific use-cases. For example, in our case we create a platform to specify an EDSL (<a href="https://learn-haskell.blog/03-html/03-edsls.html">Embedded Domain-Specific Language</a>) to implement the determinate protocol.</p>
<p>So far, <code>kai</code> is just a proof of concept which implements a basic version of each of the three ideas above. The nascent determinate protocol so far has two commands in the CLI, <code>shell</code> and <code>build</code>:</p>
<ul>
<li>
<p><code>shell</code>: Enters a shell containing the packages specified in <code>kai.roc</code> by calling the corresponding <code>shell</code> command. This is equivalent to a <code>flake.nix</code> for <code>nix</code> and a <code>manifest.scm</code> for <code>guix</code>. If none exists in the current directory, it scaffolds a <code>kai.roc</code> file with no packages. Example:</p>
<pre><code class="language-roc">app [config] { kai: platform &quot;../platform/config.roc&quot; }

config = [
    Shell({
        name: &quot;simple-kai-shell&quot;,
        # `packages` is a Roc header keyword in this compiler, so shell configs use `pkgs`.
        pkgs: [&quot;git&quot;],
    }),
]
</code></pre>
</li>
<li>
<p><code>build</code>: Builds the specified machine image according to <code>kai.roc</code>. Technically this should be a generic build for any target, but I’ve chosen a very common use case for the time being for simplicity. Example:</p>
<pre><code class="language-roc">app [config] { kai: platform &quot;../platform/config.roc&quot; }

config = [
    MachineBuild({
        hostname: &quot;kai-example&quot;,
        system: &quot;x86_64-linux&quot;,
        install: [&quot;git&quot;],
        ssh_keys: [],
        state_version: &quot;25.05&quot;,
        image: { format: &quot;qcow2&quot; },
    }),
]
</code></pre>
</li>
</ul>
<p>There is also a third command, <code>blueprint</code>, which allows you to choose the backend which implements the determinate protocol. In our case we have two blueprints which can implement <code>shell</code> and <code>build</code>: <code>nix</code> and <code>guix</code>. So if you <code>blueprint list</code> to see your options, then do <code>blueprint set nix</code>, calling <code>shell</code> will perform <code>nix develop</code> under the hood and <code>build</code> will call the appropriate command to build the machine above.</p>
<p>There is also a <code>.kai</code> directory which manages a config file corresponding to the blueprint. For <code>nix</code>, <code>.kai</code> will contain a <code>flake.nix</code> and our <code>build</code> and <code>shell</code> commands will manipulate and use that under the hood. For <code>guix</code>, the file is <code>manifest.scm</code>.</p>
<p>Next in the plan is to add a bespoke <code>deploy</code> command modularly, such that <code>deploy</code> can mean different things for different people, and we can both (1) show the power of <code>kai</code> to easily deploy machines preconfigured with software, and (2) show how people can extend <code>kai</code> commands to suit their various use cases.</p>
<p>Onward!</p>
]]></content:encoded></item>
<item><title>Lyceum: A New Ancient Greek Digital Library For A Post-LLM World</title><link>https://blu.cx/posts/articles/2026-03-31-lyceum-pipeline/</link><guid isPermaLink="true">https://blu.cx/posts/articles/2026-03-31-lyceum-pipeline/</guid><pubDate>Tue, 31 Mar 2026 12:00:00 +0000</pubDate><description>Building an AI-powered pipeline to generate the largest, richest corpus of Ancient Greek texts with interlinear translations, morphology, and treebank data from a single prompt.</description><content:encoded><![CDATA[<blockquote>
<p>…to read the Latin &amp; Greek authors in their original is a sublime luxury … I enjoy Homer in his own language infinitely beyond Pope’s translation of him […] I thank on my knees him who directed my early education for having put into my possession this rich source of delight: and I would not exchange it for any thing which I could then have acquired &amp; have not since acquired.</p>
<p>Thomas Jefferson, <a href="https://tjlibraries.monticello.org/tjandreading/quotes.html"><em>Thomas Jefferson to Joseph Priestley, Philadelphia, 27 Jan. 1800</em></a></p>
</blockquote>
<hr />
<blockquote>
<p>But of the mere facts of history, as commonly accepted, what educated youth of any mental activity does not learn as much as is necessary, if he is simply turned loose into an historical library? What he needs on this, and on most other matters of common information, is not that he should be taught it in boyhood, but that abundance of books should be accessible to him.</p>
<p>The only languages, then, and the only literature, to which I would allow a place in the ordinary curriculum, are those of the Greeks and Romans; and to these I would preserve the position in it which they at present occupy. That position is justified, by the great value, in education, of knowing well some other cultivated language and literature than one’s own, and by the peculiar value of those particular languages and literatures.</p>
<p>John Stuart Mill, <a href="https://monadnock.net/mill/inaugural.html"><em>Inaugural Address</em></a>, 1867.</p>
</blockquote>
<hr />
<p>I’m building a new open source digital library for Ancient Greek using AI.</p>
<p>With the rise of LLMs, we have an opportunity to create new tools for preservation, learning, and engaging with ancient texts that would have taken massive amounts of dedicated and skilled work to produce before.</p>
<p>The tools which pioneered the work of bringing Ancient Greek to the internet can be credited to projects like <a href="https://www.perseus.tufts.edu/">Perseus Digital Library</a>, <a href="https://scaife.perseus.org/">Scaife Viewer</a>, and others. Building on top of their work in the age of AI can yield results previously unfeasible.</p>
<p>Thus, there is a unique opportunity to initiate a revival of interest in the works of Antiquity in their original language; to let us hear the authors speak again with their own voices.</p>
<p>To this end I created a set of individual skills, coordinated as a pipeline by a meta-skill <code>text-orchestrator</code>, for creating this database automatically. The goal is: given a single prompt like “Add Plutarch’s Lives”, it will search the web, find the original Greek edition, find public domain English translations, download them, process and clean them, segment them into verses (or create a custom versifiable translation if no extant translation aligns well), create interlinear translations, create Latin transliterations for pronunciation, and create <a href="https://en.wikipedia.org/wiki/Treebank">treebank</a> data for each word.</p>
<p>This is still only in the “proof-of-concept” stage, and there are many improvements to make, but it has successfully run through the full pipeline on a few smaller sample texts. Therefore <code>text-orchestrator</code> could be an extremely powerful tool for automating database <em>generation</em> from a single prompt. The data it collects is optimized for learning purposes, and the pipeline ends by generating a database artifact, <code>texts.db</code>, which can be directly imported into the <a href="https://lyceum.quest">lyceum.quest</a> Ancient Greek reader, which is a companion project to this one. It acts as the interface for the data and is my attempt to bring together all the best aspects of Perseus, Scaife, and other readers online, and improve them where possible.</p>
<p>I also show how I ran <a href="https://github.com/karpathy/autoresearch">Karpathy’s Autoresearch</a> to fine-tune an <code>alignment</code> skill which generates English interlinears for every word of the Greek text. We should be able to use this method to fine-tune any skill in the pipeline, but that is reserved for future work.</p>
<h3 id="table-of-contents">Table of Contents<a href="#table-of-contents" aria-label="Link to heading 'Table of Contents'" data-heading-content="Table of Contents" class="anchor"></a></h3>
<ul>
<li><a href="#why">Why</a></li>
<li><a href="#goal">Goal</a></li>
<li><a href="#limitations">Limitations</a></li>
<li><a href="#ai-generated-data">AI Generated Data</a></li>
<li><a href="#agent-optimizations-and-workflow">Agent Optimizations and Workflow</a></li>
<li><a href="#desired-data">Desired Data</a></li>
<li><a href="#generate-verify-optimize-loop">Generate, Verify, Optimize Loop</a></li>
<li><a href="#the-text-orchestrator-skill-pipeline">The Text Orchestrator Skill Pipeline</a></li>
<li><a href="#future-directions">Future Directions</a></li>
<li><a href="#bottlenecks">Bottlenecks</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<h2 id="why">Why<a href="#why" aria-label="Link to heading 'Why'" data-heading-content="Why" class="anchor"></a></h2>
<p>I believe this revival is imperative in a time where technology and culture allow and encourage a distancing from our roots which leaves us further untethered from a deep historical understanding of ourselves and our “story” in the world.</p>
<p>As a result nearly all of the institutions for which learning Greek was once a rite of passage in any serious education are gone, and the curious self-driven student is mostly left with fragmented resources for learning.</p>
<p>Further, the attention economy and modern technology constantly competes with any new learner’s focus.</p>
<p>This deadly trifecta of difficult and fragmented learning resources, with a centuries-long cultural cooling on and de-institutionalization of the classics, and deep distraction-technology integration into our personal lives are deep barriers to overcome, but the rise of LLMs can, I believe, help overcome all three.</p>
<p>I take John Stuart Mill’s view that <a href="https://monadnock.net/mill/inaugural.html">history is fundamentally interesting</a> in and of itself, and if these three barriers are overcome, students would come flocking back to learn.</p>
<p>Therefore, reviving interest in learning ancient languages mandates lowering those barriers to entry and making the learning process as engaging and as easy to dive into what the student is there to learn as possible.</p>
<p>With LLMs, we can do this.</p>
<p>As Dr. Gregory Crane, founder and head of the <a href="https://www.perseus.tufts.edu/">Perseus Digital Library</a>, notes:</p>
<blockquote>
<p>Now we have multiple Large Language Models (LLMs) that can not only translate but also provide reasonable word-by-word grammatical explanations of any source text. My first goal is to provide my students with enough knowledge so that they can begin to exploit this rich and increasingly complex scaffolding.</p>
<p>[…]</p>
<p>LLMs are slow and frontier models are expensive but, given the money, there is no reason we could not generate literal translations for all Classical Greek and Latin literature.</p>
<p>Dr. Gregory Crane, <a href="https://sites.tufts.edu/perseusupdates/2026/03/10/towards-corpus-based-learning-exercises-with-ancient-greek/"><em>Towards corpus-based learning: exercises with Ancient Greek</em></a>, 2026.</p>
</blockquote>
<h2 id="goal">Goal<a href="#goal" aria-label="Link to heading 'Goal'" data-heading-content="Goal" class="anchor"></a></h2>
<p>My primary goal is three-in-one: Create the <em>largest</em> corpus of publicly available online Greek texts with the <em>richest</em> set of accompanying data to allow us to build the most <em>intuitive</em>, <em>engaging</em>, and <em>useful</em> interfaces possible for both learners and experts.</p>
<p>If this is successful, we can expand to other ancient languages, starting with Latin.</p>
<p>I’ve already built the initial version of the interface at <a href="https://lyceum.quest">lyceum.quest</a>. See the announcement post for the dashboard <a href="https://blu.cx/posts/articles/2026-02-23-introducing-lyceum/">here</a>.</p>
<p>The proof-of-concept has been validated in showcasing a taste of what’s possible now and is already one of the most feature-rich interfaces available. It aggregates much of the freely-available data online. It makes available side-by-side Greek-English translations with the Greek fully parsed, such that each word of Greek text becomes interactive. A user can click on a word to see a popup containing definitions from each major Greek database (Perseus, <a href="https://logeion.uchicago.edu/">Logeion</a>, and <a href="https://lsj.gr/">LSJ</a>), see morphology and grammar data, and even see an AI-generated contextual meaning. It also has numerous display enrichment options like color highlighting for inflections and grammar, and an Anki-style spaced repetition deck for memorizing words from a reader’s chosen texts.</p>
<p>We can, however, do so much more.</p>
<p>For example, what I personally felt was missing most from my own learning were <a href="https://en.wikipedia.org/wiki/James_Hamilton_(language_teacher)">Hamilton</a>-style word-by-word interlinear translations, which are rare to find online, as that would fill a primary gap for me when learning. So I had Claude generate them custom for the Iliad, which was pretty token-heavy and expensive. But the value of having disambiguated word meanings was immediately apparent. Further, it’s extremely important to note that for most Greek texts, interlinear translations have <em>never been done</em>. Even for the Iliad itself, I was unable to find a complete interlinear English translation (Hamilton himself, the champion of this method, only did this for a few books of the Iliad).</p>
<p><img src="/images/articles/lyceum-pipeline/hamilton-interlinear.png" alt="Hamilton’s Iliad Interlinear" /></p>
<p>In short, it takes a tremendous amount of effort even from expert humans to create interlinear translations, which the world would greatly benefit from having. This is just one example of a real gap here that could benefit greatly from AI.</p>
<h2 id="limitations">Limitations<a href="#limitations" aria-label="Link to heading 'Limitations'" data-heading-content="Limitations" class="anchor"></a></h2>
<p>A major goal is to do this for as many texts as possible, starting with the most desired texts like Homer, Plutarch, The Gospels, or Meditations. But I was generating these interlinear translations through manual prompting, with crude trial-and-error experimentation. I eventually learned to feed it “ground truth” data (using Hamilton’s <em>Iliad</em> interlinear as a sample) so that it could develop an <em>alignment</em> skill from which it would produce Hamilton-like interlinear translations for a given text. I was able to achieve moderate success with this but still found the method too manual, ad-hoc, and error-prone, and translations would not always disambiguate the word correctly for the given context.</p>
<p>Two things quickly became clear:</p>
<ol>
<li>There was much more we could do than merely interlinear translations.</li>
<li>My current approach of prompting AI per-translation was expensive and unscalable.</li>
</ol>
<p>For (1), I realized I wanted more than interlinears. In one reader view, I wanted:</p>
<ul>
<li>Full prose translations (as many public domain as I could find)</li>
<li>Interlinear beneath each word</li>
<li>Segmented verses, like the Bible (enabling side-by-side views)</li>
<li>Transliterations (the Latin rendering for pronunciation help)</li>
<li>Treebanks containing grammar, inflection, and other morphology data for each word (ideally color coded for easier sight-recognition)</li>
</ul>
<p>This way, information could be <em>progressively</em> revealed to the reader as it suits them. They can try to learn each word one at a time, learn how it fits within the phrase or sentence, then learn how that sentence fits within the greater narrative. This level of granularity exists at the word level.</p>
<p>This way, the text can truly “come alive” as each word now represents a rich treasure trove of data that a reader can dig into at their preference and to their taste.</p>
<p>So a single Greek text can go from this:</p>
<p><img src="/images/articles/lyceum-pipeline/before-greek-text.png" alt="Before: Just Greek Text" /></p>
<p>To this:</p>
<p><img src="/images/articles/lyceum-pipeline/after-enriched.png" alt="Shows interlinear/translit and inflection/grammar etc" /></p>
<p>These are just a sample of the things we can do with <em>static</em>, one-time LLM use. We could also add interactive AI chat elements like highlighting and asking about a passage, like <a href="https://ancientgreek.jean.land">ancientgreek.jean.land</a> does, for example. Or a chat just to talk about a specific work or ask general questions.</p>
<p>Eventually, we could even use AI speech-to-text (STT) and text-to-speech (TTS) features to help students learn and grade their pronunciation, or read passages to them.</p>
<h2 id="ai-generated-data">AI Generated Data<a href="#ai-generated-data" aria-label="Link to heading 'AI Generated Data'" data-heading-content="AI Generated Data" class="anchor"></a></h2>
<p>For now, we will focus on what can be done up-front with AI. The tradeoff we are making at present is that the high costs of doing this work are all in the <em>initial</em> phase. Adding in dynamic AI features can come later. A good static data set for translations/transliterations etc. only needs to be done once and is then available forever.</p>
<p>Some, like <a href="https://ancientgreek.jean.land/">ancientgreek.jean.land</a>, have taken the approach of using AI to quickly generate translations on the fly, which is a great novel feature. But this won’t scale and should be long-term unnecessary. We need not spend tokens on any of the work required here any more than is necessary to produce a correct result. Therefore, what’s really needed is a large token budget to:</p>
<ol>
<li>Refine a process for generating the translations</li>
<li>Create translations using it</li>
</ol>
<p>And then a budget for human Greek experts to:</p>
<ol>
<li>Manually audit and correct the data produced by the process itself</li>
<li>Optimize ground-truth “holdout” data used by the agents to evaluate the quality of their work by (and automate improvements)</li>
</ol>
<h2 id="agent-optimizations-and-workflow">Agent Optimizations and Workflow<a href="#agent-optimizations-and-workflow" aria-label="Link to heading 'Agent Optimizations and Workflow'" data-heading-content="Agent Optimizations and Workflow" class="anchor"></a></h2>
<p>LLMs have recently passed a capability threshold in which many programmers find themselves no longer writing code, but exclusively prompting instead. This has led to massive debates on the true efficacy of LLMs: whether the code they produce is truly high-quality, functional, and maintainable, or “slop” masquerading under a flashy veneer. There is significant skepticism in the Classics community as well.</p>
<p>Despite valid misgivings, it appears that with the right workflows AI can produce surprisingly accurate results, which can then be refined by a human expert.</p>
<p>So if AI can translate accurately <em>enough</em>, this turns the problem from one of translation <em>generation</em> to one of <em>validation</em>, and the human cognitive load for validating work is orders of magnitude easier than the act of creating it.</p>
<p>This means we can, perhaps, for the first time in history, have a hope of creating a unified corpus rich interlinears, morphology data, versifications, and more to provide progressively enhanced experiences when engaging with ancient texts, all-in-one, as opposed to the fragmented efforts which exist today.</p>
<p>That said, the current state of the art appears to involve the heavy use of markdown files like SKILL.md and AGENTS.md along with intelligent context management (context being the primary scarce resource for agents). I’ve attempted to use best practices where possible to build a scalable pipeline for Lyceum which can take a simple query like “Add Plutarch’s Lives” and end with a rich set of data which includes the texts, translations, generated interlinear, etc. which are ready to import into the reader.</p>
<h2 id="desired-data">Desired Data<a href="#desired-data" aria-label="Link to heading 'Desired Data'" data-heading-content="Desired Data" class="anchor"></a></h2>
<p>As I was working through this it soon became clear that we may be able to automate the following:</p>
<ul>
<li>Interlinear translation</li>
<li>Latin transliteration</li>
<li>New Testament-like verse segmentation of Greek/English for phrase-level learning</li>
<li>Custom “versified” translation if no English translation aligns well (ex. Homer translations are rarely direct)</li>
<li>Treebanks</li>
</ul>
<p>Each of these would have been extremely time-intensive pre-AI, but following a Generate-Verify-Optimize loop (below), this could become a far more feasible task of auditing and verification rather than generation from scratch.</p>
<h2 id="generate-verify-optimize-loop">Generate, Verify, Optimize Loop<a href="#generate-verify-optimize-loop" aria-label="Link to heading 'Generate, Verify, Optimize Loop'" data-heading-content="Generate, Verify, Optimize Loop" class="anchor"></a></h2>
<p>Using <a href="https://agentskills.io/home">agent skills</a>, if we apply an approach of <em>Generate-Verify-Optimize</em> feedback looping, we can refine texts through a combination of agent-based methods like <a href="https://github.com/karpathy/autoresearch">Karpathy’s Autoresearch</a> and human feedback loops. For any skill we have which <em>generates</em> data, like <code>/skill:create-interlinear</code>, we craft human-expert curated data we can <em>verify</em> against from which the agent can <em>optimize</em> itself.</p>
<p>We save this verification “holdout” data to be used only for evaluation against the LLM’s results at the end of the process, so as not to bias it.</p>
<p>After comparing the generated data against the holdout data, we can see the error rate. There is some ambiguity here, as Greek word-level translations can have multiple correct meanings, even given the same passage or context.</p>
<p>For example, the opening word of the Iliad, μηνιν, could mean “wrath” or “rage”, and if the holdout data only has “wrath”, it could count “rage” as incorrect. Of course, over time the holdout data can be refined to account for multiple correct answers, but the primary downside is negligible as it just means we’re forcing the AI to conform to a specific style.</p>
<p>After verification, based on how effective it was, we can optimize the skill, either by hand or using tools such as Autoresearch.</p>
<p>Human verification is mandatory, but using Autoresearch to improve the skill can be very helpful and save a lot of time skill debugging. Autoresearch can be extremely effective at optimizing to hit a benchmark, but if the holdout data isn’t sufficiently accurate, or diversified enough to be representative of our generic goal of being able to work with any Greek text, we could be optimizing for the wrong solution. Of course, multiple skills could be optimized. This is where human judgement should intervene and manually inspect both the skill to ensure the wording accurately captures our goals, and, more importantly, the holdout data to make sure we’re optimizing the right target.</p>
<p>The current recommended path is probably something like:</p>
<ol>
<li>Human prompts AI to create generation skill to achieve a goal (i.e. “Generate interlinears for a given Greek text”).</li>
<li>Human reads and refines the generated skill.</li>
<li>Human creates holdout data or runs the skill once to see what sort of data it generates.</li>
<li>Human inspects holdout data and refines it to their exact requirements.</li>
<li>Begin AI <em>Generate-Verify-Optimize</em> loop with Autoresearch, which will:
a. <strong>Generate</strong> the data by invoking the skill in a subagent.
b. <strong>Verify</strong> the skill’s efficacy.
c. <strong>Optimize</strong> the skill by tweaking in response to failures.</li>
<li>Stop after N predetermined rounds or until the correctness threshold is met (e.g. 95% translation match against holdout data).</li>
<li>Human re-reads generated skill to ensure no unexpected overfitting.</li>
<li>Re-run Autoresearch if desired threshold isn’t met.</li>
</ol>
<p>Personally, I don’t see a problem with having the AI generate a skill initially: it’s often trivial for AI to lookup SKILL.md best practices and implement them better and faster than a human can. But since the temptation to “let the AI handle it” is strong due to the experience of waiting around for it to complete its tasks, the human-in-the-loop should take extra caution to ensure it’s behaving as expected, truly matching against the holdout as it claims to be, and passing against large and diverse sets of holdout data that accurately capture the breadth of the desired output.</p>
<h2 id="the-text-orchestrator-skill-pipeline">The Text Orchestrator Skill Pipeline<a href="#the-text-orchestrator-skill-pipeline" aria-label="Link to heading 'The Text Orchestrator Skill Pipeline'" data-heading-content="The Text Orchestrator Skill Pipeline" class="anchor"></a></h2>
<p>The <code>text-orchestrator</code> pipeline is a “set of skills” skill. Its goal is to take a simple prompt like “Add Plutarch’s Lives” and end with an <code>output/texts</code> directory containing a set of <code>.json</code> files and a query-able database <code>texts.db</code> which contains all the data listed in our desired list above. This <code>texts.db</code> is the same one that <a href="https://lyceum.quest">lyceum.quest</a> uses.</p>
<p>As subsequent texts are added via <code>text-orchestrator</code>, the skill is instructed to update, not overwrite, <code>texts.db</code>.</p>
<p><code>text-orchestrator</code> spawns subagents to orchestrate a 10 stage process from a kickoff prompt:</p>
<table>
<thead>
<tr>
<th>Stage</th>
<th>Name</th>
<th>Script</th>
<th>Description</th>
<th>Key Artifacts</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Intake</td>
<td><code>text_pipeline_intake.go</code></td>
<td>Resolve the requested work, choose add/apply mode, and create or refresh the workspace.</td>
<td><code>manifest.json</code>, <code>state.json</code>, <code>provenance.md</code>, <code>replay/stage-history.json</code></td>
</tr>
<tr>
<td>1</td>
<td>Source Discovery</td>
<td><code>text_pipeline_source_hunt.go</code></td>
<td>Find, rank, and record Greek/English/auxiliary source candidates with provenance and license notes.</td>
<td><code>sources/greek_candidates.json</code>, <code>sources/english_candidates.json</code>, <code>sources/auxiliary_resources.json</code>, <code>sources/source-recommendations.md</code></td>
</tr>
<tr>
<td>2</td>
<td>Extraction</td>
<td><code>text_pipeline_source_extract.go</code></td>
<td>Acquire approved sources, extract parseable text, preserve raw downloads alongside extracted output.</td>
<td><code>raw/*</code>, <code>extracted/greek.txt</code>, <code>extracted/english.txt</code>, <code>qa/extraction-report.md</code></td>
</tr>
<tr>
<td>3</td>
<td>Cleaning</td>
<td><code>text_pipeline_text_cleaning.go</code></td>
<td>Remove contamination, normalize Unicode, produce trustworthy clean text with an auditable report.</td>
<td><code>clean/greek.txt</code>, <code>clean/english.txt</code>, <code>clean/cleaning_results.json</code>, <code>qa/cleaning-report.md</code></td>
</tr>
<tr>
<td>4</td>
<td>Segmentation</td>
<td><code>text_pipeline_segmentation.go</code></td>
<td>Choose the canonical reference system and segment Greek into stable structural units.</td>
<td><code>structured/greek.json</code>, <code>structured/greek.txt</code>, <code>structured/reference-inventory.json</code>, <code>qa/segmentation-report.md</code></td>
</tr>
<tr>
<td>5</td>
<td>Witness Collection</td>
<td><code>text_pipeline_translation_witness.go</code></td>
<td>Gather, normalize, and catalog PD English translations as reference material.</td>
<td><code>witnesses/catalog.json</code>, <code>witnesses/&lt;name&gt;.txt</code>, <code>witnesses/README.md</code>, <code>qa/witness-report.md</code></td>
</tr>
<tr>
<td>6a</td>
<td>Translation Synthesis ★</td>
<td><code>generate_translation.py</code></td>
<td>Generate verse-aligned English from Greek, adversarially reviewed against PD witnesses.</td>
<td><code>versification/chapter_*.json</code></td>
</tr>
<tr>
<td>6b</td>
<td>Versification</td>
<td><code>text_pipeline_versify.go</code></td>
<td>Validate 1:1 alignment of generated translation with Greek; prepare verse-aligned edition for import.</td>
<td><code>versification/english_versified.json</code>, <code>versification/english_edition.json</code>, <code>versification/alignment.json</code>, <code>versification/metadata.json</code>, <code>qa/versification-report.md</code></td>
</tr>
<tr>
<td>7</td>
<td>Transliteration</td>
<td><code>text_pipeline_transliteration.go</code></td>
<td>Generate deterministic romanized transliteration for every Greek token.</td>
<td><code>interlinear/transliteration.json</code>, <code>qa/transliteration-report.md</code></td>
</tr>
<tr>
<td>8</td>
<td>Interlinear/Treebank ★</td>
<td><code>text_pipeline_treebank.go</code>, <code>text_pipeline_alignment.go</code></td>
<td>Import/generate treebank data and build candidate word-level interlinear glosses.</td>
<td><code>interlinear/candidate-alignment.json</code>, <code>interlinear/chapter_*_llm.json</code>, <code>interlinear/morphology-constraints.json</code>, <code>interlinear/treebank-constraints.json</code>, <code>qa/treebank-report.md</code>, <code>qa/alignment-report.md</code></td>
</tr>
<tr>
<td>9</td>
<td>Reader QA</td>
<td><code>text_pipeline_reader_reliability.go</code></td>
<td>Audit whether the text works in the reader — verify display modes, detect silent blanks.</td>
<td><code>qa/reader-reliability-report.md</code>, <code>qa/reliability-report.md</code></td>
</tr>
<tr>
<td>10</td>
<td>Ship</td>
<td><code>text_pipeline_new_text_ship.go</code></td>
<td>Promote approved artifacts into the shipped DB, run import/build, assemble final review pack.</td>
<td><code>qa/final-review-pack.md</code>, editions/segments in <code>texts.db</code></td>
</tr>
</tbody>
</table>
<p>★ = Full LLM judgement required at this step, expensive</p>
<p>Since the stages are sequential, the subagents are spawned for essential context savings. Scripts are also used to make the process deterministic, allowing the LLM to save precious context on making hard-to-script judgement calls, like whether to create a custom translation for versification or use an existing translation that is sufficiently versify-able. The use of scripts also allows us to add tests.</p>
<p><code>text-orchestrator</code> is a versatile skill which can be used with the following commands:</p>
<ul>
<li><code>/text-orchestrator run [work] [--mode add|apply] [--profile &lt;profile&gt;]</code> — Run the full pipeline for a text</li>
<li><code>/text-orchestrator replay [work] --profile &lt;profile&gt;</code> — Replay specific stages on an existing workspace</li>
<li><code>/text-orchestrator resume [work]</code> — Resume an in-progress pipeline from where it stopped</li>
<li><code>/text-orchestrator step [work]</code> — Execute only the next pending stage, then stop</li>
<li><code>/text-orchestrator status [work]</code> — Show pipeline progress and next stage</li>
<li><code>/text-orchestrator abort [work]</code> — Mark the pipeline as blocked and stop</li>
</ul>
<p>The most important of these is perhaps the <code>replay</code> command, which can replay specific stages of the pipeline at will. A dedicated <code>text-replay</code> skill is also provided which can regenerate data from the already-extracted sources. These skills are important since as we iterate on improving the skills and pipeline, we can run <code>replay</code> to improve on the previous results without having to start from scratch.</p>
<p>I’ve tested the current iteration of the full <code>text-orchestrator</code> pipeline on two small text samples so far:</p>
<ul>
<li>The first 5 hexameters of the <em>Iliad</em></li>
<li>Aesop’s Fable <em>The Crab and the Fox</em></li>
</ul>
<p>The interlinear generation run provides cost tracking when run in API mode (faster and better provenance, but much more expensive than a subscription). In the future this will be added to every stage of the pipeline for complete pipeline cost tracking and provenance. Here are the results of the run for the <em>Iliad</em>:</p>
<pre><code class="language-json">{
  &quot;run_id&quot;: &quot;2026-03-24T15:12:41Z-homer-iliad-proem-1-1-5-1&quot;,
  &quot;text&quot;: &quot;homer-iliad-proem-1-1-5&quot;,
  &quot;book&quot;: 1,
  &quot;range&quot;: &quot;book 1&quot;,
  &quot;started_at&quot;: &quot;2026-03-24T15:12:41Z&quot;,
  &quot;completed_at&quot;: &quot;2026-03-24T15:14:15Z&quot;,
  &quot;elapsed_seconds&quot;: 94.0,
  &quot;seconds_per_word&quot;: 3.13,
  &quot;total_latency_ms&quot;: 94006,
  &quot;creator_model&quot;: &quot;claude-sonnet-4-20250514&quot;,
  &quot;skeptic_model&quot;: &quot;claude-sonnet-4-20250514&quot;,
  &quot;referee_model&quot;: &quot;claude-opus-4-20250514&quot;,
  &quot;total_words&quot;: 30,
  &quot;total_batches&quot;: 5,
  &quot;total_input_tokens&quot;: 10187,
  &quot;total_output_tokens&quot;: 4721,
  &quot;total_cost_usd&quot;: 0.248784,
  &quot;cost_per_word_usd&quot;: 0.008293,
  &quot;total_challenges&quot;: 7,
  &quot;challenge_rate&quot;: 0.2333,
  &quot;failed_call_count&quot;: 0,
  &quot;model_breakdown&quot;: {
    &quot;claude-sonnet-4-20250514&quot;: {
      &quot;calls&quot;: 10,
      &quot;input_tokens&quot;: 6978,
      &quot;output_tokens&quot;: 2906,
      &quot;cost_usd&quot;: 0.064524
    },
    &quot;claude-opus-4-20250514&quot;: {
      &quot;calls&quot;: 5,
      &quot;input_tokens&quot;: 3209,
      &quot;output_tokens&quot;: 1815,
      &quot;cost_usd&quot;: 0.18426
    }
  },
  &quot;benchmark_score&quot;: null
}
</code></pre>
<p>The interlinear generation is an expensive process as it uses adversarial subagents (Creator, Skeptic, Referee) to debate the best English translation for each word. This is the most LLM-intensive and longest-running part of the pipeline.</p>
<p>Here are what the results look like after importing <code>texts.db</code> into the reader:</p>
<p><img src="/images/articles/lyceum-pipeline/reader-transliteration-interlinear.png" alt="" /></p>
<p>Here we have <a href="https://lyceum.quest">lyceum.quest</a>’s reader enabled with “Transliteration” and “Interlinear” toggled on. This sectioning created by the <code>segmentation</code> skill enables us to learn the Iliad section-by-section by adding each section to a spaced-repetition, Anki-style deck built-in to the reader, enabling progressive, direct learning by using the most <a href="https://gwern.net/spaced-repetition">scientifically-proven memorization algorithm known</a>.</p>
<p><img src="/images/articles/lyceum-pipeline/reader-srs.png" alt="" /></p>
<h2 id="future-directions">Future Directions<a href="#future-directions" aria-label="Link to heading 'Future Directions'" data-heading-content="Future Directions" class="anchor"></a></h2>
<p>There are several directions we can go from here:</p>
<ul>
<li>
<p>One of the most obvious things to do is benchmark various models for accuracy. What we need here are Greek experts to create holdout data for each skill, run Autoresearch against it, and measure results, and tweak. My proposal would be to run autoresearch against individual skills, then run autoresearch against the text-orchestration pipeline as a whole (though defining binary acceptance criteria for <code>text-orchestration</code> is a bit “fuzzier” than the other skills).</p>
</li>
<li>
<p>As we refine the processes, we can begin to aggregate or generate new types of data. For example, a recent project used AI to successfully transcribe Latin manuscripts never-before uploaded with a 90% accuracy rate. As this improves we will be able to make previously unavailable data available.</p>
</li>
<li>
<p>There’s no reason if we get the proper funding that we couldn’t have agents running overnight. Imagine, for example, a <a href="https://hermes-agent.nousresearch.com/">Hermes</a> agent running <code>text-orchestrator</code> for a predetermined set of texts continuously, to be imported into the reader and evaluated at the leisure of experts. Upon reaching an acceptable confidence interval, we can leave it running for a larger subset of texts and human-verify in bulk.</p>
</li>
<li>
<p>As Dr. Crane mentions in the <a href="https://sites.tufts.edu/perseusupdates/2026/03/10/towards-corpus-based-learning-exercises-with-ancient-greek/">article</a> “Towards corpus-based learning: exercises with Ancient Greek”, we can eventually use AI on the other end of the spectrum: to create custom experiences per-student (or expert!). For example, a user may be able to highlight a piece of text and ask questions about the grammar, or meaning, etc. AI could tailor lessons to each student such that they can immediately engage with the text while focusing on their specific weak-points.</p>
</li>
<li>
<p>Hand-crafted books can be much more information-dense and educationally useful than exist at present. Currently, the best-in-class is widely regarded as the Loeb Library. But these books only put a Greek translation alongside an English translation. For a beginner, this makes it very difficult to dive into the Greek because it’s very hard to know from reading the English where words, phrases, and sentences correspond in the Greek. Thus these books are limited in their utility to inexperienced readers. We currently have the ability to replicate Loeb naively without any AI assistance and indeed the reader at lyceum.quest does this already for multiple translations, allowing the user to even switch between translations side by side as they see fit, which the Loeb Library can’t do. In web form, this isn’t as much of a problem as a reader can choose their level of granularity. But in book form, choosing which information to include per-word becomes a judgement problem. Still, I personally would love to have Greek-English editions with color-coded word-inflections, grammar hints, and interlinear translations. Perhaps readers can even select which information they want their copy of the book to include when ordering it from the website themselves, like a <em>build your perfect book</em> experience tailored to their preferences as they would in the online reader. In short, we can come a long way in making the Greek originals “come alive” by curating a tradeoff between information density and usefulness in a novel print format.</p>
</li>
<li>
<p>Lastly, it should be noted that these skills were generated on a NixOS machine, and thus they’re tied to a lot of <code>nix</code>-specific instructions. We will soon need to generalize these skills to other Linux systems.</p>
</li>
</ul>
<h2 id="bottlenecks">Bottlenecks<a href="#bottlenecks" aria-label="Link to heading 'Bottlenecks'" data-heading-content="Bottlenecks" class="anchor"></a></h2>
<p>Some bottlenecks to the project are:</p>
<ol>
<li><strong>Expertly hand-crafted ground truth and holdout data</strong>. These holdouts should exist across <em>genres</em> (epic, philosophy, history, etc.) and across <em>dialects</em> (Ionic, Attic, Koine, etc.).</li>
<li><strong>API token funding</strong>. Generating these tokens, especially in multi-stage adversarial loops as exist in text-orchestrator, is token-intensive. The tradeoff we’re aiming to make here is high upfront cost for long-term payoff. Rather than suboptimal on-the-fly generation, it is better to find methods which generate a great translation once, with some degree of human-review acceptable.</li>
<li><strong>Skill development and model optimization</strong>. Methods like spawning subagents for each stage, or even adversarial subagents within each stage (i.e. Stage 8), can help improve results. Karpathy’s Autoresearch for automating skill improvement against holdout data will be extremely useful here.</li>
<li><strong>The models themselves</strong>. I’ve only tested this on Claude models (Sonnet/Opus). Benchmarking will likely reveal models optimally suited to different tasks. It should also be noted that the staggering rate of model improvement may render any processes developed to optimize translations irrelevant, and therefore time invested in optimizing the methods should be weighed against acceptable output quality and the knowledge that any methods we develop are likely to have ephemeral use and diminishing returns.</li>
</ol>
<h2 id="conclusion">Conclusion<a href="#conclusion" aria-label="Link to heading 'Conclusion'" data-heading-content="Conclusion" class="anchor"></a></h2>
<p>The rise of LLMs has opened a new door to build better educational tools and more comprehensive databases than have ever existed. The opportunities are particularly salient for niche fields ripe for revival. When building resources for ancient Greek in the age of the internet, we were historically constrained by a shrinking cultural interest in the classics and therefore a smaller pool of experts dedicating resources to creating these tools and databases. Now, however, LLMs have reached a point where custom translations, morphology, and other useful data can be generated with a high degree of accuracy, and it is likely that these tools will continue to improve.</p>
<p>With the careful application of frontier models, quality harnesses, and rigorous human expert review, it is now possible to create a unified, comprehensive database of ancient Greek texts and generate useful data from them optimized for learning and researching.</p>
<p>Hallucination is still a major concern, and more work needs to be done to see if we can reliably produce accurate translations at the word, sentence, and paragraph level.</p>
<p>But the results for the tools as they exist speak for themselves, and skew optimistic.</p>
<p>At this point, I’m bottlenecked by time, money, and Greek expertise. I’ve just been using standard Claude and Codex subscriptions to create and test the orchestrations, but with the right resources and team we can do so much more and at an unprecedented scale.</p>
<p>If you like ancient languages, AI, or are simply excited about preserving and expanding our knowledge of history through digital libraries, please consider <a href="https://discord.gg/mnvAS6WUzz">Joining the Discord</a>, helping <a href="https://github.com/lyceum-quest/texts">audit translations</a>, <a href="https://github.com/sponsors/lyceum-quest">donating</a>, or spreading the word!</p>
<p>What I’ve covered here only scratches the surface of what’s now possible to improve our digital access to Greek and other ancient languages.</p>
<h2 id="links">Links<a href="#links" aria-label="Link to heading 'Links'" data-heading-content="Links" class="anchor"></a></h2>
<ul>
<li><a href="https://github.com/lyceum-quest">Lyceum Github</a></li>
<li><a href="https://github.com/lyceum-quest/orchestrator">Lyceum Orchestrator Skill Pipeline</a></li>
<li><a href="https://github.com/lyceum-quest/texts">Lyceum Orchestrator Outputs</a></li>
<li><a href="https://discord.gg/mnvAS6WUzz">Discord Community</a></li>
<li><a href="https://github.com/sponsors/lyceum-quest">Donate</a></li>
</ul>
]]></content:encoded></item>
<item><title>Introducing Lyceum: A Modern Interface for Reading Ancient Greek Texts</title><link>https://blu.cx/posts/articles/2026-02-23-introducing-lyceum/</link><guid isPermaLink="true">https://blu.cx/posts/articles/2026-02-23-introducing-lyceum/</guid><pubDate>Mon, 23 Feb 2026 12:00:00 +0000</pubDate><description>Lyceum is a modern interface for reading Ancient Greek works in their original language, with interlinear translations, morphology, and spaced-repetition vocabulary learning.</description><content:encoded><![CDATA[<blockquote>
<p><em>I learned ancient Greek as I would have learned a living language.</em></p>
<p>Heinrich Schliemann, the man who rediscovered the lost city of Troy, quoted from <em>The Story of Civilization Vol. II: The Life of Greece</em></p>
</blockquote>
<p>Months ago, after having visited Rome and delving deeper and deeper into ancient history, I decided I couldn’t tolerate translations for the Ancient authors. I felt compelled to read them in their own words. And this meant that I had to learn to read Greek and Latin. I started on that journey with Ancient Greek. I am still a beginner, but I felt strongly that a tool which met my learning needs was missing. So I used Claude to build a website to bridge the gap: <a href="https://lyceum.quest">https://lyceum.quest</a></p>
<p>I began with a top recommendation online: <em>Athenaze</em>. It has given me a great foundation, particularly for grammar, but I quickly became frustrated by a few things:</p>
<ol>
<li>
<p>I was learning Greek through a made-up story about an Athenian farmer. It was engaging, but it wasn’t “real” Ancient Greek and it wasn’t a story they wrote, which quickly became demotivating when things got difficult. This isn’t to fault the book, which is great, but I wanted something that put me in direct contact with the texts to make me feel like I was “unlocking” some ancient secret by learning the language.</p>
</li>
<li>
<p>So much of language learning is memorization, and we have a scientifically proven solution to the long-term memorization problem: <a href="https://gwern.net/spaced-repetition">spaced-repetition</a>, which I wasn’t using, and which is very difficult to implement without the help of software. The strategy I was using of just haphazardly writing down words over and over was inefficient and exhausting.</p>
</li>
<li>
<p>Flipping through glossaries/dictionaries is difficult, and every language-learning book will force you to do this by the nature of that format. Each lesson introduced new words, but if I forgot a previous word somewhere I’d have to find which chapter it was in or flip to the dictionary. Since I didn’t know <em>most</em> words at the start of each chapter because my memorization technique was bad, I found myself spending a lot of time flipping pages. This was further complicated by the fact that each word is <em>inflected</em>, which meant the glossary definitions just had to pick one specific form of that word in the definition. This is confusing to a new student as the dictionary definition often just looks like a completely different word. Eventually, I ended up just typing the words I forgot into an LLM to have it tell me what it was, which was far faster. Double-checking with the glossary I found that the LLM was almost always correct.</p>
</li>
<li>
<p>The answer key to the book wasn’t built in, which seems common practice for Latin/Greek books, likely because they are geared for universities. But for a self-learner, I had the constant sense that I was veering off course with no way to self-correct except to validate my answers via an LLM and online dictionaries. This was also incredibly frustrating.</p>
</li>
</ol>
<p>There are some books that tried to address concern 1, such as <em>Complete Ancient Greek</em> by Gavin Betts and Alan Henry, but they still have to simplify the original for learning purposes and usually do not seem to address 2-4.</p>
<p>I began to search more seriously for ways to just make direct contact with the authors I wanted to read, with some sort of translation to help me engage with the original. After a bit of searching, it seems that the most serious player in this game is the <a href="https://www.loebclassics.com/">Loeb Library</a>, which produces beautiful little books which have the Greek (or Latin) original on one page alongside a quality translation on the other. As far as I can tell, this is one of the only publishers which even <em>publishes</em> works with original Greek, translation or no translation. I enjoyed these and tried reading side by side, but this too was difficult and fraught with error. The word orders, and sometimes even sentence orders, of the translations are different. While this was a step in the right direction as an aid to learning and made the process much more rewarding, it still felt incomplete to me.</p>
<p>Eventually I discovered the interlinear and/or <a href="https://en.wikipedia.org/wiki/James_Hamilton_(language_teacher)">Hamiltonian</a> method of teaching these languages and stumbled across <a href="https://archive.org/details/iliadhomerwitha00clargoog/page/n12/mode/2up">his translation</a> of the <em>Iliad</em>, which felt like a breath of fresh air. An interlinear translation is one where the translation of the word is placed directly below its appearance in the original. One problem with this is that word order in Greek can be completely different than in English, making it harder to comprehend. So Hamilton rearranges the words of the original in order to make them have a more natural flow in English.</p>
<p><img src="/images/articles/introducing-lyceum/hamilton-interlinear.png" alt="Hamilton’s Iliad Interlinear" /></p>
<p>Stumbling on this, it felt like the obvious solution. Here the text came <em>alive</em> to me in my language. I was engaging <em>directly</em> and <em>immediately</em> with my purpose for learning it. Suddenly learning transformed from something tedious to something fun — learning the language <em>through</em> the ancient texts is giving me the sensation of unlocking an ancient secret. Learning through made up stories designed to teach lessons, by contrast, felt like busywork that was obscuring my true goals. It was based on the idea that the <em>vocabulary</em>, not the grammar, was the primary blocker to learning a language. This method was pretty popular throughout the 19th century and was advocated by John Locke and others (as you can read in the intro to Hamilton’s <em>Iliad</em> above), and is still in use today by seminary students and many of the most popular apps for Latin learning (<a href="https://legentibus.com/">Legentibus</a>, for example, uses interlinears). This was a solution to problems 1, 3, and 4 above all in one.</p>
<p>So after learning the basics of declensions and verbs from <em>Athenaze</em>, I switched to solving problem 2: memorization. I painstakingly added Hamilton’s cards by hand, word for word, into Anki. It was slow-going, but I found after a couple days I could actually read a sentence or two of the real <em>Iliad!</em>. And with each day, more.</p>
<p>Then, while listening to <em>The Story of Civilization, Vol. II: The Life of Greece</em> by Will Durant, I heard the insane story of <a href="https://en.wikipedia.org/wiki/Heinrich_Schliemann">Heinrich Schliemann</a>. As an eight-year-old boy, his father read Homer to him, and little Schliemann became so enamored with it that he made a promise to his father that when he grew up he would rediscover the lost city. Around age thirty, a successful merchant, figuring he’d acquired enough wealth to fund his dream, he set out to find Troy. To the disbelief of many a scholar at the time, it appears that he <a href="https://en.wikipedia.org/wiki/Troy#"><em>actually did find it!</em></a>. In a footnote, Durant includes Schliemann’s journal where he describes how he learned the language, which was the final nudge I needed to inspire me to build Lyceum:</p>
<blockquote>
<p>“In order to acquire quickly the Greek vocabulary,” Schliemann writes, “I procured a modern Greek translation of <em>Paul at Virginie</em>, and read it through, comparing every word with its equivalent in the French original. When I had finished this task I knew at least one half the Greek words the book contained; and after repeating the operation I knew them all, or nearly so, without having lost a single minute by being obliged to use a dictionary….Of the Greek grammar I learned only the declensions and the verbs, and never lost my precious time in studying its rules; for as I saw that boys, after being troubled and tormented for eight years and more in school with the tedious rules of grammar, can nevertheless none of them write a letter in ancient Greek without making hundreds of atrocious blunders, I thought the method pursued by the schoolmasters must be altogether wrong….I learned ancient Greek as I would have learned a living language.”</p>
</blockquote>
<p>Everything about this felt right to me: the learning by doing approach, not wasting time (academic methods seem to assume you want to make the study of the language a science, and expect you to learn it formally better than your native tongue. In short, they do <em>not</em> respect your time), the observation on ineffectual tedium, the focus on learning it as a living language. It doesn’t hurt that this is what Troy’s discoverer advocated and that he also happened to know Russian, French, English, Dutch, Spanish, Portuguese, Italian, Swedish, Polish, Latin, Arabic, and his native German by the end of his life.</p>
<p>I now had enough intuition about what I felt was lacking to build the tool I wanted.</p>
<h2 id="building-the-website">Building the Website<a href="#building-the-website" aria-label="Link to heading 'Building the Website'" data-heading-content="Building the Website" class="anchor"></a></h2>
<p>I asked myself if I could build a website with a rich interface for reading and engaging with the original texts that is better than what currently exists. So I started building, with the following goals in mind:</p>
<ol>
<li>
<p>The reading experience should start by at least achieving parity with current resources (Scaife, Loeb). This meant in practice that I needed English translations (multiple preferred, if available). This alone would let us achieve parity with the other viewers.</p>
</li>
<li>
<p>There are databases which exist to serve different purposes, but they have not been combined into a single intuitive interface. Combine freely-available, scholarly data in one place so that it can be maximally beneficial.</p>
</li>
<li>
<p>The site should tightly integrate spaced-repetition features into texts of the reader’s choice as a proven effective method for memorization.</p>
</li>
<li>
<p>Use AI to do useful upfront work that it would take humans a long time to do (e.g. interlinear text generation). Like in Bitcoin, my primary field, proof-of-work is far easier to <em>validate</em> than to <em>generate</em>. As long as we’re up front about the limitations, there’s no reason we can’t use AI to generate initial interlinear texts which are then refined. I will go into more detail about my thoughts on using LLMs to generate interlinear translations and how I did it here in a subsequent post, but suffice it to say, they can achieve better results than you may expect.</p>
</li>
</ol>
<p>I was aiming for something like a cross between the <a href="https://scaife.perseus.org/">Scaife Reader</a> (which is difficult to use and more overtly academic) and <a href="https://legentibus.com/">Legentibus</a> (which has proven very successful, but is more paywall-ed, more focused, and does Latin instead of Greek).</p>
<p>Using <a href="https://claude.ai/">Claude</a> to build the site I was able to achieve all of these goals for at least a couple texts each, validating the concept. The one exception is that I need some mechanism and/or scholarly review to validate and correct mistakes in the interlinear texts. Thankfully, these can easily be compared in-site with the scholarly sourced definitions from LSJ, Perseus, and Logeion short definitions at any time for the time being.</p>
<h2 id="tour-of-the-site">Tour of the Site<a href="#tour-of-the-site" aria-label="Link to heading 'Tour of the Site'" data-heading-content="Tour of the Site" class="anchor"></a></h2>
<p>The homepage gives you an overview of the primary features.</p>
<p><img src="/images/articles/introducing-lyceum/homepage.png" alt="Lyceum homepage" /></p>
<p>Clicking “Browse All Texts” will take you to the <code>/texts</code> page, where you can search for texts by author or title, similar to Scaife Viewer:</p>
<p><img src="/images/articles/introducing-lyceum/browse.png" alt="Browse page" /></p>
<p>The corpus spans 373 authors and 1,837 works across all sources. Many of these will only contain the original Greek text with no translation for the time being, although I would love to fix that over time. The five texts at the top (Aesop’s Fables, Gospel of John, Odyssey, Iliad, and Meditations) have been given special attention. These texts have interlinear translations and transliterations, and were what was primarily used for building and testing the site. If we click on one, we can see the reader view:</p>
<p><img src="/images/articles/introducing-lyceum/reader.png" alt="Reader view" /></p>
<p>There’s a lot to unpack here. On the “Display Options” card, we have checkboxes which we can use to add morphology information (sourced from Perseus) to each word to help with our understanding of the grammar. For example, there is a “Color by Part of Speech”, “Color Inflection”, and “Show Inflection” option. For each word, there is a popup you can click on to see that word’s definition from various sources (Logeion, Perseus, and LSJ), and grammar. You can also see multiple translations side by side with the original Greek when they are available. Any of these options can be combined to your reading taste. As far as I’m aware, there is currently no Ancient Greek resource which combines dictionary definitions, grammar/morphology, and side by side translations for free.</p>
<p><img src="/images/articles/introducing-lyceum/morphology-options.png" alt="Morphology and display options" /></p>
<p><img src="/images/articles/introducing-lyceum/side-by-side.png" alt="Side-by-side translation" /></p>
<p><img src="/images/articles/introducing-lyceum/word-popup.png" alt="Word popup" /></p>
<p>For paid users, the page gets more interesting and useful. Two new checkboxes unlock in “Display Options”: “Show Interlinear” and “Show Transliteration” both generated by Claude. Transliterations are useful to show the Greek characters in Latin text, and thus help beginners to memorize the pronunciation of the Greek characters:</p>
<p><img src="/images/articles/introducing-lyceum/transliteration.png" alt="Transliteration view" /></p>
<p>Interlinear texts, as discussed earlier, give you direct word-by-word translations <em>in context</em>, so that you are no longer left guessing which of the many Perseus/LSJ definitions apply in the context of the book you are reading.</p>
<p><img src="/images/articles/introducing-lyceum/interlinear.png" alt="Interlinear view" /></p>
<p>Because these are LLM-generated and not scholarly-reviewed translations, you should compare them against their Logeion/Perseus/LSJ counterparts to be certain, but I have found they are usually accurate for these curated texts. You can compare them easily via the popup:</p>
<p><img src="/images/articles/introducing-lyceum/popup-comparison.png" alt="Popup comparison with interlinear" /></p>
<p>I am working on ways to better ensure/enhance the accuracy of these AI-generated interlinears, but in the meantime, this built-in cross-validation with the official scholarly sources should suffice.</p>
<p>These interlinear translations, combined with official translations and scholarly-source cross checking, make for a very powerful interface for reading.</p>
<p>There is one other feature that I’m particularly fond of: Anki-style spaced-repetition. Paid users can select a section from any text and add those words to a deck, where they can later practice memorizing them.</p>
<p><img src="/images/articles/introducing-lyceum/srs-deck.png" alt="SRS deck selection" /></p>
<p>Then, clicking on the “Study” tab will take you to:</p>
<p><img src="/images/articles/introducing-lyceum/study-tab.png" alt="Study tab" /></p>
<p>Now, you will be presented with cards like this:</p>
<p>Front:</p>
<p><img src="/images/articles/introducing-lyceum/card-front.png" alt="Card front" /></p>
<p>Back:</p>
<p><img src="/images/articles/introducing-lyceum/card-back.png" alt="Card back" /></p>
<p>You can even click “View in context” to have it highlight occurrences of that word in the text you added it from!</p>
<p><img src="/images/articles/introducing-lyceum/view-in-context.png" alt="View word in context" /></p>
<p>Finally, we have a dashboard with a few charts to track your learning progress:</p>
<p><img src="/images/articles/introducing-lyceum/dashboard.png" alt="Dashboard" /></p>
<h1 id="conclusion">Conclusion<a href="#conclusion" aria-label="Link to heading 'Conclusion'" data-heading-content="Conclusion" class="anchor"></a></h1>
<p>Lyceum is a rich modern reader for reading Ancient Greek texts with a novel combination of features to let you access the Greek authors of Antiquity in their native tongue. It is meant as a helpful, intuitive, and engaging resource to other means of learning Greek. It is under active development and new features and fixes will be added regularly.</p>
<p>Ultimately I’m building this because I want it to exist. I am the first customer. I now use the spaced-repetition and interlinear features every day and am learning the words from the <em>Iliad</em>, <em>Odyssey</em> and Aesop’s <em>Fables</em> using it. Every day I can read a little more, and I am genuinely excited by my progress. I will continue to add features I (and others) think are useful, I’m sure there are plenty of mistakes to correct and improvements to be made that I’m missing.</p>
<p>If you want to have a voice in Lyceum’s development or provide feedback to the project please join the <a href="https://discord.gg/mnvAS6WUzz">Discord</a> or send an email to <a href="mailto:support@lyceum.quest">support@lyceum.quest</a> with any suggestions you have or mistakes you find.</p>
<p>Future goals include:</p>
<ul>
<li>Fix any missing or incorrect dictionary definitions, with a user-driven feedback mechanism.</li>
<li>Add many more interlinear translations and transliterations especially for popular texts.</li>
<li>A feedback mechanism for evaluating/correcting LLM-generated interlinear translations/transliterations.</li>
<li>Full English/other language translations for a wider variety of texts (many currently only have the Greek).</li>
<li>Voice/Audio enabled reads (like Legentibus) for pronunciation.</li>
<li>Make texts downloadable in nice readable formats. A fun thing might even be to allow PDF generation in whatever display format options you’ve selected for offline reading.</li>
<li>Some day, the above could evolve into creating books/printouts with rich morphological information per word for certain texts (for example, a book could include interlinear translations, per-word grammar information). No publisher seems to do this as of now.</li>
</ul>
<h3 id="gratitude-for-previous-work">Gratitude for Previous Work<a href="#gratitude-for-previous-work" aria-label="Link to heading 'Gratitude for Previous Work'" data-heading-content="Gratitude for Previous Work" class="anchor"></a></h3>
<p>Ross Scaife for laying the groundwork which led to the <a href="https://scaife.perseus.org/">Scaife Viewer</a>, which was a huge inspiration. The <a href="https://scrollprize.org/">Vesuvius Challenge</a>, which stirred my imagination with an excitement about rediscovering the past, the Loeb Library for their excellent books, Anki for being the only widely-adopted spaced-repetition software, Athenaze for being the book I started learning Greek with, and Legentibus for setting the gold standard for modern Latin learning and being a great resource to take inspiration from.</p>
<h3 id="data-sources">Data Sources:<a href="#data-sources" aria-label="Link to heading 'Data Sources:'" data-heading-content="Data Sources:" class="anchor"></a></h3>
<h4 id="texts">Texts<a href="#texts" aria-label="Link to heading 'Texts'" data-heading-content="Texts" class="anchor"></a></h4>
<ul>
<li><a href="https://github.com/PerseusDL/canonical-greekLit">Perseus Digital Library</a> Greek texts and English translations from canonical-greekLit repository. 50+ authors, 631 works, from Homer through the Church Fathers.</li>
<li><a href="https://figshare.com/articles/dataset/The_Diorisis_Ancient_Greek_Corpus/6187256">Diorisis Ancient Greek Corpus</a>: 820 pre-lemmatized XML texts with word-level morphology, POS tagging, and sentence boundaries. Used to aid in generating contextual meanings.</li>
<li><a href="https://github.com/OpenGreekAndLatin/First1KGreek">First1K Greek Project</a>: Additional Greek texts from the Open Greek and Latin project at the University of Leipzig.</li>
<li><a href="http://www.mythfolklore.net/aesopica/">Chambry Aesopica</a>: Chambry’s critical edition of Aesop’s Fables with Perry numbering, multiple recensions, and scholarly apparatus.</li>
<li><a href="https://www.gutenberg.org/ebooks/21">Townsend Aesop Translation</a>: George Fyler Townsend’s 1867 English translation of Aesop’s Fables, used for parallel reading.</li>
</ul>
<h4 id="morphology--glosses">Morphology &amp; Glosses<a href="#morphology--glosses" aria-label="Link to heading 'Morphology &amp; Glosses'" data-heading-content="Morphology &amp; Glosses" class="anchor"></a></h4>
<ul>
<li>
<p><a href="https://github.com/PerseusDL/treebank_data">Perseus Ancient Greek Dependency Treebank</a> Syntactic annotations (dependency trees) and glosses for select texts including Aesop, Homer, and Attic prose. Used for word-level alignment with translations.</p>
</li>
<li>
<p><a href="https://github.com/pjheslin/diogenes">Diogenes</a>: Morphological analyses for 400K+ Greek word forms. Each entry maps an inflected form to its lemma, part of speech, and full grammatical analysis (case, number, gender, tense, voice, mood, person).</p>
</li>
</ul>
<h4 id="definitions">Definitions<a href="#definitions" aria-label="Link to heading 'Definitions'" data-heading-content="Definitions" class="anchor"></a></h4>
<ul>
<li><a href="https://lsj.gr/">LSJ</a>: 116,000+ dictionary entries from the definitive Ancient Greek lexicon (9th edition, 1940). Full scholarly definitions with citations, etymology, and usage notes. The “gold standard” comprehensive Greek definitions.</li>
</ul>
<h4 id="claude-ai">Claude AI<a href="#claude-ai" aria-label="Link to heading 'Claude AI'" data-heading-content="Claude AI" class="anchor"></a></h4>
<ul>
<li><strong>Content</strong>: AI-generated contextual glosses that analyze word meaning within specific passages. Used to disambiguate words with multiple meanings (e.g., “λόγος” as “word” vs “argument” vs “reason” depending on context).</li>
</ul>
]]></content:encoded></item>
<item><title>Stack Programs Like Legos with Nix!</title><link>https://blu.cx/posts/blog/2025-11-21-nix-programs-as-legos/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2025-11-21-nix-programs-as-legos/</guid><pubDate>Fri, 21 Nov 2025 12:00:00 +0000</pubDate><description>Learn how to stitch programs together reprodicibly with Nix</description><content:encoded><![CDATA[<p>Nix was made to solve the <em>software deployment problem</em>, concisely defined by creator Eelco Dolstra thus:</p>
<blockquote>
<p>[The software deployment problem] is about getting computer programs from one machine to another—and having
them still work when they get there.</p>
<p>The Purely Functional Software Deployment Model, Eelco Dolstra</p>
</blockquote>
<p>Nix allows you to setup software on your computer in such a way that your setup is <em>reproducible</em>, meaning your setup on machine A can be <em>exactly</em> the same as your setup on machine B – as long as you have Nix.</p>
<p>To most people, learning Nix is a pain, due to the new concepts, sparse and outdated documentation, and community infighting.</p>
<p>But I think Nix can make using computers <em>fun</em> and <em>powerful</em> and <em>less painful</em>, once you learn how to handle its’ edges.</p>
<p>One fun way we can use Nix is to stitch together programs like Legos and have them interact with each other in a reproducible way. Let’s create an example!</p>
<p>First <a href="https://nixos.org/download/">install Nix</a> if you haven’t.</p>
<p>Say we have a fun little <code>bash</code> script like this:</p>
<pre><code class="language-sh">#!/usr/bin/env bash

# filename: pokefortune.sh

message=&quot;${1:-}&quot;
pokemon=&quot;${2:-slowking}&quot;

# Silence Perl locale warnings.
export LC_ALL=C

# Generate a fortune if user did not pass a message.
if [[ -z &quot;$message&quot; ]]; then
  message=$(fortune)
fi

echo &quot;$message&quot; | pokemonsay -p &quot;$pokemon&quot; -n
</code></pre>
<p>It optionally takes in a message and a <a href="https://pokemondb.net/pokedex/all">Pokédex</a> number, and prints out that Pokémon and message using the <code>pokemonsay</code> program. If either the message or the Pokemon aren’t specified, it uses a default Pokemon and the program <code>fortune</code> to generate the message.</p>
<p>Save this in a file <code>pokefortune.sh</code>, make it executable, then run it:</p>
<pre><code class="language-sh"># Make the bash script executable
chmod +x pokefortune.sh

./pokefortune.sh
</code></pre>
<p>If you’re like most people, you probably don’t have <code>pokemonsay</code> or <code>fortune</code> installed on your system, so you’ll likely see something like this:</p>
<pre><code class="language-sh">./pokefortune.sh: line 6: fortune: command not found
./pokefortune.sh: line 7: pokemonsay: command not found
</code></pre>
<p>Therefore this script, which works on my system because I have these programs installed, isn’t <em>reproducible</em> on your system. Let’s create a Nix <em>derivation</em> to make it so. Create a file called <code>pokefortune.nix</code> and copy the following:</p>
<pre><code class="language-nix"># filename: pokefortune.nix

{
  pkgs ? import (fetchTarball &quot;https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz&quot;) { },
}:
pkgs.writeShellScriptBin &quot;pokefortune&quot; ''

message=&quot;''${1:-}&quot;
pokemon=&quot;''${2:-slowking}&quot;

# Silence Perl locale warnings.
export LC_ALL=C

# Generate a fortune if user did not pass a message.
if [[ -z &quot;$message&quot; ]]; then
  message=''$(${pkgs.fortune}/bin/fortune)
fi

echo $message | ${pkgs.pokemonsay}/bin/pokemonsay -p &quot;$pokemon&quot; -n
''
</code></pre>
<p>Now run:</p>
<pre><code class="language-sh">nix-build pokefortune.nix
</code></pre>
<p>This may take awhile, especially if this is your first time running Nix. Let’s look at the output:</p>
<pre><code class="language-sh">nix-build src/scripts/nix/pokefortune.nix
unpacking 'https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz' into the Git cache...
this derivation will be built:
  /nix/store/vg4zmghqcnhjbs8kqhx04xixvm36d3ik-pokefortune.drv
these 9 paths will be fetched (2.65 MiB download, 26.91 MiB unpacked):
  /nix/store/0vdf5mpd762bw53rgl5nkmhvzq8n4m0d-file-5.45
  /nix/store/77cdqhqprqbciyhzsnzmsk7azbk2xv6r-fortune-mod-3.20.0
  /nix/store/d0i8idmbb4jji9ml01xsqgykrbvm7dss-gnu-config-2024-01-01
  /nix/store/0554jm1l1qw1pcfqsliw91hnifn11w8m-gnumake-4.4.1
  /nix/store/2wp235bg03gykpixd9v2nyxp08w8xq8a-patchelf-0.15.0
  /nix/store/c5x32idp600dklz9n25q38lk78j5vwxb-pokemonsay-1.0.0
  /nix/store/pba53n11na87fs4c20mp8yg4j7qx1by2-recode-3.7.14
  /nix/store/zix67r268ihi4c362zw7c0989z12jmy7-stdenv-linux
  /nix/store/2329271b42wh6b6yhl7jmjyi0cs4428b-update-autotools-gnu-config-scripts-hook
copying path '/nix/store/d0i8idmbb4jji9ml01xsqgykrbvm7dss-gnu-config-2024-01-01' from 'https://cache.nixos.org'...
copying path '/nix/store/c5x32idp600dklz9n25q38lk78j5vwxb-pokemonsay-1.0.0' from 'https://cache.nixos.org'...
copying path '/nix/store/0vdf5mpd762bw53rgl5nkmhvzq8n4m0d-file-5.45' from 'https://cache.nixos.org'...
copying path '/nix/store/0554jm1l1qw1pcfqsliw91hnifn11w8m-gnumake-4.4.1' from 'https://cache.nixos.org'...
copying path '/nix/store/2wp235bg03gykpixd9v2nyxp08w8xq8a-patchelf-0.15.0' from 'https://cache.nixos.org'...
copying path '/nix/store/pba53n11na87fs4c20mp8yg4j7qx1by2-recode-3.7.14' from 'https://cache.nixos.org'...
copying path '/nix/store/2329271b42wh6b6yhl7jmjyi0cs4428b-update-autotools-gnu-config-scripts-hook' from 'https://cache.nixos.org'...
copying path '/nix/store/zix67r268ihi4c362zw7c0989z12jmy7-stdenv-linux' from 'https://cache.nixos.org'...
copying path '/nix/store/77cdqhqprqbciyhzsnzmsk7azbk2xv6r-fortune-mod-3.20.0' from 'https://cache.nixos.org'...
building '/nix/store/vg4zmghqcnhjbs8kqhx04xixvm36d3ik-pokefortune.drv'...
/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune
</code></pre>
<p>Nix fetched the packages we specified in our <code>.nix</code> file: <code>pokemonsay</code>, <code>fortune</code>, and some we didn’t specify: such as <code>file</code>, <code>patchelf</code>, and <code>recode</code>, which one or both of the other two packages depends on. Then it copied them locally, built a binary, and placed it at <code>/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune/bin</code>. We can confirm this by running it:</p>
<pre><code class="language-sh">/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune/bin
</code></pre>
<p>You should see something like this:</p>
<img src="/images/blog/nix-programs-as-legos/img_2_pokefortune.webp" alt="pokefortune result" width="800" height="1240" loading="lazy">
<p>To be reproducible, Nix ensures that it knows about every single dependency needed to create a package at all times. Because of that, you can do fun things like this:</p>
<pre><code class="language-sh"># Quickly enter a shell with the dependencies to create and display images.
nix-shell -p graphviz chafa

# Query the derivation's dependency graph, create a .png from it and display it:
    nix-store --query --graph $(nix-build pokefortune.nix) | \
    dot -Tpng -o pokefortune-dependency-graph.png &amp;&amp; chafa pokefortune-dependency-graph.png
</code></pre>
<p>Resulting in a view of every single dependency (the “closure”) that our <code>pokefortune</code> program requires:</p>
<!-- <div style="text-align: center;"> -->
<img src="/images/blog/nix-programs-as-legos/img_3_pokefortune_dependency_graph.webp" alt="pokefortune dependency graph" width="800" height="634" loading="lazy">
<!--   <img src="assets/introduction/intro_img_3_pokefortune_dependency_graph.png" alt="pokefortune result"  height="600"> -->
<!-- </div> -->
<p>How cool is that! We can see the whole dependency tree for anything packaged with Nix!</p>
<p>You have just done something very powerful with Nix: You’ve created a reproducible derivation that anyone who has the package manager installed on their system can use, which they couldn’t before.</p>
<p>This is just a taste of what Nix can do, but I hope that the potential is clear. As Farid Zakaria says in his blog post “Learn Nix the Fun Way”:</p>
<blockquote>
<p>Hopefully, seeing the fun things you can do with Nix might inspire you to push through the hard parts.</p>
<p>There is a golden pot 💰 at the end of this rainbow 🌈 awaiting you.</p>
<ul>
<li><a href="https://fzakaria.com/2024/07/05/learn-nix-the-fun-way">https://fzakaria.com/2024/07/05/learn-nix-the-fun-way</a></li>
</ul>
</blockquote>
<hr />
<p><em>This article is directly inspired by Farid Zakaria’s blog post <a href="https://fzakaria.com/2024/07/05/learn-nix-the-fun-way"><strong>Learn Nix the Fun Way</strong></a>. Check out his excellent blog <a href="https://fzakaria.com/">here</a>.</em></p>
<p><em>Special thanks to <a href="https://github.com/russweas">Russell Weas</a> and <a href="https://veracius.dev">veracius</a> for their input to this article and code</em></p>
<hr />
<p>References:</p>
<ul>
<li><a href="https://edolstra.github.io/pubs/phd-thesis.pdf">https://edolstra.github.io/pubs/phd-thesis.pdf</a></li>
<li><a href="https://web.archive.org/web/20171017151526/http://aptitude.alioth.debian.org/doc/en/pr01s02.html">https://web.archive.org/web/20171017151526/http://aptitude.alioth.debian.org/doc/en/pr01s02.html</a></li>
<li><a href="https://fzakaria.com/2024/07/05/learn-nix-the-fun-way">https://fzakaria.com/2024/07/05/learn-nix-the-fun-way</a></li>
<li><a href="https://zero-to-nix.com/start/nix-run/">https://zero-to-nix.com/start/nix-run/</a></li>
</ul>
]]></content:encoded></item>
<item><title>Fear to Attempt</title><link>https://blu.cx/posts/articles/2025-06-20-fear-to-attempt/</link><guid isPermaLink="true">https://blu.cx/posts/articles/2025-06-20-fear-to-attempt/</guid><pubDate>Fri, 20 Jun 2025 12:00:00 +0000</pubDate><description>Our doubts are traitors and make us lose the good we oft might win by fearing to attempt</description><content:encoded><![CDATA[<blockquote>
<p>Our doubts are traitors and make us lose the good we oft might win by fearing to attempt</p>
<ul>
<li>Shakespeare, <em>Measure for Measure</em></li>
</ul>
</blockquote>
<p>We <em>need</em> you to dream; then pursue those dreams with vigor. You are a human being. You are a machine with hopes and dreams; a calculator who looks at cold, mute, void, dead nature, and somehow sees God and meaning despite yourself. You are a <a href="http://famouspoetsandpoems.com/poets/russell_edson/poems/15012">teetering bulb of dread and dream</a>. You cannot help but see the world in infinite terms. Despite your best efforts, you believe in right and wrong, black and white, good and evil. Nihilism is your greatest lie to yourself, and you feel the nausea it induces when it <a href="https://www.youtube.com/watch?v=T5xuzSjl8eU">crawls into your heart and dies</a>, because it is <em>unnatural</em> and anti-life. Are you truly so bored with the ease and comfort that the vast array of the past has struggled, and suffered, and died to provide you with, that you have to cut yourself to remember you can feel?</p>
<blockquote>
<p>Shall I be carried to the skies,</p>
<p>On flowery beds of ease,</p>
<p>While others fought to win the prize,</p>
<p>And sailed through bloody seas?</p>
<p>Laura Ingalls Wilder, <em>Little House in the Big Woods</em>, p. 96</p>
</blockquote>
<hr />
<p><em>Use this life</em>. Determine what is good, or at least, what is not evil, then <em>act</em> as a humble servant of the former, and in direct repudiation of the latter. Your heart will thank you for it, even if the world never does. It is a cooling salve for your depression and melancholy. Though at the cost of greater anxiety. But anxiety is excitement also, and adventurousness, and <em>worth the struggle</em>.</p>
<blockquote>
<p>Far better it is to dare mighty things, to win glorious triumphs, even though checkered by failure, than to take rank with those poor spirits who neither enjoy much nor suffer much, because they live in the gray twilight that knows not victory nor defeat.</p>
<p>Theodore Roosevelt, <a href="https://voicesofdemocracy.umd.edu/roosevelt-strenuous-life-1899-speech-text"><em>Strenuous Life</em></a></p>
</blockquote>
<p>You are hopelessly finite; but your actions can and do graze infinity. You can make a <a href="https://en.wikipedia.org/wiki/Steve_Jobs_(book)">dent in the Universe</a>. Some of us little people have. And they who have are frightfully similar to you.</p>
<hr />
<p>How can any honest person look around at their world and not see that it is a <a href="https://x.com/collision/status/1529452415346302976">museum of passion projects</a>? A handful of souls through the brief span of history shake off their multitude of excuses, doubts, fears, false friends, oppressive family - shed the dying skin - to chase these impossible dreams, and oftener than we should expect, achieve them. Even in the infested rivers of Calcutta, what is all that trash polluting the river? It is the unmaintained dreams of the past piling on and on into a neglectful present. Plastics and juice boxes and old toys are miracles if you stop them from coalescing into growing piles of grime.</p>
<p>Work passionately to some purpose. Most of those who regret working hard worked hard as slaves to some lower passion like money or cheap status. Those who conduct their work in service of Eternity have a cleaner conscience. Did we hear honest words of regret from Socrates? From Cicero or Cato? From Adams, Hamilton, Jefferson, or Washington? Only in their moments of weakness and fear when their tasks ahead bore relentlessly down on them. But, determining to answer the Call of Duty, they steeled themselves and pressed forward, leaving behind a trail of proud memories which sustained genuine pride in the shade of life.</p>
<hr />
<blockquote>
<p>My depression is the most faithful mistress I have known — no wonder, then, that I return the love.</p>
<p>Soren Kierkegaard, <em>Either/Or: A Fragment of Life</em></p>
</blockquote>
<p>Work toward depression and nothingness, and you will achieve it.</p>
<p>Weekends we spend alone, absorbing Netflix, Youtube, ads. Burning away the candle on video games, where we pretend we are heroes, when <em>real</em> knighthood beckons to the brave. Doomscrolling our ten-second videos of AI-generated sequences of ever-more unintelligible noises and images, then we complain that we can’t afford a house. Watch porn, then complain there are no good women. Work harder and smarter on justifying our inaction, and find enemies to our supposed purposes everywhere. We will never find an enemy sufficient to outsource our self-loathing. In this way we will never be cured of it. We are not taking our problems seriously by failing to recognize ourselves as their root cause. And who can we expect to take us seriously if not ourselves?</p>
<p>You are not jealous of the past. You have more than them if you cared to look. You are jealous of your own better nature which you glimpse buried within yourself manifesting forthrightly in others. Their lived virtue is recognizable to you as your own dormant morality.</p>
<p>You are not jealous of the past, except their Hope for the future. Reclaim the Hope, and you’ll quickly see the thousand conveniences and asymmetric advantages afforded to you not given them.</p>
<p>That hope will be reclaimed with the youthful vigor of active optimism; never by a passive pessimism.</p>
<hr />
<blockquote>
<p>“Friends,” said he, “the taxes are indeed very heavy, and if those laid on by the government were the only ones we had to pay, we might more easily discharge them; but we have many others, and much more grievous to some of us. We are taxed twice as much by our idleness, three times as much by our pride, and four times as much by our folly; and from these taxes the commissioners cannot ease or deliver us by allowing an abatement. However, let us hearken to good advice, and something may be done for us; <em>God helps them that help themselves</em>.</p>
<p>Benjamin Franklin, <em>The Way to Wealth</em></p>
</blockquote>
<p>You have the <em>internet</em> friend! And you’re using it to watch cat videos and porn. What’s wrong with you? How many philosophers through the millenia have <em>dreamed</em> of this moment, where anyone and everyone had access to the voluminous fount of human knowledge! So many of them thought this day would <a href="https://www.theintrinsicperspective.com/p/why-we-stopped-making-einsteins">unleash the golden age</a> we’ve finally been waiting for. You can, for next to nothing, read the minds of the greatest thinkers, inventors, and scientists who’ve ever lived. But instead, we discovered that we are easily hackable, we will still use the evil software of companies we know are ill-designing against us, or at best do not care. We sell our souls in as near literal a sense as anyone could have possibly imagined, for a little convenience, a little fun, a little dopamine, and say that it is impractical to live without them, and give up. If we use this great tool for knowledge at all we’re most likely to use it for a petty win in a debate not worth having. For the most part, we’ve shortened our attention spans and become addicted further still to our baser instincts. This is not Utopia. This is a Brave New World, and we, for the most part, feel oppressed by a future we wrongly think we have little power to avoid.</p>
<hr />
<blockquote>
<p>The best way to predict the future is to invent it.</p>
<p><a href="https://www.youtube.com/watch?v=dTPI6wh-Lr0">Alan Kay</a></p>
</blockquote>
<p>Knowledge and wisdom are now cheap, and as such, unvalued. Even our educated don’t know much today, because knowledge and understanding are no longer the point. The knowledge of our college graduates is the kind of knowledge any true thinker hates. Commoditized and packaged so they can “get a job” and “have a career”, and rarely develop the mark of a truth-seeker:</p>
<blockquote>
<p>So many people today - and even professional scientists - seem to me like somebody who has seen thousands of trees but has never seen a forest. A knowledge of the historic and philosophical background gives that kind of independence from prejudices of his generation from which most scientists are suffering. This independence created by philosophical insight is - in my opinion - the mark of distinction between a mere artisan or specialist and a real seeker after truth.</p>
<p>Albert Einstein, <a href="https://www3.nd.edu/~dhoward1/AEquotes.pdf">Correspondance to Robert Thorton in 1944</a></p>
</blockquote>
<hr />
<p>You can adopt the whole human heritage. Do not be bound by superficial alliances. Be bound by vision and destiny. The human race is your anchor. Any giant’s shoulders are shoulders to stand on if they are good and true. I admire Socrates and Pericles, though I am not Greek. Cicero and the Antonines, though I am not Roman. Confucius, Nietzsche, Kant, Petrarch, Da Vinci, what do I care for their allegiances? They painted their minds to us, and now they are mine.</p>
<p>Even within these flawed humans, or any flawed human, we can take the good and discard the bad. No human is really one human. We are instead a hash of blurry archetypes, oscillatingly present or absent from person to person, and within the same person at different times, places, and circumstances.</p>
<blockquote>
<p>Every man I meet is in some way my superior, and in that, I can learn of him.</p>
<p>Emerson, <em>Think</em>, Vol. 4-5 (1938), p. 32</p>
</blockquote>
<hr />
<p>Is the state of society not to your liking? <em>Good</em>. <em>Fix it!</em>. What else do you have to do? Complain? Settle? You are not allowed to be angry and <em>do nothing</em>. Society will sink under the festering weight of this accumulated hypocrisy. I do not intend to imply this is easy; swimming against the tide never is. Far from it. Only that it is worth it. It is <em>necessary</em>.</p>
<p>Do you see problems? Well I see an army of problem-solvers; an array of latent potential waiting to unleash its energy, and hold back the tide of societal decay. Your mission in life is waiting for you. What excites you? If you have nothing that excites you, then what bothers you? What do you wish was, that isn’t now? There is your destiny. Only a liar can’t think of a worthy pursuit that will provide him a lifetime of work. Worthy pursuits and things worth doing are ever-present. You are far more limited than they are scarce. If you cultivate reasons enough for Being, imbibe enough meaning and purpose in your daily diet, a panoply of destinies scream and beg for you to take them on.</p>
<p>I’ve heard the cry from other people my age that “everything’s been done”, and all this noise about artificial intelligence isn’t helping young people dream about brighter futures. It seems to be feeding their already weak grasp on the meaning of life. They constantly ask themselves what they will have to do, not realizing the opportunity lying latent in every problem. Every where I look I see little issues, and have reached the point that I lament the fact that I don’t have the time or energy to work on all of them myself, as they would distract too much from an even greater or more exciting mission.</p>
<p>Do you feel that the problems are too big? Start small. Rome wasn’t built in a day. Do you feel unmatched by the impenetrable weight of what you’re fighting? Fight it anyway, and become a salient force to be reckoned with along the way, growing strong from each victory, and stronger still from each defeat. A scattered array of untrained militia defeated the might of the British Empire at its height, with muskets they used to hunt birds and deer. Can you honestly decry that your petty worries are anywhere near <em>that</em> insurmountable? You have been living through the <em>Pax Americana</em>, and you and I spent the whole of it complaining, and if we’re not extremely careful we will be reminded what real pain and suffering is.</p>
<hr />
<blockquote>
<p>I think that there is only one way to science - or to philosophy, for that matter: to meet a problem, to see its beauty and fall in love with it; to get married to it and to live with it happily, till death do ye part - unless you should meet another and even more fascinating problem or unless, indeed, you should obtain a solution. But even if you do obtain a solution, you may then discover, to your delight, the existence of a whole family of enchanting, though perhaps difficult, problem children, for whose welfare you may work, with a purpose, to the end of your days.</p>
<p>Karl Popper, <em>Realism and the Aim of Science</em></p>
</blockquote>
<p>So long as there are problems, this world needs your hidden and unique offerings. So long as there are problems, you can be the solution. Do not deprive us of your secret skills. Meet your destiny head-on. Cast your gaze upward toward the Lights of the Firmament, and dream.</p>
]]></content:encoded></item>
<item><title>&lt;bitcoin-qr/&gt; - A Zero-Dependency Web Component for Stylish BIP-21 Payments</title><link>https://blu.cx/posts/blog/2025-02-21-bitcoin-qr/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2025-02-21-bitcoin-qr/</guid><pubDate>Fri, 21 Feb 2025 12:00:00 +0000</pubDate><description>A QR code web component for Bitcoin on-chain, Lightning, and unified BIP-21 payments</description><content:encoded><![CDATA[<span class="flex justify-center w-[100px]">
<img src="/images/blog/bitcoin-qr/qr.webp" alt="Bitcoin QR Example" width="594" height="596" loading="lazy">
</span>
<p>When developing a Bitcoin payment flow, there are multiple ways a user can expect to be able to pay. They might want to pay an on-chain address or Lightning invoice, they may be scanning a QR Code from their phone, copy/pasting from a wallet, or using a <a href="https://www.webln.guide/">WebLN</a> browser extension. Creating an intuitive interface that also captures all the possible ways a user can pay is one of the fundamental UX challenges of developing an application that can receive payments in Bitcoin.</p>
<p>This flexibility opens up many <a href="https://blu.cx/articles/bitcoin/micropayments">exciting use cases</a>, but often comes at the cost of being able to easily develop a smooth experience for the user. The greater the developer’s cognitive load, the more difficult it is to create intuitive UX.</p>
<p>This project aims to provide everything needed to allow standard Bitcoin &amp; Lightning Network payments out of the box. It handles creating the proper <a href="https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki">URIs</a> from just an address or invoice, and favors creating unified URIs whenever possible. Styles are highly customizable and images can be embedded. It also includes polling functionality – a callback can be passed as a property of the element to periodically check for payment.</p>
<p>Check it out on <a href="https://github.com/thebrandonlucas/bitcoin-qr">Github</a> and feel free to play around with the options and create your own <a href="https://bitcoin-qr.blu.cx">here</a>.</p>
<p>In addition, because it’s a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">web component</a>, you should just be able to drop it into any framework and have it work, whether it’s pure HTML, Sveltekit, React, making it very versatile. Examples are included in the Github repo.</p>
<p>Here’s a quick demo of how to use it in Sveltekit:</p>
<p>Install:</p>
<pre><code class="language-sh">yarn add bitcoin-qr
</code></pre>
<p>Then:</p>
<pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
  import { defineCustomElements } from 'bitcoin-qr/loader';

  defineCustomElements();
&lt;/script&gt;

&lt;bitcoin-qr
 id=&quot;qr&quot;
 width=&quot;300&quot;
 height=&quot;300&quot;
 bitcoin=&quot;bc1p4mqqpxdyg3l0tvpv9xaesw5rsqv7fqpcn9ydawx3e8yr4enmc3qq7m59vd&quot;
 lightning=&quot;lno1zrxq8pjw7qjlm68mtp7e3yvxee4y5xrgjhhyf2fxhlphpckrvevh50u0q2a8y2qt9jeu3pj7p3u58rxs98yu93dxjlmy3eqp24qze09anfr6cqszm40686ujqq7t9jsjjf42tl2pryv3ds9f4hm6hysk7lvswdm70ntsqvlgc5nm4px6k3q2wmj4xun784sss2jn00q0w4dhydmln92wvash6jckffu0wmetwptj38f5g09k8pznuk4wq2df046yv0c5m3dakwgfz5ths2ymm3z6vnehvxj6urcarufa7uecwqqst9x4063hdpxq4frmupnsam9ahg&quot;
 parameters=&quot;amount=0.00001&amp;label=bitcoin-qr.blu.cx&amp;message=bitcoin-qr.blu.cx&quot;
 image=&quot;./assets/bitcoin.svg&quot;
 is-polling=&quot;false&quot;
 poll-interval=&quot;1000&quot;
 type=&quot;svg&quot;
 corners-square-color=&quot;#000000&quot;
 corners-dot-color=&quot;#000000&quot;
 corners-square-type=&quot;extra-rounded&quot;
 dots-type=&quot;classy-rounded&quot;
 dots-color=&quot;#000000&quot;
 debug=&quot;true&quot;
 poll-callback={callbackExample}
/&gt;
</code></pre>
<p>I built this about a year ago but didn’t post about it because there was more I wanted to do and test before putting it out there. But there’s always something else to do, isn’t there?</p>
<p>If you find any issues, feature requests, or suggestions for improvement, feel free to <a href="https://github.com/thebrandonlucas/bitcoin-qr/issues/new">create an issue</a> in Github.</p>
<p>Hope this saves you some time!</p>
]]></content:encoded></item>
<item><title>20 February 2025</title><link>https://blu.cx/posts/journal/2025-02-20-notes/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2025-02-20-notes/</guid><pubDate>Thu, 20 Feb 2025 12:00:00 +0000</pubDate><description>Thought: Perhaps the next browser just be an LLM that cites it’s sources. That’s it. I literally just open the browser and see a chatbox, can switch between LLM backends, and can use my own one if…</description><content:encoded><![CDATA[<ul>
<li>
<p><em>Thought</em>: Perhaps the next browser just be an LLM that cites it’s sources. That’s it. I literally just open the browser and see a chatbox, can switch between LLM backends, and can use my own locally-hosted one if I’d like. For the ones I can’t buy, I just pay a bitcoin lightning invoice to use. That’s it!</p>
</li>
<li>
<p><em>Thought</em>: I wonder how much it would be to build a desktop computer that could run Deepseek 1776? Much cheaper than buying a laptop with equivalent specs, I suppose?</p>
</li>
<li>
<p>It would be cool to do an aggregate of the most popular tools rated by experts in each domain in software is. I.e. frontend developers love <a href="https://svelte.dev/">Svelte</a>, backend developers love <a href="https://www.rust-lang.org/">Rust</a>, SysAdmins love, I dunno, <a href="https://github.com/tmux/tmux/wiki">tmux?</a>, etc. There should be some criteria that we develop for what makes software “high-ranking”, such as it’s survival rate (“vim” and “emacs” are both still around and extremely popular, so they get high scores). Similar to the way in economics we have the measure the quality of a currency by its fungibility, divisibility, portability, durability, scarcity.</p>
</li>
<li>
<p><em>accountless.io or Awesome Accountless</em>: Maintain a repo/website of services which do not require accounts/respect privacy/are free and open source etc., for their use.</p>
</li>
<li>
<p>Today I ran and connected to a Minecraft server self hosting on my local NixOS device running over a Wireguard VPN. The server exists in the <code>minecraft-server</code> package, but you have to first do two things in your /etc/nixos/configuration.nix file:</p>
<ul>
<li>
<p>Add the lines:</p>
<pre><code class="language-nix">nixpkgs.config = {
  allowedUnfree = [&quot;minecraft-server&quot;]
}
</code></pre>
<p>By default, NixOS does not include Free and Open-Source Software (FOSS). So for any package that you try to download that doesn’t isn’t FOSS (which includes Minecraft 🥲), you’ll get this error. You can explicitly define which packages you’d like to have as unfree in an array as done above, or you can just generically do <code>nixpkgs.config.allowUnfree = true;</code>. I opted for specificity as I want to include as little unfree software as possible ;)</p>
</li>
<li>
<p>The second issue, which I got tripped up on for awhile, was the firewall. NixOS has configuration options for letting certain ports past the firewall. The <code>minecraft-server</code> port is 25565 (which you can see if you observe the server logs), so we want to allow packets through both (I believe both are necessary, anyway) the UDP and TCP port <code>25565</code>, like so:</p>
<pre><code class="language-nix">nixpkgs.firewall.allowedTCPPorts = [ 25565 ];
nixpkgs.firewall.allowedUDPPorts = [ 25565 ];
</code></pre>
<p>After that’s done, remember to run <code>sudo nixos-rebuild switch</code> to rebuild your NixOS configuration, and you should now be able to connect, assuming that server is reachable via your computer.</p>
</li>
</ul>
</li>
<li>
<p>I also managed to get Nextcloud running on my NixOS server, using <a href="https://nixos.wiki/wiki/Nextcloud">this guide</a> and some help from <a href="https://ppq.ai">ppq.ai</a> (Grok 2, in this case). Here is the setup I used:</p>
</li>
</ul>
<pre><code class="language-nix">  environment.etc.&quot;nextcloud-admin-pass&quot;.text = &quot;PWD&quot;;
  services.nextcloud = {
    enable = true;
    package = pkgs.nextcloud30;
    hostName = &quot;10.8.0.2&quot;; # An example of an IP assigned to the device by Wireguard VPN. This way it's accessible from other devices.
    config.adminpassFile = &quot;/etc/nextcloud-admin-pass&quot;;
    config.dbtype = &quot;sqlite&quot;;
  };
</code></pre>
<p>There are a couple of deviations here from the original post. First, I needed to upgrade to pkgs.nextcloud30 instead of pkgs.nextcloud28, as the docs recommend (it’s officially deprecated and <code>nixos-rebuild switch</code> will hassle you about it). You also have to change <code>&quot;PWD&quot;</code> to a password that’s allowed by <a href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_password_policy.html">Nextcloud’s default password policy</a> as mentioned in the article above, which, in my case, just amounted to creating a secure password with a password manager and them using that.</p>
<p>Also, be sure to later enable the TCP ports 80 (HTTP) and 443 (HTTPs):</p>
<pre><code class="language-nix">nixpkgs.firewall.allowedTCPPorts = [ 80 443 ];
</code></pre>
<p>I was able to access the Nextcloud UI at <code>10.8.0.2</code> after this! However, I got stuck in password hell for reasons I cannot fathom (it <em>seemed</em> like it was loading my password correctly, only to tell me after over 10 seconds that the password was wrong). What I <em>think</em> was happening was that Nextcloud wasn’t accepting the original password I used, which was something stupid simple like <code>password123</code>, and it was failing Nextcloud’s password rules. But then when I went to change it, something about the database was already screwed up or my IP address was just banned and I couldn’t login even after having set a good password. In the end, I just ended up nuking all the Nextcloud setup via <code>sudo rm -rf /var/lib/nextcloud</code> (careful with that command!), and then I re-ran <code>sudo nixos-rebuild switch</code> which re-setup the config with a good password from the start this time. And wouldn’t you know it, things worked! I could login with the default username of <code>root</code> and the password I had set in the config above.</p>
<p>By the way, another neat trick I learned is that you can look up what options exist for a <code>nix</code> service by using the <code>nixos-option</code> command. By running <code>nixos-option services.nextcloud.config</code>, I found you could set an admin username of your choice as well via <code>config.adminuser</code>.</p>
]]></content:encoded></item>
<item><title>18 February 2025</title><link>https://blu.cx/posts/journal/2025-02-18-notes/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2025-02-18-notes/</guid><pubDate>Tue, 18 Feb 2025 12:00:00 +0000</pubDate><description>I now have: A VPS running Wireguard VPN which my devices are connected to An Apple Silicon (Macbook M1) computer running NixOS The NixOS Macbook running a Jellyfin server for my media The Jellyfin can…</description><content:encoded><![CDATA[<ul>
<li>
<p>I now have:</p>
<ul>
<li>A VPS running Wireguard VPN which my devices are connected to</li>
<li>An Apple Silicon (Macbook M1) computer running NixOS</li>
<li>The NixOS Macbook running a Jellyfin server for my media</li>
<li>The Jellyfin server can successfully be accessed by any computer connected to the VPN</li>
</ul>
</li>
<li>
<p>Example of why I love open-source. I was having an issue today where my Jellyfin server would keep disconnecting from my other devices, seemingly sporadically, after awhile. Often when checking the logs I would see an error related to websockets, but didn’t understand why it was occurring. After browsing the <a href="https://github.com/jellyfin/jellyfin/issues">Jellyfin Github Issues</a> for a bit <a href="https://github.com/jellyfin/jellyfin/issues/13379">I discovered</a> someone who very likely is running into the same issue as me. It likely has something to do with running Jellyfin on Apple Silicon, which I am, despite the OS being NixOS. This person opened the issue last month, and the maintainers responded saying they had resolved it in the upcoming release. That release happened two days ago, and the maintainers responsible for pushing that update to the Nix package manager are now behind, so I literally can’t update it yet. But because the project was open-source, I could find that I had the same problem as someone else, and rest assured that I simply have to update my software when the release becomes available. And since the Nix package for Jellyfin is only one patch version behind, I expect that to be very soon.</p>
</li>
<li>
<p><em>Idea</em>. <a href="https://ppq.ai">ppq.ai</a> allows you to access many different AI models using micropayments. It’s amazing. The only thing I’d like now is a command line tool that I can use their API with.</p>
</li>
</ul>
]]></content:encoded></item>
<item><title>15 February 2025</title><link>https://blu.cx/posts/journal/2025-02-15-notes/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2025-02-15-notes/</guid><pubDate>Sat, 15 Feb 2025 12:00:00 +0000</pubDate><description>Thought - in general, we really need more things that are just “type these values into a list and run a program against it”. NixOS’s idea of just having a configuration file that defines an is a…</description><content:encoded><![CDATA[<ul>
<li>
<p>Thought - in general, we really need more things that are just “type these values into a list and run a program against it”. NixOS’s idea of just having a configuration file that defines an operating system is brilliant and is something we should have been doing for much longer in the computing world, and more programs and systems should operate this way. Instead of having to “re-setup” your computer every time you buy a new one (or relying on cleverly closed-source solutions like Apple has thus far successfully managed to do), you just plop in your config, sync your data, and <em>boom!</em>, you’re back in business. Over the years your computer would become more and more personalized to <em>you</em>. As a side note, it seems this means NixOS can sort of be a playground with which to create the best operating systems (you can configure anything pre-built).</p>
</li>
<li>
<p><em>Idea</em> - Feedify - Turn a set of Markdown files into an RSS Feed. We really need to make RSS Great Again. It would appear, at a superficial glance, that it is just architecturally much more user friendly, privacy-respecting, and self-hostable in every way. Why don’t we all use it? And, for what it’s worth, XMPP for messaging?</p>
</li>
<li>
<p>Thought - I got this idea from <a href="https://lukesmith.xyz/">Luke Smith</a>, but wouldn’t it be awesome to have browsers that could be constructed from config files as well?</p>
</li>
<li>
<p><em>Installing NixOS on Apple Silicon</em>: (Note: I went ahead and <em>uninstalled</em> asahi linux to simplify the process, since these docs seem to assume a clean installl). I followed the instructions <a href="https://github.com/tpwrules/nixos-apple-silicon/blob/main/docs/uefi-standalone.md">here</a>, <em>carefully</em>, and was successfully able to run (headless, at least) NixOS on Apple Sillicon.</p>
</li>
</ul>
]]></content:encoded></item>
<item><title>Valentine's Day. ssh-copy-id</title><link>https://blu.cx/posts/journal/2025-02-14-ssh-copy-id/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2025-02-14-ssh-copy-id/</guid><pubDate>Fri, 14 Feb 2025 12:00:00 +0000</pubDate><description>This lovely Valentine’s, I learned about a cool command for automating ssh logins to another computer.</description><content:encoded><![CDATA[<p>This lovely Valentine’s, I learned about a cool command for automating <code>ssh</code> logins to another computer.</p>
<p><code>ssh-copy-id</code> sets up the <code>ssh</code> keypair exchange for you automatically by just running it against your <code>username@host-ip</code> like so:</p>
<pre><code class="language-bash">ssh-copy-id root@123.456.789
</code></pre>
<p>Then, it’ll ask you for your password, and voila! You should now have <code>ssh</code> setup between your computer and the host such that the next time you run <code>ssh root@123.456.789</code> it will just auto-log you in, no passwords required!</p>
]]></content:encoded></item>
<item><title>Automating Site Deployment for an `nb` -&gt; Github -&gt; VPS Setup</title><link>https://blu.cx/posts/blog/2025-02-13-automate-site-deploy/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2025-02-13-automate-site-deploy/</guid><pubDate>Thu, 13 Feb 2025 12:00:00 +0000</pubDate><description>As mentioned in the blog post on syncing my nb notebook across devices using Syncthing, I wanted a way to auto-publish to my site after finishing a piece of writing. What I ended up coming up with was…</description><content:encoded><![CDATA[<p>As mentioned in the blog post on <a href="../blog/syncthing-nb.md">syncing my <code>nb</code> notebook across devices using Syncthing</a>, I wanted a way to auto-publish to my site after finishing a piece of writing. What I ended up coming up with was having two notebooks: <em>draft</em> and <em>final</em>, which were structurally the same as the markdown folder in the actual website code. When done with a piece in <em>draft</em>, I <code>cp</code> it to the appropriate place in <code>final</code>, then run a bash syncing script which copies all files from <em>final</em> to the corresponding markdown folder in my Sveltekit website repo, <code>git commit</code>‘s the results, and pushes it to the remote repository. Here is the bash script I’m using, in case anyone else finds it useful. Note the environment variables which must be defined before running. It’s really just a directory copying + <code>git commit</code> script:</p>
<pre><code class="language-bash">#!/bin/bash

# Script to copy files from nb drafts to site code, then commit and push

# --- Check environment variables ---
if [ -z &quot;$SITE_GIT_REPO_DIR&quot; ]; then
  echo &quot;ERROR: SITE_GIT_REPO_DIR is not set.&quot;
  exit 1
fi
if [ -z &quot;$NB_FINAL_DRAFT_DIR&quot; ]; then
  echo &quot;ERROR: NB_FINAL_DRAFT_DIR is not set.&quot;
  exit 1
fi

if [ -z &quot;$NB_FINAL_DRAFT_IMAGES_DIR&quot; ]; then
  echo &quot;ERROR: NB_FINAL_DRAFT_IMAGES_DIR is not set.&quot;
  exit 1
fi

if [ -z &quot;$SITE_CODE_MD_DIR&quot; ]; then
  echo &quot;ERROR: SITE_CODE_MD_DIR is not set.&quot;
  exit 1
fi

if [ -z &quot;$SITE_CODE_MD_IMAGES_DIR&quot; ]; then
  echo &quot;ERROR: SITE_CODE_MD_IMAGES_DIR is not set.&quot;
  exit 1
fi

# --- Copy files ---

# Copy markdown files
echo &quot;Copying files from $NB_FINAL_DRAFT_DIR to $SITE_CODE_MD_DIR...&quot;
cp -r &quot;$NB_FINAL_DRAFT_DIR&quot;/* &quot;$SITE_CODE_MD_DIR&quot;/

# Check if the copy command was successful.
if [ $? -ne 0 ]; then
  echo &quot;ERROR: Failed to copy files from $NB_FINAL_DRAFT_DIR to $SITE_CODE_MD_DIR.&quot;
  exit 1
fi

# Copy image files
echo &quot;Copying files from $NB_FINAL_DRAFT_IMAGES_DIR to $SITE_CODE_MD_IMAGES_DIR...&quot;
cp -r &quot;$NB_FINAL_DRAFT_IMAGES_DIR&quot;/* &quot;$SITE_CODE_MD_IMAGES_DIR&quot;/

# Check if the copy command was successful.
if [ $? -ne 0 ]; then
  echo &quot;ERROR: Failed to copy files from $NB_FINAL_DRAFT_IMAGES_DIR to $SITE_CODE_MD_IMAGES_DIR.&quot;
  exit 1
fi

# --- Git Commit and Push ---

# Navigate to the Git repository directory
cd &quot;$SITE_GIT_REPO_DIR&quot;

# Check if it's a git repository
if [ ! -d &quot;.git&quot; ]; then
  echo &quot;ERROR: $SITE_GIT_REPO_DIR is not a git repository.&quot;
  exit 1
fi

# Add the changed files (only the md and images directories)
echo &quot;Adding files to git...&quot;
git add &quot;$SITE_CODE_MD_DIR&quot; &quot;$SITE_CODE_MD_IMAGES_DIR&quot;

# Check if there are any changes to commit
if ! git diff --cached --quiet --exit-code; then
  echo &quot;Committing changes to git...&quot;
  git commit -m &quot;Update content&quot;

  # Push the changes to the remote repository
  echo &quot;Pushing changes to remote origin...&quot;
  git push origin master

  # Check if the push command was successful.
  if [ $? -ne 0 ]; then
    echo &quot;ERROR: Failed to push changes to remote origin.&quot;
    exit 1
  fi

  echo &quot;Successfully updated remote repository.&quot;
else
  echo &quot;No changes to commit.&quot;
fi

echo &quot;Script completed.&quot;
exit 0

</code></pre>
<h2 id="automating-deployments-to-vps">Automating Deployments to VPS<a href="#automating-deployments-to-vps" aria-label="Link to heading 'Automating Deployments to VPS'" data-heading-content="Automating Deployments to VPS" class="anchor"></a></h2>
<p>I also wanted to automate deployments to the VPS I was using on every push. So essentially, my website would update every time the script ran. So I put this <code>deploy.yml</code> in <code>.github/workflows</code>:</p>
<pre><code class="language-yml">name: Deploy to VPS

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install

      - name: Build SvelteKit app
        run: npm run build

      - name: Deploy to VPS
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          password: ${{ secrets.VPS_PASSWORD }}
          source: &quot;build/*&quot;
          target: &quot;/var/www/blu&quot;
          rm: true
          strip_components: 1
</code></pre>
<p>Note that the <code>VPS_HOST</code>, <code>VPS_USER</code>, and <code>VPS_PASSWORD</code> must be defined repository/environment secrets in Github. Also note the action we’re using at the end to do the actual deploy, <code>appleboy/scp-action</code>. Two parameters were important for what I was trying to do. First, <code>rm: true</code>, which deletes the target folder (<code>/var/www/blu</code> in this case) before uploading the data. In the scenario where I delete or move a file in the new build, the old file would’ve remained present without that parameter set, which I didn’t want.</p>
<p>Second, the <code>strip_components: 1</code> is also important here. Without it, the <code>build</code> directory was getting placed inside <code>/var/www/blu</code>, instead of the <em>contents</em> of the build directory moving into <code>blu</code>. So <code>strip_components</code> specifies that while I want to use <code>build/*</code> as the <em>source</em> of my files, I only want <em>what’s in it</em>, i.e. <code>build/*</code>, so don’t put anything after the first slash (e.g. <code>build</code>) into <code>/var/www/blu</code>.</p>
<p>That’s it! Now every time I update the content of the site (such as with this post), I just copy it over to <em>final</em>, then run the sync script, and voila!</p>
]]></content:encoded></item>
<item><title>Hard Reset Asahi Linux After Boot Error and Data Backup</title><link>https://blu.cx/posts/blog/2025-02-12-asahi-boot-fix/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2025-02-12-asahi-boot-fix/</guid><pubDate>Wed, 12 Feb 2025 12:00:00 +0000</pubDate><description>dnf update may cause a boot into a black screen. Here's how you can reset your computer.</description><content:encoded><![CDATA[<p><a href="https://asahilinux.org/">Asahi Linux</a> is a port of Linux on Apple Silicon (the M-series chips).</p>
<p>I’ve been using it fairly happily for awhile, but it booted into a black screen recently. I had a flash drive plugged in and I had also installed new programs using <code>dnf</code> (The Fedora package manager, which is the distro Asahi uses), and after some reddit searching it appears that the problem was caused by something having to do with <code>dnf update</code>. After restarting the computer, it showed the Apple logo, Asahi logo, and Fedora logo in sequence as usual, and then never booted past a black screen. Unfortunately I wasn’t really able to “fix” it, but I was at least able to get into a terminal screen, mount a flash drive, backup my data, and then do a hard reset.</p>
<p>On boot, press and hold FN + CTRL + OPTION + F2. This should take you into a terminal-based login screen. Login with your normal user credentials. Then plugin a flash drive. Identify the device name by typing <code>lsblk</code>. It should appear as <code>/dev/sda</code> or something of the like. You should be able to identify it by its’ file size (e.g. 32GB, etc.). Then, mount the device. First, you should create a mount point using <code>mkdir</code>, such as in <code>/mnt</code>:</p>
<p>Create a mount point:</p>
<pre><code>sudo mkdir `/mnt/mydrive`
</code></pre>
<p>Mount the device:</p>
<pre><code>sudo mount /dev/sda /mnt/mydrive
</code></pre>
<p>Copy files from your <code>home</code> directory (or wherever you have data stored you want to keep) to the flashdrive:</p>
<pre><code>cp -r ~/* /mnt/mydrive
</code></pre>
<p>Unmount and eject the drive when done:</p>
<pre><code>sudo umount /mnt/mydrive
sudo eject /dev/sda
</code></pre>
<h3 id="repartition-the-hard-drive">Repartition the Hard Drive<a href="#repartition-the-hard-drive" aria-label="Link to heading 'Repartition the Hard Drive'" data-heading-content="Repartition the Hard Drive" class="anchor"></a></h3>
<p>Now, you’ll need to do a complete uninstall/reinstall of Asahi Linux by repartitioning the hard drive on your Mac.</p>
<p>I followed <a href="https://www.youtube.com/watch?v=nMnWTq2H-N0&amp;t=7s">this tutorial video</a> to help me repartition the Mac properly, and it worked like a charm. Then, I was able to go through the same process of reinstalling Asahi again and using my Apple Silicon machine with Linux.</p>
]]></content:encoded></item>
<item><title>File Synchronization For All Your Devices with Syncthing</title><link>https://blu.cx/posts/blog/2025-02-12-syncthing-nb/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2025-02-12-syncthing-nb/</guid><pubDate>Wed, 12 Feb 2025 12:00:00 +0000</pubDate><description>How to synchronize files across many devices automagically</description><content:encoded><![CDATA[<p>I use the <a href="https://en.wikipedia.org/wiki/Text-based_user_interface">TUI</a>-based tool <a href="https://github.com/xwmx/nb">nb</a> for writing things. But I have a few problems:</p>
<ol>
<li>I use it on a lot of different computers and often want to add to notes that only exist on computer A while I’m on computer B.</li>
<li>Since everything I write on my website is in Markdown, I want to have the ability to have what I place in that notebook automatically update the website (or at least update it after running some command)</li>
</ol>
<p>Solving Problem 1 will be the focus in this article. In a subsequent post, I plan to detail solving problem 2.</p>
<h2 id="requirements">Requirements<a href="#requirements" aria-label="Link to heading 'Requirements'" data-heading-content="Requirements" class="anchor"></a></h2>
<p>I want a system that <em>automatically</em> syncs files across devices that have it installed on and connected to at an interval I define when the machine comes online.</p>
<h2 id="syncthing">Syncthing<a href="#syncthing" aria-label="Link to heading 'Syncthing'" data-heading-content="Syncthing" class="anchor"></a></h2>
<p>After prompting <a href="https://www.perplexity.ai/">Perplexity.ai</a> about the issue, two methods which appealed to me are <a href="https://syncthing.net/">Syncthing</a> and Git. Using Git would’ve been more manual and time-consuming, and since Syncthing is a very popular tool and fully open source, I went with that option.</p>
<h2 id="installing">Installing<a href="#installing" aria-label="Link to heading 'Installing'" data-heading-content="Installing" class="anchor"></a></h2>
<p>Syncthing has a great <a href="https://docs.syncthing.net/intro/getting-started.html">“Getting Started”</a> page that gave me everything I needed to know to use it. Just download the app on two devices. Select the architecture you’re using from their releases page <a href="https://github.com/syncthing/syncthing/releases">here</a>. Follow their Getting Started page and you should have all the basics to get up and running.</p>
<h2 id="helpful-notes">Helpful Notes<a href="#helpful-notes" aria-label="Link to heading 'Helpful Notes'" data-heading-content="Helpful Notes" class="anchor"></a></h2>
<h3 id="use-cases">Use Cases<a href="#use-cases" aria-label="Link to heading 'Use Cases'" data-heading-content="Use Cases" class="anchor"></a></h3>
<p>Syncthing is not really meant to be a backup software like <a href="https://linux.die.net/man/1/rsync">rsync</a>, because changes made on one machine will synchronize to the other machines, and therefore if one machine deletes a file it will also be deleted on the other machine. It’s meant to be a <em>sharing</em> software keep documents on different computers synchronized, as the name suggests. Which means that it should really be used for things like media libraries or as an alternative to cloud storage solutions like Box or Google Drive, or when needing to edit documents on multiple machines, as in my case with <code>nb</code>.</p>
<h3 id="mobile-devices">Mobile Devices<a href="#mobile-devices" aria-label="Link to heading 'Mobile Devices'" data-heading-content="Mobile Devices" class="anchor"></a></h3>
<p>I wanted one of my devices to be my phone, so just note that for mobile (i.e. by mobile I mean <em>Android</em> or a security-hardened derivative like <a href="https://grapheneos.org/">GrapheneOS</a> — sorry iPhone fans, but their anti open source ethos precludes you from using this one) you have to use a <a href="https://github.com/Catfriend1/syncthing-android">fork</a> of their <a href="https://github.com/syncthing/syncthing-android">now deprecated Android app</a>. You can download the apk file <a href="https://github.com/Catfriend1/syncthing-android/releases">here</a>.</p>
<h3 id="folder-types">Folder Types<a href="#folder-types" aria-label="Link to heading 'Folder Types'" data-heading-content="Folder Types" class="anchor"></a></h3>
<p>There are <a href="https://docs.syncthing.net/users/foldertypes.html">three types</a> of folders:</p>
<ul>
<li>Send Only</li>
<li>Receive Only</li>
<li>Send and Receive</li>
</ul>
<p>This is useful for sharing files while still setting some basic permissions for them. Send Only is great for sharing with people/devices that you don’t want to be able to modify your local copy via their modifications; Receive Only is the opposite — it can only <em>be shared with</em>, and yet changes from the sending folder will not modify changes in the receiving folder.</p>
<h3 id="the-faq-is-very-helpful">The FAQ is Very Helpful<a href="#the-faq-is-very-helpful" aria-label="Link to heading 'The FAQ is Very Helpful'" data-heading-content="The FAQ is Very Helpful" class="anchor"></a></h3>
<p>Initially when using Syncthing I was confused about a few things (Should I use this for backups? Why does syncing take so long? How does it work? Will it ever connect to anything other than my own machines and can they see my data?). The <a href="https://docs.syncthing.net/users/faq.html#what-is-syncthing">FAQ</a> answered all my questions very nicely and in detail, so be sure to give that a read.</p>
<h3 id="note-for-obsidian-users">Note For Obsidian Users<a href="#note-for-obsidian-users" aria-label="Link to heading 'Note For Obsidian Users'" data-heading-content="Note For Obsidian Users" class="anchor"></a></h3>
<p><a href="https://obsidian.md/">Obsidian</a> is an (unfortunately closed-source) note-taking app that pairs nicely with <code>nb</code>. I use <code>nb</code> on all my desk/laptop machines and use Obsidian on mobile since trying to use a terminal on mobile would be a terrible experience. After installing Syncthing-Fork on your mobile device and syncing the relevant folder, you can open that folder as your Vault in Obsidian. Before making any changes in Obsidian, however, you should disable a setting in Syncthing. Click on the synced folder, scroll down to “Watch for changes”, and disable it. This will make it so changes to that synced directory will only attempt to sync changes to the others on an hourly basis. Obsidian saves files to disk extremely frequently, basically on every keystroke, and this caused Syncthing to behave strangely or simply not sync any changes made from Obsidian to the other devices. This is a bit unfortunate because there’s a possibility that you could update the same file on your mobile device and another machine within that hour, causing a conflict. But fortunately Syncthing will save both versions of the file in a “conflict” file and you will have both versions so that there’s no content loss.</p>
]]></content:encoded></item>
<item><title>Toward A Better Site</title><link>https://blu.cx/posts/blog/2025-01-29-blog-revamped/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2025-01-29-blog-revamped/</guid><pubDate>Wed, 29 Jan 2025 12:00:00 +0000</pubDate><description>A more thoughtful approach to personal site development</description><content:encoded><![CDATA[<blockquote>
<p>We shall not cease from exploration<br />
And the end of all our exploring<br />
Will be to arrive where we started<br />
And know the place for the first time.</p>
</blockquote>
<ul>
<li><a href="https://www.columbia.edu/itc/history/winter/w3206/edit/tseliotlittlegidding.html">T.S. Eliot, from “Little Gidding,” Four Quartets. Originally published 1943.</a></li>
</ul>
<p>This past year, I’ve experimented with several different methods of creating and managing a personal website. I didn’t merely want the most convenient solution, or the solution most likely to buy popularity more easily via its platform or network effects. I wanted a solution that was in line with:</p>
<ul>
<li>
<ol>
<li>A growing set of principles I want to practice see more of in software I use and create.</li>
</ol>
</li>
<li>
<ol start="2">
<li>A set of very unique and specific features I desired.</li>
</ol>
</li>
</ul>
<p>The pursuit of principles and features, of course, give way to the brutal constraints of reality to some degree. I don’t have infinite time. And so the desire to obtain the features I want while maintaining those principles must be balanced with the constraints of time and effort. After experimenting with several iterations of website-building, I have in many ways “arrived where I started”, but now feel more confident, more sure-footed, more “aligned”, and more excited about what even a simple little personal website could be.</p>
<h3 id="first-site--a-diy-compromise">First Site — A DIY Compromise<a href="#first-site--a-diy-compromise" aria-label="Link to heading 'First Site — A DIY Compromise'" data-heading-content="First Site — A DIY Compromise" class="anchor"></a></h3>
<p>I started out as most Gen-Z developers start, I think. You want to be a developer, you need a portfolio. You want to be a <em>web developer</em>? Your portfolio is your website. And so my first website was just that. I built it during many skipped classes in college, while I was still painstakingly and inefficiently learning HTML, CSS, and Javascript. It was designed to be artsy and beautiful and to get me a job as a web developer. And so I didn’t post or write about anything, just listed what I felt would be most impressive to recruiters. But, after proving to yourself that you can indeed get a job and you can indeed make enough to pay your rent and not be a broke college kid forever, you start to realize there’s more important things in life than just trying to impress recruiters. You realize that a personal website can be so much more. It can be a <em>reflection of yourself</em>.</p>
<p>And so, with that in mind, I built my first “real” attempt at a personal site. I built it in <a href="https://svelte.dev/">Sveltekit</a> (which was in unstable version 3 at the time), used the <a href="https://github.com/pngwn/MDsveX">MDSveX</a> library so I could write my articles in <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a>. This was a proper blogging site that also listed projects and “About Me” and “Contact” and whatnot, however there were several problems:</p>
<ul>
<li><strong>Hosting</strong>: It was hosted on Netlify, which meant that there were often cold starts when visiting it. This also meant that I wasn’t hosting the site myself, which wasn’t a big deal to me at the time, but developed into one over the course of the year.</li>
<li><strong>Features</strong>: The site was low on features and bells and whistles, but this was mainly because I just didn’t take the time to implement them. The fact that everything was written in Sveltekit meant that I could have, but I wasn’t taking the time to really think about what I wanted implemented and then come up with a good plan to do it.</li>
<li><strong>Preprocessing the Markdown</strong>: I didn’t quite understand how to set up the preprocessor in MDSveX properly, which does the job of converting the Markdown to HTML when building the site. What I <em>think</em> was happening was this resulted in pages having to convert the Markdown to HTML on the fly during runtime, and so the bigger the article, the slower the page load, but I never truly investigating this since I decided to redo the website from the ground up anyway. To the end user, this just felt like the page was frozen for awhile, which was really annoying UX.</li>
<li><strong>Content and Organization</strong>: I wasn’t sure of how I wanted things organized, and I only had a few articles, blogs, and quotes on there. The way I would write my content was by writing the article in Markdown directly on the site and then just committing and pushing the code to Github, and from there it would build and deploy to Netlify automagically. That was nice, but I would prefer to be able to write the article in another program (so that I could have “draft” versions of it and whatnot) and then just commit + push when ready in a single command/click sort of way.</li>
</ul>
<p>All these problems contributed to a year of experimentation that left my actual ability to write things a bit in limbo.</p>
<h3 id="substack">Substack<a href="#substack" aria-label="Link to heading 'Substack'" data-heading-content="Substack" class="anchor"></a></h3>
<p>I started out by just wanting something that worked, and worked well, for <em>writing</em>, without having to worry about anything else. Substack is the obvious choice for this. It’s famously anti-censorship, has an intuitive UI, a large audience, most of my own subscriptions worth reading are from Substack, easy subscription tiers and connection to Stripe for payments, easy email subscriptions. What’s not to like?</p>
<p>There was one thing that just ended up being a deal breaker for me. Probably a bizarre requirement to most people, but I just couldn’t in good conscience do it. At the time, I was considering requiring subscriptions and payments for some of the articles I wrote, and on Substack, there’s <em>no option for bitcoin payments</em>. The saddest thing of all is, it appears that there <em>was</em> a way to pay with bitcoin while <a href="https://opennode.com/blog/bitcoin-payments-with-substack/">Substack was using OpenNode’s API</a>, but don’t any longer. I suppose that’s a price of vendor lock-in. It is now Substack’s <a href="https://support.substack.com/hc/en-us/articles/360037862551-I-can-t-use-Stripe-Do-you-accept-PayPal-crypto-clamshells-etc">official policy</a> to <em>not</em> support any bitcoin/crypto payments. I work in the Lightning Network <em>because</em> the payments use case of bitcoin is one of if not it’s most exciting aspect. It’s probably one of the strongest ways bitcoin can positively influence how we use the internet. To only allow credit card subscriptions, and not bitcoin lightning micropayments, for paywalled articles if I did any would be sad and hypocritical of me. There are other reasons of course, like the fact that Substack isn’t open source and that I wouldn’t actually own my own data. The idea that my blog dies when the company dies was another major negative.</p>
<p>So what other options were there that fit my increasingly stringent criteria?</p>
<h3 id="ghost">Ghost<a href="#ghost" aria-label="Link to heading 'Ghost'" data-heading-content="Ghost" class="anchor"></a></h3>
<p>Enter <a href="https://ghost.org">Ghost</a>, a website which markets itself as essentially the more <a href="https://ghost.org/vs/substack/">principled and feature-rich version of Substack</a> (which I believe they are). They are fully open source and you can run the blog software locally yourself, deploy it on a VPS of your choice, setup your own email provider, etc. If you choose to pay for them to host it for you, it’s only $9 a month and that includes the ability to bring your own domain, email subscriptions, etc. A huge improvement to me, and a project and team that I was (and still am) very excited about.</p>
<p>They even have a marketplace of templates and integrations, and given their open-source spirit I was hopeful they’d have some sort of bitcoin integration on there — and there is one…that you have to pay for. It’s called <a href="https://scribsat.com/">Scribsat</a>; here’s the Ghost Forum <a href="https://forum.ghost.org/t/a-new-bitcoin-payments-integration-scrib/36125/4">post</a> about it. They charge a monthly fee steeper than Ghost’s hosting fees itself to allow you to accept bitcoin lightning payments on Ghost. That didn’t really fit the bill for me, either, and I don’t want to be forced to depend on another service to accept the payments for me. For what it’s worth, this does seem to be a <a href="https://forum.ghost.org/t/bitcoin-payments/10514/37">hot topic</a> on the Ghost Forum, and I hope that someone will take up the call of developing something people can use for this. Ghost is a great tool and company, <a href="https://blog.lopp.net/">used by Bitcoiners</a>, and would be powerful to many if properly combined with bitcoin payments.</p>
<p>I considered working on this myself, but there is significant overhead that I just didn’t and don’t have the time (for now) to take up.</p>
<p>For awhile, the lack of bitcoin-based payment options was worth it, and I paid for a full year’s subscription even though I wasn’t sure if I’d use it for long (I didn’t, but I consider it a tithe to open-source software). I loved the writing and publishing experience, which was extremely simple, and there were a good number of default themes to choose from. However, none that I <em>loved</em>, and I found myself longing for more customizability. For most bloggers who can pay a few bucks a month or have the wherewithal to self-host, I would definitely recommend Ghost.</p>
<h3 id="inspiration-draws-from-disparate-sources">Inspiration Draws From Disparate Sources<a href="#inspiration-draws-from-disparate-sources" aria-label="Link to heading 'Inspiration Draws From Disparate Sources'" data-heading-content="Inspiration Draws From Disparate Sources" class="anchor"></a></h3>
<blockquote>
<p>Shall I tell you the secret of the true scholar? It is this: Every man I meet is my master in some point, and in that I learn of him.</p>
</blockquote>
<ul>
<li><a href="https://wist.info/emerson-ralph-waldo/65482/">Ralph Waldo Emerson, <em>Greatness</em></a></li>
</ul>
<p>My website was hosted on Ghost for a couple months, and it actually allowed me to write an article or two and post a quote here and there, and I was starting to give up on monetizing it (was I ever really doing it for that anyway?).</p>
<p>But around this time I was coming into greater contact with online presences that helped me to think differently, and though they all differ radically in occupation, style, and personal beliefs, they share an energy for creating good things meant to <em>last</em>; a passion for thinking long-term and for following their hearts over easy money and fame. A desire to achieve greatness in some form or other, and to have the revenues of their considerable efforts paid in something deeper than cash.</p>
<p>One of those I stumbled across was <a href="https://lukesmith.xyz/">Luke Smith</a>, a thoroughly original person, whose unique admixture of Twitter-like meme humor, dead-serious power usage of FOSS-only tech, deep historical, philosophical, and linguistic knowledge, and opinionated take on Christianity fascinated me, even if I didn’t agree with many of his opinions and means of presenting that information. It’s rare to find a person occupying all those particular spheres simultaneously, particularly in technology. From him I learned to ask for more from my software, to seek and value the right to and practice of <em>ownership</em>, whether that’s in owning your own land, owning your own money (bitcoin, being debt-free), or owning your own technology to the highest degree possible. He even <a href="https://lukesmith.xyz/updates/retiring-fiat-donation-portal/">removed all credit card-based ways to donate to him</a> on his website, as they weren’t in keeping with his principles. I used his tutorial website on becoming an <a href="https://landchad.net/">Internet Landlord</a> by self-hosting a website on your own DNS, for example, to build and host the version of this site at the time of writing. Another thing that is important to him is to have technology be a <em>tool you use</em>, rather than you be <em>its’ tool</em>. To that end, he helped encourage me to try to build and use software that will last, and focus less on the ephemeral. Not an easy pursuit in the software world, open source or otherwise!</p>
<p>Another figure who influenced this blog is <a href="https://www.henrikkarlsson.xyz/">Henrik Karlsson</a>, whose article <a href="https://www.henrikkarlsson.xyz/p/search-query"><em>A blog post is a very long and complex search query to find fascinating people and make them route interesting stuff to your inbox</em></a> had a deep impact on me and the direction of my thinking about how a blog should be made. It is excellent, both the idea and the read.</p>
<p><a href="https://ziglang.org/">Zig</a> creator Andrew Kelley, also comes to mind, whose article <a href="https://andrewkelley.me/post/why-we-cant-have-nice-software.html"><em>Why We Can’t Have Nice Things</em></a> helped me realize the incredible waste that often goes into making products worse when money alone is the goal.</p>
<p><a href="https://visakanv.com/">Visakan Veerasamy</a>’s article <a href="https://visakanv.substack.com/p/we-were-voyagers"><em>We Were Voyagers</em></a> is also an amazing and deeply encouraging read, and while it didn’t directly influence how I built this website, it further influenced the “do something useful to others with your life in a spirit of gratitude” approach I’m trying to take to life in general, and will have an effect on the prose and content of the site if nothing else.</p>
<p>But the final major influence was <a href="https://gwern.net/">gwern.net</a>, whose beautiful and carefully crafted website design is matched by its rich content and elegant prose. It’s filled with a hundred little features, optimizations, and design decisions that make it a joy to use. For example, it has custom <a href="https://www.mediawiki.org/wiki/Page_Previews">page previews</a> which allow you to see a preview of where a link will take you when hovering over it. It has tables of contents and footnotes for each article, and the footnotes become sidenotes on wide screens for convenience. He takes the time to design artsy <a href="https://en.wikipedia.org/wiki/Initial">dropcaps</a> for each article. And something I found very interesting that I’ve never seen before, is his extensive use of local archives to fight against <a href="https://en.wikipedia.org/wiki/Link_rot">linkrot</a>, which is an important problem if we wish to preserve something of ourselves for the future. Read his <a href="https://gwern.net/about">about page</a> for the site to learn more.</p>
<p>All told, Gwern’s website appears to me an exercise in how to create an optimal personal website for the long-run. He summarizes the fundamental question of building a site from the link above:</p>
<blockquote>
<p>What does it take to present, for the long-term, complex, highly-referenced, link-intensive, long-form text online as effectively as possible, while conserving the reader’s time &amp; attention?</p>
</blockquote>
<p>This version of my site, though still nascent, is based on a fuzzy mesh of these influences, and I will make the attempt to improve it and make it more resilient and higher quality over time.</p>
<h3 id="knowing-the-place-for-the-first-time">Knowing the Place for the First Time<a href="#knowing-the-place-for-the-first-time" aria-label="Link to heading 'Knowing the Place for the First Time'" data-heading-content="Knowing the Place for the First Time" class="anchor"></a></h3>
<p>Who was I kidding? I’m a <em>web developer</em>. I should’ve known from the beginning it would feel wrong to me to outsource that task, despite the work it may have taken. So after a year of exploration, I’m back at doing the same thing I did before — building the website in Sveltekit and MDsveX. But hopefully this time better, and having learned a lot about how to practically achieve that in addition to learning my own reasons for trying to do so in the first place.</p>
<p>Before, building my own website was a cool thing to put on my resume. But this time I have my <em>reasons</em>.</p>
<h3 id="what-now">What Now?<a href="#what-now" aria-label="Link to heading 'What Now?'" data-heading-content="What Now?" class="anchor"></a></h3>
<p>My primary reason for building this site is to document everything I do that I think might be of use to someone else (or my future self). I want something that I built myself and which I have full control over, whose format I have the freedom to modify or restructure at any time (which I couldn’t do in my previous methods). I want something that allows me to publish articles, blogs, daily journals, quotes I find interesting, projects I’m working on, what have you, in as frictionless a manner as possible while still retaining maximum expressive possibilities. Which, in essence, means I have to build things myself, and that there will be a high up-front cost to doing so.</p>
<p>So be it!</p>
]]></content:encoded></item>
<item><title>Accountless AI Editors Paid with Bitcoin</title><link>https://blu.cx/posts/journal/2025-01-21-accountless-ai-editors/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2025-01-21-accountless-ai-editors/</guid><pubDate>Tue, 21 Jan 2025 12:00:00 +0000</pubDate><description>ppq.ai is a website that allows you to use pretty much any AI model using bitcoin micropayments (via the Lightning Network) instead of having to apply for a subscription or pay with a credit card. I…</description><content:encoded><![CDATA[<p><a href="https://ppq.ai/">ppq.ai</a> is a website that allows you to use pretty much any AI model using bitcoin micropayments (via the Lightning Network) instead of having to apply for a subscription or pay with a credit card. I hate that the modern web requires email accounts and either subscriptions or ads for everything, and love the idea of using micropayments for non-recurring and/or anonymous and/or just plain more convenient purchases online.</p>
<p>This is one of my favorite applications of micropayments I’ve seen so far. Excitingly, they have an API key in addition to the web interface, which meant that I was able to get <a href="https://neovim.io/">neovim</a> working like the <a href="https://www.cursor.com/">Cursor</a> code editor using the <a href="https://github.com/yetone/avante.nvim">avante</a> neovim plugin by customizing my config! So now, I finally have a way to <em>accountlessly</em>, <em>privately</em>, and <em>subscription-less-ly</em> pay for a service I very much want to use. Exciting times! I will gladly pay for this service now for my AI code-editing needs, over paying flat rates for subscription-based things like Cursor. I will try to do a writeup on how to do this soon.</p>
]]></content:encoded></item>
<item><title>Inauguration Day</title><link>https://blu.cx/posts/journal/2025-01-20-inauguration-day/</link><guid isPermaLink="true">https://blu.cx/posts/journal/2025-01-20-inauguration-day/</guid><pubDate>Mon, 20 Jan 2025 12:00:00 +0000</pubDate><description>Idea: Quote Scrolls - Looping scrolls with quotes on them that people can sift through. Would be good for museums to be able to collate a bunch of famous things said about/by the place/person on I if…</description><content:encoded><![CDATA[<ul>
<li>
<p>Idea: <strong>Quote Scrolls</strong> - Looping scrolls with quotes on them that people can sift through. Would be good for museums to be able to collate a bunch of famous things said about/by the place/person on exhibit.</p>
</li>
<li>
<p>I wonder if there is a portable version of Markdown files? Something like a PDF, which includes all embedded image or other data, but also has the nice edit-ability properties of a Markdown file. One of the troubles with Markdown now is figuring out how to export/import them around. Do you just zip the .md and image/video files referenced in the same directory? What if the path to the desired image or video wasn’t in the same directory when you wrote it? This could perhaps be something as simple as a standardized and agreed-upon way to put referenced files of different types in folders of the same name (e.g. a “videos” and “images” directory standard co-located with the .md file).</p>
</li>
</ul>
]]></content:encoded></item>
<item><title>Payjoin for a Better Bitcoin Future</title><link>https://blu.cx/posts/articles/2023-10-31-payjoin-better-future/</link><guid isPermaLink="true">https://blu.cx/posts/articles/2023-10-31-payjoin-better-future/</guid><pubDate>Tue, 31 Oct 2023 12:00:00 +0000</pubDate><description>Payjoin is a protocol that uses a simple, clever trick in the way Bitcoin creates transactions to solve many of its problems at once.</description><content:encoded><![CDATA[<p>Payjoin is a protocol that uses a simple, clever trick in the way Bitcoin creates transactions to solve many of its problems at once. It was created to solve Bitcoin’s biggest <a href="https://decrypt.co/107376/bitcoin-privacy-problem-what-cypherpunks-are-doing">privacy problem</a>, but can also assist with its <a href="https://en.wikipedia.org/wiki/Bitcoin_scalability_problem">scaling problem</a>, and help people <a href="https://github.com/orgs/payjoin/discussions/91#discussion-5439172">save on fees</a>. It’s particularly compatible with Lightning Network Nodes, as it currently has a liveness requirement for the receiver, meaning they must be online at the moment they are being sent money (just like a Lightning Node). In the future, even this requirement will be eliminated and it can be used offline. It can be <a href="https://geyser.fund/project/payjoin#:~:text=Payjoin%20is%20easy%20to%20integrate%2C%20but%20can%20only%20take%20off%20when%20software%20supports%20sending%20and%20receiving%20via%20the%20BIP%2078%20spec">easily integrated into wallets</a>, it can be used to <a href="https://github.com/payjoin/nolooking">open many lightning channels at once</a>, and it’s usage can be made passive, so that you can receive the benefits of Payjoin without even knowing you’re using it. The privacy benefits of payjoin cascade, so that everyone would see privacy gains even if only a <a href="https://reyify.com/blog/payjoin#conclusion">minority of people used it</a>. And, perhaps best of all, payjoin doesn’t require a hard or soft fork. It can be and is used on Bitcoin today, and it could be used on Bitcoin from the very first version of the software.</p>
<p>Payjoin is a derivative of <a href="https://en.bitcoin.it/wiki/CoinJoin">Coinjoin</a>, which is an older, more interactive protocol, meaning the user must be heavily engaged with it to use it, which necessarily drives down usability and inhibits adoption. Somehow, despite this, Coinjoin has seen much higher adoption than payjoin so far, despite the more obvious benefits and ease of use with payjoin. A complex, unclear path forward for developers inhibits adoption by wallet software.</p>
<p>Payjoin has been around for a few years, and given:</p>
<ol>
<li>The <em>many</em> benefits listed above</li>
<li>The ability for a passive, nonintrusive user interface</li>
<li>The simplicity for wallet providers to integrate it</li>
</ol>
<p>Why is <a href="https://en.bitcoin.it/wiki/PayJoin_adoption">adoption so low</a>?</p>
<p>Especially compared to the far more interactive, difficult to use, and expensive Coinjoin protocol?</p>
<p>In this article, we’ll look at current attacks on Bitcoin privacy, the history of payjoin from the perspective of privacy, how it works and how it can provide so many benefits with no changes to Bitcoin, and the current state of adoption. If payjoin can radically improve privacy, scaling, and fee issues with Bitcoin, the low effort required for wallets to integrate it would be well worth it.</p>
<h2 id="why-privacy-matters-for-bitcoin">Why Privacy Matters for Bitcoin<a href="#why-privacy-matters-for-bitcoin" aria-label="Link to heading 'Why Privacy Matters for Bitcoin'" data-heading-content="Why Privacy Matters for Bitcoin" class="anchor"></a></h2>
<p>Before discussing the importance of payjoin, we must understand the importance of privacy. If you don’t need convincing on this front, feel free to skip down to the history and explanation of payjoin.</p>
<p>In Western democracies, the prime importance of privacy is difficult to communicate, as its benefits still seem invisible to people. It is harder to convincingly explain to someone why privacy matters (especially in the face of higher cost or greater inconvenience) if they’ve never felt the often terrible consequences of bad actors having too much information about them, perhaps because it requires people to think long-term about the consequences of these invasions.</p>
<p>Still, privacy seems to be something more and more people want in theory, but they typically do little to achieve it unless the barriers are extremely low and convenient. Therefore, technologies that wish to protect people’s privacy should be designed to do so with minimal requirements for interaction and inconvenience for the user.</p>
<h3 id="fungibility">Fungibility<a href="#fungibility" aria-label="Link to heading 'Fungibility'" data-heading-content="Fungibility" class="anchor"></a></h3>
<p>Privacy is not the only problem payjoin can help solve, but it is the problem it was created to solve. The lack of privacy by default on Bitcoin has long been lamented, and one taken very seriously in the Bitcoin community. Bitcoin was designed to be directly peer-to-peer and censorship resistant. But the ability to track owners of future payments once coins have been linked to an identity allows for discrimination. This destroys fungibility – or the degree to which one of a currency’s individual units are indistinguishable from another of the same unit – which is one of the primary properties of a good money.</p>
<p>If buyers can be tracked, not only can coins that are currently owned by illicit actors be rejected, but coins that <em>have ever</em> been used for illicit purposes can be marked as such and be rejected by merchants, nevermind whether the current owners obtained them through perfectly legitimate means. Imagine not being able to buy milk at the grocery store because the dollars you have were once-upon-a-time used to buy drugs, would it be fair to you to be discriminated against by having them say “your money is no good here”? Should you be punished for someone else’s crime? And what would that do to how you treat those dollars? It would make them worthless as your spending power is diminished by owning them, and make some dollars (the “non-tainted” dollars) more valuable than other dollars, which of course shouldn’t make sense. One dollar should always equal one dollar, no matter what individual one dollar bill is used to represent it, or the ability of the dollar to do its job of transferring value is impeded.</p>
<h3 id="the-criminal-myth">The Criminal Myth<a href="#the-criminal-myth" aria-label="Link to heading 'The Criminal Myth'" data-heading-content="The Criminal Myth" class="anchor"></a></h3>
<p>It is often said by Bitcoin and privacy detractors that privacy is for criminals. This is the familiar “if you aren’t doing anything wrong, you have nothing to hide” logic, which is easily refuted:</p>
<ul>
<li>Few people would be okay with an internet live stream of them taking a shower or using the bathroom being broadcast. Is that because it’s wrong? This is to point out that <em>everyone</em> has something to hide, and hiding things isn’t inherently wrong.</li>
<li>More broadly, the government is charged with providing the legal basis for what’s wrong, but that definition is always subject to change. If people aren’t free to have privacy, then they really aren’t free at all, as their actions will be severely restricted by perceived social, if not governmental, restrictions. People are judged and attacked for doing perfectly legal things by others all the time. <a href="https://nakamotoinstitute.org/cypherpunk-manifesto/">Privacy is the power to choose who you reveal yourself to</a>.</li>
</ul>
<p>Aside from this naive and self-evidently illogical argument, criminals, unlike most law-abiding citizens, are willing to accept high costs to obtain privacy, so measures to hurt privacy-by-default hurt regular people far more than they hurt criminals. This would be the case even if governments weren’t doing a poor job of actually using their new privacy-restricting measures to actually catch criminals en masse, but instead to “pick and choose” and selectively spy on the citizenry at their will. If a citizen says something those in power don’t like, and what those in power don’t like is always in flux, it can selectively decide to target and hurt them.</p>
<p>Finally, the desire for privacy isn’t merely the fear of government overreach. It is a practical safety or reputational concern. If someone can find out how much money you have and where you live, how hard is it to steal from you? Now consider the number of places you entered your address, payment details, pictures, on the internet. Do you trust every one of those sites to keep your information secure? You shouldn’t, because even the best fail, and criminals are paying large sums of money to hackers who exploit weaknesses for this valuable information.</p>
<h3 id="privacy-and-democracy">Privacy and Democracy<a href="#privacy-and-democracy" aria-label="Link to heading 'Privacy and Democracy'" data-heading-content="Privacy and Democracy" class="anchor"></a></h3>
<p>A prerequisite to control in any totalitarian state is <em>knowledge</em> of the speech, access to information, and financial activity of its citizens. Without it, it can’t know what to attack, shut down, or manipulate to advance its narrative or further its control. If it doesn’t reliably have access to this information, it becomes difficult to target citizens on a whim. In totalitarian societies of the past, such as the Soviet Union and Nazi Germany, privacy was eroded by ensuring the indoctrination of family members who would report on unfavorable opinions in private conversations, destroying webs of trust in families. When this same erosion of privacy occurs with money, its effects can be even more powerful than with speech. Cutting off the source of funding can be a very effective means of disbanding political dissent.</p>
<h3 id="bitcoin-privacy-is-under-attack">Bitcoin Privacy is Under Attack<a href="#bitcoin-privacy-is-under-attack" aria-label="Link to heading 'Bitcoin Privacy is Under Attack'" data-heading-content="Bitcoin Privacy is Under Attack" class="anchor"></a></h3>
<blockquote>
<p>Never waste an opportunity afforded by a good crisis</p>
</blockquote>
<p>– Attributed to Nicollo Machiavelli</p>
<p>It is in the name of combating criminals, in this case Hamas terrorists, that new regulation is opportunistically being proposed to make privacy-preserving methods in Bitcoin illegal.</p>
<p>On October 10, 2023, an article was published by the <a href="https://www.wsj.com/world/middle-east/militants-behind-israel-attack-raised-millions-in-crypto-b9134b7a">Wall Street Journal</a> reporting that Hamas acquired $130 million dollars of funding by means of cryptocurrency. One week later, senator Elizabeth Warren <a href="https://www.warren.senate.gov/imo/media/doc/2023.10.17%20Letter%20to%20Treasury%20and%20White%20House%20re%20Hamas%20crypto%20security.pdf">wrote a letter</a> to President Biden urging him to address questions regarding his administration’s response to the “use of cryptocurrency by terrorist organizations” by October 31, citing the WSJ article as evidence of the urgent need for such regulation. The letter obtained the signatures of 29 of the 100 senators, and 76 members of the House. Curiously, on October 19th, just two days after the letter was sent, the Financial Crimes Enforcement Network <a href="https://www.fincen.gov/sites/default/files/federal_register_notices/2023-10-19/FinCEN_311MixingNPRM_FINAL.pdf">published a proposal</a> for regulating cryptocurrency mixing due to the risk of money laundering. It lists, among the methods used to obscure transactions:</p>
<blockquote>
<p><em>Using programmatic or algorithmic code to coordinate, manage, or manipulate the structure of a transaction:</em> This method involves the use of software that coordinates two or more persons’ transactions together in order to obfuscate the individual unique
transactions by providing multiple potential outputs from a coordinated input, decreasing the probability of determining both intended persons for each unique transaction.</p>
</blockquote>
<p>This definition includes Coinjoin and Payjoin, although <em>using algorithmic code</em> is broad enough to include any transaction, and therefore justify at-will censorship.</p>
<p>But the WSJ article, which provided the sentiment for the letter and attempted to justify the regulation, horribly misinterpreted the data – the actual dollar amount that was connected with Hamas was <a href="https://www.chainalysis.com/blog/cryptocurrency-terrorism-financing-accuracy-check/">$450,000</a>. Cryptocurrency is nowhere near a major source of Hamas’ funding. Hamas itself explicitly stated that they did not want funding via Bitcoin due to its traceability.</p>
<p>The irony of the proposed regulation, then, is that its effects will be minimally impactful to the terrorist organization that is the justification for its existence, but maximally impactful to everyone else who wants to use Bitcoin and other cryptocurrencies.</p>
<p>There can be little doubt that the battle for the right to privacy in Bitcoin has arrived in the U.S., and it has predictably masked itself in the guise of national security against foreign adversaries. It’s more important now than ever to understand the need for and use of privacy-preserving techniques on Bitcoin to combat the ongoing attempts to chip away at it.</p>
<blockquote>
<p><a href="https://nakamotoinstitute.org/cypherpunk-manifesto/">We must defend our own privacy if we expect to have any.</a></p>
</blockquote>
<h2 id="1-how-transactions-work">1. How Transactions Work<a href="#1-how-transactions-work" aria-label="Link to heading '1. How Transactions Work'" data-heading-content="1. How Transactions Work" class="anchor"></a></h2>
<p>To understand the need for payjoin and how it works, it’s crucial to understand how transactions work. Each transaction in Bitcoin is a combination of <em>inputs</em> and <em>outputs</em>. Outputs define which public key or “address” the bitcoin is being sent to. Inputs define which “sources” or <em>previous</em> outputs are used in creating the new outputs for a transaction. It is helpful to think of this as an analogy to how we use different denominations of cash to pay for things. Imagine you need to pay $25 to a restaurant for dinner, plus a $5 tip for the waiter, for a total of $30 (these are your outputs, two different “chunks” of money going to two different people – the restaurant and the waiter).</p>
<p>How would you pay it? Let’s say you have the following bills available (these are your inputs):</p>
<ul>
<li>One $20</li>
<li>Two $10’s</li>
<li>Six $5’s</li>
</ul>
<p>To construct this transaction, you could use one $20 bill and two $5 bills, one being for the waiter:</p>
<p><img src="/images/articles/payjoin-better-future/25-5-tx.webp" alt="Restaurant payment" /></p>
<p>Note one important way in which our cash analogy breaks down: the $20 and the $5 <em>merge</em> into one “bill”. This would be more analagous to melting individual pieces of gold into one bigger piece in order to be able to pay the exact amount required, instead of handing over multiple pieces. Bitcoin allows us to split and merge inputs however we’d like to create the appropriate output.</p>
<p>You might also use two $10 bills and two $5 bills like so:</p>
<p><img src="/images/articles/payjoin-better-future/10-5-tx.webp" alt="Restaurant payment" /></p>
<p>You could even use your six $5 bills:</p>
<p><img src="/images/articles/payjoin-better-future/5-5-tx.webp" alt="Restaurant payment" /></p>
<p>Before they’re spent on something, the individual bitcoin “bills” you have are called <em>unspent transaction outputs</em>, or UTXOs. This name is confusing at first, but makes perfect sense if you think about it. They are the “results” (<em>outputs</em>) of transactions that haven’t yet been used up by <em>other</em> transactions. A transaction output that is <em>unspent</em> is a transaction output that you <em>can spend</em>. Therefore, in effect, UTXOs become like the bills of cash in your wallet. After they’re spent, they become inputs to another transaction’s outputs (the cash in someone else’s wallet), and are no longer spendable by you, but the <em>record</em> of the fact that you spent that bill to them remains on the blockchain.</p>
<p>Unlike cash, for a Bitcoin transaction to be valid, we need the approval of the sender. This is done with the sender’s <em>digital signature</em>, which serves as proof of their intent to spend the coin. A valid signature (that is, a signature corresponding to the address of the UTXO) needs to be present on each UTXO that the sender is trying to spend as an input to a new transaction. The presence of a signature “unlocks” the UTXO and signals that it was its owner’s intent to spend it in the proposed transaction.</p>
<p>Here is an example of a real transaction that had 1 confirmation while writing this:</p>
<p><img src="/images/articles/payjoin-better-future/real-transaction-1.webp" alt="Example of a real transaction with 1 input and three outputs, one being a fee" /></p>
<p><a href="https://mempool.space/tx/6a1f3e9b12a3e4f947b471a290c8c90681e1fe6e9869245dbc253b4015dc3bf6">(Link)</a></p>
<p>As we can see, the above transaction consumes one input and creates two outputs, one being the actual payment, and the other almost certainly being change sent back to the spender. A fee equal to the sum of the inputs minus outputs is also paid to the miner.</p>
<p>This “UTXO model” is very powerful. Since every transaction has inputs and outputs, and since the outputs of one transaction become the inputs of another transaction later, we end up with a long chain of transactions, and are able to track bitcoins’ transfer of ownership from transaction to transaction all the way back to a miner. Since Bitcoin has a limited supply, and derives its crucially non-inflationary properties from this fact, it’s important to be able to audit how much is in circulation or “unspent” at any given time, which the UTXO model allows for.</p>
<p>This is the crux of the privacy problem with Bitcoin. <em>Every transaction has a history</em>. All the bitcoin that came to you and anywhere you send it to can be <em>easily</em> tracked. The system was explicitly designed to support this, albeit not with the intention of tracking individuals. In this system, your only true bet is to never link your identity with your public key, which, in our age of mass surveillance, can be very hard.</p>
<h2 id="the-historical-origins-of-payjoin">The Historical Origins of Payjoin<a href="#the-historical-origins-of-payjoin" aria-label="Link to heading 'The Historical Origins of Payjoin'" data-heading-content="The Historical Origins of Payjoin" class="anchor"></a></h2>
<h3 id="satoshis-modest-mistake">Satoshi’s Modest Mistake<a href="#satoshis-modest-mistake" aria-label="Link to heading 'Satoshi’s Modest Mistake'" data-heading-content="Satoshi’s Modest Mistake" class="anchor"></a></h3>
<p>When Satoshi published the <a href="https://bitcoin.org/bitcoin.pdf">whitepaper</a> in 2008, he recognized the privacy problems that came from the requirement of announcing every transaction to the public, and keeping it private.</p>
<p>He had two recommendations to avoid linking an identity with transactions:</p>
<ol>
<li>Keep public keys anonymous</li>
<li>Don’t reuse public keys</li>
</ol>
<p>This is good advice, but for 1) it is very difficult to keep our identities completely isolated from our payments without extreme caution when making payments online. And for 2) even without public key reuse, if transactions spending from multiple keys are spent together in subsequent payments, it is not too difficult for a tracker to put together which keys belong to which person. These suggestions, even when combined, make for a very difficult to enact and imperfect privacy solution.</p>
<p>After these suggestions, Satoshi then makes a modest mistake, exaggerating the weakness of his own system:</p>
<blockquote>
<p>As an additional firewall, a new key pair should be used for each transaction to keep them from being linked to a common owner. Some linking is still unavoidable with multi-input transactions, which necessarily reveal that their inputs were owned by the same owner. The risk is that if the owner of a key is revealed, linking could reveal other transactions that belonged to the same owner.</p>
</blockquote>
<p>Satoshi’s assumption, and indeed, all the examples shown so far, presume that all the inputs in a transaction belong to the same owner. In other words, it assumes all the “bills” come from your wallet, which is a fair assumption, but one that isn’t <em>necessarily</em> true. This assumption is called the <a href="https://en.bitcoin.it/wiki/Common-input-ownership_heuristic">common input ownership heuristic</a>. It is almost always true for any given transaction, and it is the basis of chain surveillance.</p>
<h3 id="coinjoin">Coinjoin<a href="#coinjoin" aria-label="Link to heading 'Coinjoin'" data-heading-content="Coinjoin" class="anchor"></a></h3>
<p>In early 2013, Gregory Maxwell played a fun game in the <a href="https://bitcointalk.org/index.php?topic=139581.0">bitcointalk.org forum</a>. He provided one of his UTXOs (worth 1 BTC) and an address of his, and asked people to create new transactions using that UTXO as an input. If they proposed a transaction where they sent less than 1 BTC back to him, that meant they were trying to take from him, if more, they were giving to him. If they used the transaction to send exactly 1 BTC back to him, however, they were simply using his address in the transaction to increase privacy by making it <em>look</em> as though it was one of the spender’s UTXOs, even though it wasn’t. When one of Maxwell’s inputs was consumed and sent back to one of his addresses, he posted another one so someone could continue the game. From the perspective of a chain analysis company, this would have the effect of making Maxwell seem rich! Since his address was public, and many UTXOs were being used to construct transactions involving him, anyone analyzing the transactions and making the assumption that all inputs in a transaction were owned by the same person would think Maxwell was transacting with far more bitcoin than he actually was, hence the post’s title: “I taint rich!”.</p>
<p>Of course, this game wasn’t really private, since Maxwell posted his address in a public forum, but it proved a very important and consequential concept. As Maxwell states:</p>
<blockquote>
<p>A lot of people mistakenly assume that when a transaction spends from multiple addresses all those addresses are owned by the same party. This is commonly the case, but it doesn’t have to be so: people can cooperate to author a transaction in a secure and trustless manner.</p>
</blockquote>
<p>In a <a href="https://bitcointalk.org/?topic=279249">later post</a> that same year, Maxwell formalizes this idea into a concept he called Coinjoin:</p>
<blockquote>
<p>When considering the history of Bitcoin ownership one could look at transactions which spend from multiple distinct scriptpubkeys as co-joining their ownership and make an assumption: How else could the transaction spend from multiple addresses unless a common party controlled those addresses?</p>
<p>[…]</p>
<p>This assumption is incorrect. Usage in a single transaction does not prove common control (though it’s currently pretty suggestive), and this is what makes CoinJoin possible:</p>
<p>The signatures, one per input, inside a transaction are completely independent of each other. This means that it’s possible for Bitcoin users to agree on a set of inputs to spend, and a set of outputs to pay to, and then to individually and separately sign a transaction and later merge their signatures. The transaction is not valid and won’t be accepted by the network until all signatures are provided, and no one will sign a transaction which is not to their liking.</p>
</blockquote>
<p>This means that, in effect, an arbitrary number of people can coordinate to create transactions each contributing and signing their own inputs, without risk of theft by others <em>at any point</em>.</p>
<p>He then highlights another benefit of Coinjoin transactions, which is that transactions can be <em>batched</em> to save on fees by finding someone else who wants also wants to make a payment at the same time as you:</p>
<blockquote>
<p>The idea can also be used more casually. When you want to make a payment, find someone else who also wants to make a payment and make a joint payment together. Doing so doesn’t increase privacy much, but it actually makes your transaction smaller and thus easier on the network (and lower in fees); the extra privacy is a perk.</p>
</blockquote>
<p>And finally, Coinjoin is a protocol such that, if enough people use it, everyone wins, resulting in privacy gains for everyone:</p>
<blockquote>
<p>Such a transaction is externally indistinguishable from a transaction created through conventional use. Because of this, if these transactions become widespread they improve the privacy even of people who do not use them, because no longer will input co-joining be strong evidence of common control.</p>
</blockquote>
<p>To provide a concrete example, say we find three people who want to do a coinjoin. Beforehand, they agree to mix 0.1 bitcoin, gaining privacy by having three equal-sized outputs to make it unclear which address owns which coins. The change addresses will be clear to an analyst, but the 0.1 values will not.</p>
<p><img src="/images/articles/payjoin-better-future/coinjoin-tx.webp" alt="Coinjoin Example" /></p>
<p>The privacy gains aren’t necessarily very strong with only three participants, especially if the other participants de-anonymize in later transactions by tying them to their identity, but this can be mitigated with multiple rounds of coinjoin on those coins or by using larger anonymity sets.</p>
<p>To summarize, <strong><em>Coinjoin is a transaction created with inputs and outputs from multiple parties, such that it is difficult to tell who owns which coins after the transaction.</em></strong></p>
<p>For a deeper dive into how to actually create a Coinjoin and what tools are available, see <a href="https://bitcoinmagazine.com/technical/a-comprehensive-bitcoin-coinjoin-guide">this guide</a>.</p>
<p>Coinjoin is one of the most effective and widely adopted privacy solutions for Bitcoin today, but it has some major drawbacks:</p>
<ol>
<li><b>Interactivity</b>: Coinjoin requires a high degree of interaction from the participants; they need to agree to a uniform output size and must all supply their signatures within a reasonable time. High interactivity requirements create friction for users, and therefore hinder adoption by wider audiences.</li>
<li><b>Centralized coordinators</b>: Wasabi and Whirlpool are currently the most popular methods of Coinjoining. They take fees for conducting the coordination, in addition to the miner fees paid just to participate in the transaction (since Coinjoin transactions have a lot of signature data, they are more expensive). JoinMarket is an example of a non-coordinated service, but this has the tradeoff of increased interactivity.</li>
<li><b>Multiple rounds required for enhanced privacy</b>: To get better privacy, it is often recommended to Coinjoin multiple rounds due to the limitations of small anonymity set sizes, which costs time, increases interactivity, and costs more in fees.</li>
<li><b>Coinjoins look different from normal transactions</b>: Coinjoin transactions have a distinctive, recognizable pattern: multiple inputs from multiple parties resulting in multiple <em>uniformely sized</em> outputs. This means that if your coins have been identified prior to you doing the Coinjoin, the snooper will know you’ve Coinjoined. They may not know where the money went or what you do with it after you Coinjoin, but they know how much you had and that you did a Coinjoin in the first place.</li>
</ol>
<p>Clearly, this is not an end-all be-all solution for Bitcoin privacy due to these limitations, especially for more passive Bitcoiners who want a privacy-by-default approach.</p>
<p>After a few years, a better solution was on the horizon, one that could be done without <em>any</em> extra steps having to be taken by the transactees, was directly peer-to-peer with no centralized coordinators or marketplaces (therefore saving time and money), and looked no different from normal transactions: Payjoin.</p>
<p>Payjoin has been developed from a series of earlier innovations, let’s take a look at those.</p>
<h2 id="bip-21">BIP-21<a href="#bip-21" aria-label="Link to heading 'BIP-21'" data-heading-content="BIP-21" class="anchor"></a></h2>
<p>An important user experience (UX) improvement in early Bitcoin was BIP-21. <a href="https://github.com/bitcoin/bips/tree/master">BIP</a> stands for Bitcoin Improvement Proposal, and defines a set of standards that either require consensus changes to the Bitcoin protocol (e.g. hard forks or soft forks) or provide information and methods otherwise useful to interacting with Bitcoin.</p>
<p>BIP-21 is a standard that defines a scheme using URIs to simplify user interaction with Bitcoin, allowing them to pay by clicking links or scanning QR codes. A few query parameters, such as <code>amount</code>, <code>label</code>, and <code>message</code> are also defined so that client software can easily access and parse them for better UX. <a href="https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki">Here is an example</a> BIP-21 URI with those parameters:</p>
<p><img src="/images/articles/payjoin-better-future/bip21-message.webp" alt="BIP-21 Simple" /></p>
<p>Importantly, the standard is extensible, as custom query parameters can be created and new standards can be built on top of it. For example, in the Lightning Network, the custom parameter <code>lightning</code> can be added <a href="https://bitcoinqr.dev/">in addition</a> to the Bitcoin address so that the user can pay with either method:</p>
<p><img src="/images/articles/payjoin-better-future/bip21-lightning.webp" alt="BIP-21 with Lightning" /></p>
<p>This powerful and flexible BIP would prove useful in combination with concepts from Coinjoin.</p>
<h3 id="pay-to-endpoint-p2ep">Pay-to-Endpoint (P2EP)<a href="#pay-to-endpoint-p2ep" aria-label="Link to heading 'Pay-to-Endpoint (P2EP)'" data-heading-content="Pay-to-Endpoint (P2EP)" class="anchor"></a></h3>
<p>The <a href="https://blog.blockstream.com/en-improving-privacy-using-pay-to-endpoint/">earliest mention</a> of the Payjoin concept I could find was by Blockstream, published in August 2018, where the article <a href="https://nopara73.medium.com/pay-to-endpoint-56eb05d3cac6">references a workshop</a> from which the concept emerged. It referred to the resulting idea as Pay-to-Endpoint (P2EP), because it combined the concepts of Coinjoin and BIP-21 to allow a sender and a receiver to collaboratively contribute inputs to a transaction via a BIP-21 compliant endpoint provided by the receiver. Here is an example given in the article of what an endpoint provided by the receiver may look like:</p>
<p><img src="/images/articles/payjoin-better-future/p2ep.webp" alt="P2EP Example" /></p>
<p>Of note in particular is the <code>p2ep</code> parameter, whose value is an endpoint (in this case a <code>.onion</code> address, but it could just as easily be an <code>http://</code> address or any other compatible endpoint), which signals to receiving wallets that the sender would like to try a P2EP payment. If that fails, wallets should fall back to the sender paying the address normally, just using the sender’s inputs.</p>
<p>Because the contribution of inputs are collaborative in P2EP, and don’t result in the “coin-tainting” uniform outputs that Coinjoin creates, Payjoin transactions are much more difficult to identify.</p>
<p>The idea was a major step in the right direction, but the idea was still nascent and unformalized, and had some additional complexity to be removed.</p>
<h4 id="aside---satoshis-pay-to-ip-p2ip">Aside - Satoshi’s Pay-to-IP (P2IP)<a href="#aside---satoshis-pay-to-ip-p2ip" aria-label="Link to heading 'Aside - Satoshi’s Pay-to-IP (P2IP)'" data-heading-content="Aside - Satoshi’s Pay-to-IP (P2IP)" class="anchor"></a></h4>
<p>A variation of this idea, called <a href="https://www.reddit.com/r/Bitcoin/comments/4isxjr/comment/d30y3k4/">Pay-to-IP</a>, was actually implemented by Satoshi in the <em>very first</em> version of Bitcoin. There were, however, major privacy concerns with this approach, and so it was abandoned in subsequent versions of Bitcoin.</p>
<h3 id="bustapay">Bustapay<a href="#bustapay" aria-label="Link to heading 'Bustapay'" data-heading-content="Bustapay" class="anchor"></a></h3>
<p>Later that same month, Ryan Havar emailed a <a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-August/016340.html">revised version</a> of the idea to the Bitcoin developer mailing list, and formalized it into a BIP which he called <a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-August/016340.html">Bustapay</a>. This version simplified the initial P2EP protocol and removed some of the complexity in favor of simplicity, the idea being that simplicity would prove essential for adoption.</p>
<p>The Bustapay proposal still had a few major issues that needed refinement, and the protocol was not as refined as it could have been. But it was a further step in the right direction, and its focus on simplicity for the purposes of wallet integration was a key one, especially for the slow-moving and cautious Bitcoin developer ecosystem. Although Bustapay never took off, it was a final precursor to the Payjoin proposal we have today, which is ripe for wallet integration, and positive transformation of on-chain transactions.</p>
<h3 id="the-payjoin-proposal">The Payjoin Proposal<a href="#the-payjoin-proposal" aria-label="Link to heading 'The Payjoin Proposal'" data-heading-content="The Payjoin Proposal" class="anchor"></a></h3>
<p>Finally, by mid-2019, the concepts for Bustapay and P2EP were further refined and added to by Nicolas Dorier, founder of BTCPayServer, and Kukks, with the publication of <a href="https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki">BIP-78</a>, titled “A Simple Payjoin Proposal”.</p>
<p>With our background of the protocols leading up to payjoin, the meaning and purpose of the opening abstract of the proposal should now be clear:</p>
<blockquote>
<p>This document proposes a protocol for two parties to negotiate a coinjoin transaction during a payment between them.</p>
</blockquote>
<p>This proposal laid out, in much more rigorous detail than prior methods, how to conduct a coinjoin transaction between a sender and a receiver in a way that breaks the common-input ownership heuristic that is simple, flexible, and cheap.</p>
<h2 id="how-payjoin-works">How Payjoin Works<a href="#how-payjoin-works" aria-label="Link to heading 'How Payjoin Works'" data-heading-content="How Payjoin Works" class="anchor"></a></h2>
<p>Let’s say Alice wants to pay Bob 1.1 BTC, and a chain surveillance company sees a transaction like this:</p>
<p><img src="/images/articles/payjoin-better-future/regular-tx.webp" alt="Example assuming common-input ownership heuristic" /></p>
<p>They might assume that Alice paid Bob the 0.5 BTC output and gave the rest to herself as change, like this:</p>
<p><img src="/images/articles/payjoin-better-future/regular-tx-named.webp" alt="Example that assumes the common-input ownership heuristic was used" /></p>
<p>And the vast majority of the time, they’d be right! After all, normally the change is the larger amount, and 0.5 is more “round” and thus likely to be used for payment, than 1.1.</p>
<p>They might also wonder why she used an unnecessary input (the 0.8 and 0.3 BTC inputs would’ve sufficed), but they can never know for sure that this wasn’t a normal transaction, and they can’t know for sure why an extra UTXO was used. She could have just been consolidating a UTXO for easier management later. It <em>could</em> be a payjoin, but even if you assumed it was, which UTXO’s are Alice’s and which are Bob’s? It’s impossible to know. Since most transactions <em>aren’t</em> payjoins, they’ll probably make the faulty assumption that it wasn’t.</p>
<p>Alice, however, is smart and wants to preserve her privacy, and she knows about payjoin, so she asks Bob to contribute one of his own inputs to the transaction. Bob agrees, so he creates a transaction that spends one (or more) of his UTXOs as an input, and proposes it to Alice. If the transaction looks good to Alice, she broadcasts the payjoin transaction to the network. In this case, the transaction actually looks like this:</p>
<p><img src="/images/articles/payjoin-better-future/payjoin-tx-named.webp" alt="Example payjoin transaction" /></p>
<p>If chain surveillance made the assumption that both inputs were owned by Alice (as they did in the first example, and currently do in practice), their assumptions about what both Alice and Bob own would be wrong!</p>
<p>It’s also interesting to note that Alice and Bob conducting this transaction helps everybody gain privacy. Because, unlike coinjoin, it looks like a normal transaction, if enough people use payjoin chain surveillance will be unable to trust whether any transaction is normal. By foiling chain surveillance’s attempts at spying on their transaction, Alice and Bob make <em>every</em> transaction a little bit suspect. If there are enough people doing it, it makes them all suspect. Online privacy is often a numbers game, and the more people participate, the better the privacy.</p>
<p>In this example, Alice and Bob collaborated to create a transaction that used both of their inputs to preserve privacy. Now, of course, this whole process can be automated (and <em>is</em> in practice).</p>
<p>In BIP-78, the process is more formally defined as follows:</p>
<ol>
<li>A receiver shows the sender a BIP-21 URI with a query parameter <code>pj=</code> that refers to an endpoint/server people can send <a href="https://bitcoinops.org/en/topics/psbt/">partially signed bitcoin transactions</a> (PSBTs) to. The endpoint can be HTTPs, .onion, or any other authenticated encryption protocol. For example:</li>
</ol>
<p><img src="/images/articles/payjoin-better-future/pj.webp" alt="Example PJ" /></p>
<ol start="2">
<li>
<p>The sender creates a finalized, broadcastable PSBT using only their own inputs that is fully capable of making the required payment, to the receiver’s endpoint. This is called the “Original PSBT”.</p>
</li>
<li>
<p>The receiver modifies the PSBT to include his own inputs, signs his inputs, and gives this back to the sender. He does not modify any of the inputs or outputs of the sender. This is called the “Payjoin Proposal”.</p>
</li>
<li>
<p>The sender verifies the proposal, re-signs her inputs to finalize the transaction, and broadcasts it to the network.</p>
</li>
</ol>
<p>If something goes wrong at any point in the process, such as, for example, the receiver doesn’t have any UTXOs with which to create the Payjoin Proposal, then the receiver simply broadcasts the Original PSBT and it goes though as a normal transaction. Although this uses all inputs from the same owner, again, if enough people are using payjoin, it becomes impossible to make the assumption that the two parties <em>didn’t</em> payjoin, and surveillance will simply have to assume they did and try to find other methods of tracking payments.</p>
<h2 id="payjoins-many-benefits">Payjoin’s Many Benefits<a href="#payjoins-many-benefits" aria-label="Link to heading 'Payjoin’s Many Benefits'" data-heading-content="Payjoin’s Many Benefits" class="anchor"></a></h2>
<h3 id="more-broken-surveillance-heuristics">More Broken Surveillance Heuristics<a href="#more-broken-surveillance-heuristics" aria-label="Link to heading 'More Broken Surveillance Heuristics'" data-heading-content="More Broken Surveillance Heuristics" class="anchor"></a></h3>
<p>The common-input ownership heuristic is not the only privacy-destroying assumption that payjoin breaks. BIP-78 identifies two other heuristics that can be used to identify owners:</p>
<ul>
<li>
<p>Change identification from the <code>scriptPubkey</code>:</p>
<p>In Bitcoin, <code>scriptPubKey</code> is the “locking script” that specifies the conditions under which Bitcoin can be spent. It’s called <code>scriptPubKey</code> because the locking condition requires a valid signature matching the public key (the address) to unlock it. In other words, only the person who controls the private key to a particular UTXO’s associated public key can unlock it.</p>
<p>There are multiple types of <code>scriptPubKey</code>’s in use, (P2PKH, P2WPKH, P2SH, P2TR). Typically, wallets use the same <code>scriptPubkey</code> for all transactions, and therefore, the <em>change</em> amount (the amount the sender sends back to themselves after consuming UTXOs as inputs) will likely have the same <code>scriptPubkey</code>, while outputs sent to the receiver are much more likely to have a different <code>scriptPubkey</code>. This means that UTXOs with the same <code>scriptPubkey</code> in a transaction <a href="https://en.bitcoin.it/wiki/Privacy">can be identified</a> as likely belonging to the spender, assuming the outputs sent to the receiver are different.</p>
<p>BIP-78 specifies a method to allow the receiver to only use <code>scriptPubkey</code>‘s matching the spender’s, breaking the above heuristic’s ability to tell which outputs are the change outputs.</p>
</li>
<li>
<p>Change identification and payment from round payment amounts:</p>
<p>Normally, payments made to peers have round amounts since it’s far more natural to transact with round numbers. If Bob is charging Alice (and they aren’t basing the bitcoin price on a direct conversion to a “rounder” fiat price), he’s more likely to charge her an amount like 0.00010000 as opposed to a non-rounded amount like 0.00010231, for simplicity. For transactions in which only one output is round, it’s very likely the case (for now) that the non-round output is the change output.</p>
<p>Payjoin also allows for this metric to be broken by describing a way for the receiver to add extra round outputs when constructing the proposal.</p>
</li>
</ul>
<h3 id="asymmetric-gains-by-uniting-with-a-bigger-crowd">Asymmetric Gains by Uniting with a Bigger Crowd<a href="#asymmetric-gains-by-uniting-with-a-bigger-crowd" aria-label="Link to heading 'Asymmetric Gains by Uniting with a Bigger Crowd'" data-heading-content="Asymmetric Gains by Uniting with a Bigger Crowd" class="anchor"></a></h3>
<p>As mentioned earlier, one of the main drawbacks with coinjoin from a privacy perspective is that: 1) coinjoins are easily distinguishable from other transactions and 2) few people do them relative to non-coinjoin transactions. This creates problems for the fungibility of Bitcoin, because it’s possible for people to mark coins as tainted, since some may have the ludicrous perception that the desire for privacy implies malicious intent. Of course, as <em>most</em> transactions, or even a threshold of transactions, preserve privacy, then they cease to stand out.</p>
<p>Payjoin appears like any other transaction, and therefore doesn’t draw attention. An external observer has no reason to even look at a payjoin more closely, because the payjoin shows no effort to obfuscate the payment and change amounts.</p>
<p>By appearing like any other transaction, even marginal gains in payjoin adoption mean that everyone’s privacy is harder to invade, because surveillance heuristics quickly become unreliable. Adam Gibson, a foundational contributor to JoinMarket and an expert in Bitcoin privacy, <a href="https://reyify.com/blog/payjoin">sums it up well</a>:</p>
<blockquote>
<p>If you’re even reasonably careful, these PayJoin transactions will be basically indistinguishable from ordinary payments.
[…]
Now, here’s the cool thing: suppose a small-ish uptake of this was publically observed. Let’s say 5% of payments used this method. The point is that nobody will know which 5% of payments are PayJoin. That is a great achievement […], because it means that all payments, including ones that don’t use PayJoin, gain a privacy advantage!</p>
</blockquote>
<h2 id="utxo-consolidation">UTXO Consolidation<a href="#utxo-consolidation" aria-label="Link to heading 'UTXO Consolidation'" data-heading-content="UTXO Consolidation" class="anchor"></a></h2>
<p>Clearly, payjoin and it’s predecessors were aimed at solving privacy problems. But there is a nice ancillary benefit to using payjoin, specified right in BIP-78 itself: UTXO consolidation.</p>
<p>Satoshi’s suggestion to use a new address for every received payment results in user’s wallets having many UTXOs to manage. When these UTXOs are all used together as inputs to new transactions (and assuming it’s not a coinjoin or payjoin), these transactions cost more in fees. Fees are calculated based on the size of the transaction in bytes, since block space is the scarce resource, so more inputs equals more fees for a single large transaction.</p>
<p>It should be noted that using payjoin for UTXO consolidation does not necessarily save on fees, because the expense of each UTXO for chain space still needs to be paid at some point. Rather, it spreads the fees out over time and gives an opportunity to batch the UTXO with the payment. Batching makes UTXO consolidation cheaper than doing it as its own transaction. It also makes for easier management of UTXOs, and less data stored on disk. In addition, it is possible for wallets to implement a way for a receiver to specify that they would like to consolidate their UTXOs when mining fees are low in advance, making UTXO consolidation an automatic and smoother process.</p>
<h2 id="lightning-and-payjoin-a-perfect-match">Lightning and Payjoin: A Perfect Match<a href="#lightning-and-payjoin-a-perfect-match" aria-label="Link to heading 'Lightning and Payjoin: A Perfect Match'" data-heading-content="Lightning and Payjoin: A Perfect Match" class="anchor"></a></h2>
<h3 id="opening-lightning-channels-with-payjoin">Opening Lightning Channels with Payjoin<a href="#opening-lightning-channels-with-payjoin" aria-label="Link to heading 'Opening Lightning Channels with Payjoin'" data-heading-content="Opening Lightning Channels with Payjoin" class="anchor"></a></h3>
<p>The <a href="https://lightning.network/lightning-network-paper.pdf">Lightning Network (LN)</a> is a second-layer solution built on Bitcoin that takes transactions off-chain to allow for near-instant, final settlements with far lower fees, tremendously increasing transaction throughput, improving privacy, and allowing for new use cases for Bitcoin such as <a href="https://brandonlucas.net/articles/bitcoin/micropayments">micropayments</a>. It uses a network of payment channels between nodes to route payments from source to destination. These channels require node operators to lock up “liquidity” (bitcoin that can flow between one node and its channel partner) between their channel partners. How much bitcoin you can spend in a channel is limited by how much liquidity exists on your side of that channel.</p>
<p>Most of the complexity in managing a lightning node comes from opening these channels and managing the liquidity in them. Onboarding is one of the biggest pain points, since there are several steps involved. Let’s say Alice wants to open up a channel with Bob and she has setup a brand-new, unfunded lightning node. Alice has to do the following:</p>
<ol>
<li>
<p>Send an onchain transaction funding her newly created lightning wallet with sufficient funds to at least open the channel and wait (at least 10 minutes) for it to be confirmed.</p>
</li>
<li>
<p>Use her lightning wallet software to negotiate a channel opening transaction with Bob and wait for it to be confirmed.</p>
</li>
</ol>
<p>At a minimum, Alice has to pay two fees and wait ~10 minutes per transaction, which can be tedious.</p>
<p><img src="/images/articles/payjoin-better-future/lightning-open-channel.webp" alt="Lightning Channel Open Process" /></p>
<p>Payjoin can simplify this process and help Alice save money by doing both the funding and the opening transaction at once.</p>
<p>In this scenario, Alice preconfigures her payjoin receiving endpoint with the details of how she’ll open channels: including the amount of bitcoin and which channel partners she’ll open to. Then, using a wallet with payjoin support, someone (presumably Alice) will send a payjoin proposal, negotiate the payjoin transaction with the receiving endpoint, and upon receiving the funds, the endpoint will make the necessary API calls to open a channel with Bob’s node.</p>
<p>In other words, the sender (probably Alice in this case) will send a payjoin proposal to Alice’s receiving payjoin endpoint to create a transaction that pays directly to the 2-of-2 multisignature output between Bob’s node and Alice’s node to construct a lightning channel between them. Which turns the process into a one-step transaction:</p>
<p><img src="/images/articles/payjoin-better-future/lightning-open-payjoin.webp" alt="Lightning Channel with Payjoin" /></p>
<p>One interesting thing to note is that both LN and payjoin currently have a <em>liveness</em> requirement (though, in the case of payjoin at least, perhaps <a href="https://gist.github.com/DanGould/243e418752fff760c9f6b23bba8a32f9">not for long</a>), meaning that participants must be online when transactions take place. This is fairly limiting in comparison to on-chain Bitcoin, which only requires the sender to be online <em>at the time of payment</em>. However, this also allows for the two protocols to pair together quite nicely.</p>
<p>For example, Lightning is great for increasing privacy by taking payments off-chain, and for massively increasing Bitcoin’s ability to be used as a medium-of-exchange (i.e. something you would actually use to buy everyday things) in addition to a store of value. But the requirement to open channels on-chain means that the size of your channels and the people you open channels to leaves an onchain footprint. For reasons already discussed, payjoin can obfuscate and destroy many assumptions made by snoops.</p>
<p>It also makes things <em>simpler</em> because users now only have to make one payment instead of two to open their first channel, <em>faster</em> because they only have to wait for one transaction to be confirmed, and <em>cheaper</em> because they only have to pay one fee. In fact, more than one channel can be opened by these means. What if you could make a list of all the nodes you wanted to open channels to, make a single transaction to a BIP-21 payjoin receiving endpoint, and have them all open at once, automatically, waiting for one transaction to confirm and paying one fee. <em>For all your channels</em>. Well, you can!</p>
<p>There is already a project which implemented this idea, called <a href="https://github.com/payjoin/nolooking">Nolooking</a>, which allows you to queue up a list of public keys and <em>batch</em> open multiple lightning channels at once! This way, Alice can not only open a channel up to Bob, but she can open up channels to Bob, Carol, and Dina, all the while making <em>just one on-chain payment</em>! Needless to say, the potential for this to simplify Lightning UX is huge. It’s exciting to imagine a future in which Lightning wallets just have payjoin on by default, and the <em>de facto</em> user experience is to just pick your channel partners, make a single Bitcoin transaction, and <em>voila</em>! You now have a lightning node with as many channels as you can afford, and paid <em>one</em> transaction for it. How amazing would that be?</p>
<p>It’s easy to imagine how this could simplify self-custodial lightning adoption. It would be interesting if lightning wallet software could just have a “quick setup” option, where all the user has to do is input how much Bitcoin they can lock up (i.e. how much liquidity they want), set to a default of opening a few, reasonably sized channels with decent tradeoffs for routing and fees. For advanced users, there could be an “I know what I’m doing” option.</p>
<h2 id="weaknesses">Weaknesses<a href="#weaknesses" aria-label="Link to heading 'Weaknesses'" data-heading-content="Weaknesses" class="anchor"></a></h2>
<p>As with any protocol, there are tradeoffs to payjoin.</p>
<p>One of the main issues is the liveness (or online-ness) requirement. The receiver’s payjoin web server must be online at the time the transaction is sent in the current implementation, due to the need for both sender and receiver to negotiate (programmatically, of course) the resulting transaction. This might limit adoption to merchant servers or lightning nodes, as they are the only people with an incentive to always stay online. It would be much more convenient from a user’s perspective if a transaction could be sent at any time irrespective of whether the receiving server is online.</p>
<p>Another less likely but more dangerous weakness is that if a payjoin server (i.e. the receiver’s server) is on an unsecured server, the receiver’s outputs could be <a href="https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#unsecured-payjoin-server">modified in flight</a>(before they reach the sender) and stolen.</p>
<p>However, as we’ll discuss later, a solution to the above two problems has already been proposed.</p>
<p>Finally, one weakness of the protocol is simply the fact that it faces adoption barriers as wallets must do the work to integrate it. A particular challenge is that the ideal user interface would be one in which payjoin is just enabled by default. Sending wallets and receiving wallets just try to payjoin without a user necessarily needing to be burdened with setting up that privacy feature. The best privacy is default privacy, since requiring users to take action to defend it means they probably won’t. Therefore, for payjoin adoption to really take off for the average user, it needs to be a seamless experience that they don’t have to think about. Wallets should enable it by default. Remember, it’s built right into the protocol to fallback on regular transactions without requiring any additional action from the user in case the payjoin fails.</p>
<h3 id="serverless-payjoin">Serverless Payjoin<a href="#serverless-payjoin" aria-label="Link to heading 'Serverless Payjoin'" data-heading-content="Serverless Payjoin" class="anchor"></a></h3>
<p>Dan Gould has submitted a <a href="https://gist.github.com/DanGould/243e418752fff760c9f6b23bba8a32f9">draft BIP</a> for a version 2 of payjoin which allows it to be done <em>asynchronously</em> and without using a server. This <em>serverless payjoin</em> would solve both the requirement for a receiver to be online at the time of payment, and the security issues related to this being done on unsecured servers, mentioned earlier. As the always-online nature of payjoin receivers is perhaps the biggest user experience barrier to adoption, the implementation of this BIP could be a huge benefit for payjoin adoption and passive Bitcoin privacy.</p>
<h2 id="the-current-state-of-payjoin-adoption">The Current State of Payjoin Adoption<a href="#the-current-state-of-payjoin-adoption" aria-label="Link to heading 'The Current State of Payjoin Adoption'" data-heading-content="The Current State of Payjoin Adoption" class="anchor"></a></h2>
<p>As of late 2023, payjoin adoption is still relatively low, but has been steadily increasing since its inception in 2018.<sup><a href="https://payjoin.substack.com/p/tracking-growth-in-payjoin-adoption">1</a></sup> Since payjoin is ready to use today and doesn’t require any Bitcoin consensus changes, the only real impediment is for wallets to write the software to adopt it, and the tools to help them do so are getting better every day. <a href="https://payjoindevkit.org/introduction/">Payjoin Dev Kit (PDK)</a> is a new payjoin implementation with modules that wallet software can use to integrate today. It even comes with a <code>payjoin-cli</code> tool that allows you to create payjoins from the command line. The library is written in Rust, but bindings allowing other languages to use it are currently being built.</p>
<h3 id="wallet-support">Wallet Support<a href="#wallet-support" aria-label="Link to heading 'Wallet Support'" data-heading-content="Wallet Support" class="anchor"></a></h3>
<p>BTCPayServer and JoinMarket already support both sending and receiving, although not by default. BlueWallet, Sparrow, Wasabi, and BitMask support sending. A few other wallets support it via an extension, <a href="https://github.com/payjoin/rust-payjoin">including Bitcoin Core</a>. There are active PRs to integrate payjoin into Mutiny Wallet today. See <a href="https://en.bitcoin.it/wiki/PayJoin_adoption">here</a> for the current state of payjoin adoption.</p>
<h2 id="payjoin-and-bitcoins-future">Payjoin and Bitcoin’s Future<a href="#payjoin-and-bitcoins-future" aria-label="Link to heading 'Payjoin and Bitcoin’s Future'" data-heading-content="Payjoin and Bitcoin’s Future" class="anchor"></a></h2>
<p>As mentioned earlier, Adam Gibson has proposed that even just 5% of on-chain transactions being conducted with payjoin could have a huge impact on Bitcoin privacy. We just need a threshold number large enough that analysis companies can never safely assume they’re interpreting the transactions correctly. Once their methods of spying on us are broken, then ill-informed, arbitrary, and bad-faith restrictions on Bitcoin privacy imposed by those who neither understand its benefits nor have any interest in preserving our right to privacy will become irrelevant.</p>
<p>And as we’ve seen, due to the many possibilities opened up by payjoin’s flexibility, it is not merely a privacy solution, but an extensible cooperative transaction protocol that allows for solutions such as fee reduction and single transaction multi-channel opening for lightning nodes, among others. The benefits it would provide to Bitcoin could be enormous and achieved today without changing anything about Bitcoin itself.</p>
<p>So what are we waiting for?</p>
<h2 id="support">Support<a href="#support" aria-label="Link to heading 'Support'" data-heading-content="Support" class="anchor"></a></h2>
<p>If you’d like to support or contribute to payjoin’s development, start by joining the <a href="https://discord.gg/6rJD9R684h">discord</a>, <a href="https://geyser.fund/project/payjoin/">donating</a>, or checking out <a href="https://payjoin.org/">payjoin.org</a>.</p>
<h2 id="acknowledgements">Acknowledgements<a href="#acknowledgements" aria-label="Link to heading 'Acknowledgements'" data-heading-content="Acknowledgements" class="anchor"></a></h2>
<p>I want to give a huge thank you to Dan Gould for reviewing and suggesting very helpful improvements to this article.</p>
]]></content:encoded></item>
<item><title>Reading, Writing, and Civilization</title><link>https://blu.cx/posts/blog/2023-09-26-reading-writing-civilization/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2023-09-26-reading-writing-civilization/</guid><pubDate>Tue, 26 Sep 2023 12:00:00 +0000</pubDate><description>Gibbon on the importance of reading and writing to civilization, and what distinguished the Romans from the 'barbarians', i.e. the Germans</description><content:encoded><![CDATA[<p>The importance of reading seems to need more and more explaining these days as people convince themselves they have no need for it (or because they simply have a hard time sitting still). Every once in awhile I’ll stumble across yet another explanation for why reading, and by reading I mean reading <em>great books</em>, is so important.</p>
<p>Here’s Gibbon on the importance of reading and writing to civilization, and what distinguished the Romans from the “barbarians”, i.e. the Germans:</p>
<blockquote>
<p>The Germans, in the age of Tacitus, were unacquainted with the use of letters; and the use of letters is the principal circumstance that distinguishes a civilized people from a herd of savages incapable of knowledge or reflection. Without that artificial help, the human memory soon dissipates or corrupts the ideas intrusted to her charge; and the nobler faculties of the mind, no longer supplied with models or with materials, gradually forget their powers; the judgement becomes feeble and lethargic, the imagination languid or irregular. Fully to apprehend this important truth, let us attempt, in an improved society, to calculate the immense distance between the man of learning and the <em>illiterate</em> peasant. The former, by reading and reflection, multiplies his own experience, and lives in distant ages and remote countries; whilst the latter, rooted to a single spot, and confined to a few years of existence, surpasses, but very little, his fellow-labourer the ox in the exercise of his mental faculties. The same, and even a greater, difference will be found between nations than between individuals; and we may safely pronounce that, without some species of writing, no people has ever preserved the faithful annals of their history, ever made any considerable progress in the abstract sciences, or ever possessed, in any tolerable degree of perfection, the useful and agreeable arts of life.</p>
</blockquote>
<p>– <em>Decline and Fall of the Roman Empire, Ch. IX, p. 242</em></p>
]]></content:encoded></item>
<item><title>The First Monotheist</title><link>https://blu.cx/posts/blog/2023-09-21-worlds-first-monotheist/</link><guid isPermaLink="true">https://blu.cx/posts/blog/2023-09-21-worlds-first-monotheist/</guid><pubDate>Thu, 21 Sep 2023 12:00:00 +0000</pubDate><description>Zoroaster founded the world's first monotheistic religion, thereby opening a new chapter in world history.</description><content:encoded><![CDATA[<p>I’m continuing to read <a href="https://www.amazon.com/Decline-Empire-Volumes-Everymans-Library/dp/0307700763/ref=sr_1_5?crid=1G5JJM2BIH17N&amp;keywords=decline+and+fall+of+the+roman+empire&amp;qid=1695350465&amp;sprefix=decline+and+%2Caps%2C85&amp;sr=8-5&amp;ufe=app_do%3Aamzn1.fos.f5122f16-c3e8-4386-bf32-63e904010ad0"><em>Decline and Fall of the Roman Empire</em></a> by Edward Gibbon, and I learned another fun series of facts I thought worth writing about. I’d heard of <a href="https://en.wikipedia.org/wiki/Zoroastrianism">Zoroastrianism</a>, and knew that it had something to do with Persia, but I’d failed to remember from World History class (shocker!) that it was <em>the first</em> monotheistic religion. Given that most people today, if they believe (or more likely, <em>claim to believe</em>, in God) they tend to believe there’s only <em>one</em>. This is an extremely important development in the history of human thought, and may be owed to Zoroaster.</p>
<p>He’s an extremely ancient figure, so much so that no one really knows when he lived, typically placing him somewhere between 1500 to 1000 BC. Zoroastrianism was the religion of the ancient Persians, and dominated much of the Middle East for a millennium, until being overtaken by Islam.</p>
<p>Aside from many of its very interesting ideas about free will, heaven and hell, creation and anti-creation, etc., one idea I found especially refreshing to find in minds that <em>long</em> predate Jesus, Socrates, Confucius, and even the <a href="https://en.wikipedia.org/wiki/Thales_of_Miletus">first philosopher</a> – was the rejection of asceticism; the abstinence of sensual pleasures which often also leads to the withdrawal of societal participation and sense of social responsibility. Zoroastrianism apparently differs directly with the founders of many of our modern religions in this regard. To quote Gibbon:</p>
<blockquote>
<p>But there are some remarkable instances, in which Zoroaster lays aside the prophet, assumes the legislator, and discovers a liberal concern for private and public happiness, seldom to be found among the grovelling or visionary schemes of superstition. Fasting and celibacy, the common means of purchasing the Divine favour, he condemns with abhorrence, as a criminal rejection of the best gifts of Providence. The saint, in the Magian religion, is obliged to beget children, to plant useful trees, to destroy noxious animals, to convey water to the dry lands of Persia, and to work out his salvation by pursuing all the labours of agriculture.</p>
</blockquote>
<p>One may wonder how much pointless human suffering was caused by the elevation of self-inflicted pain to an act smiled upon by God. This isn’t to diminish the merits of well-directed sacrifice – a sacrifice to some social purpose – just sacrifice <em>for sacrifice’s sake</em>.</p>
<p>Continuing with Gibbon:</p>
<blockquote>
<p>We may quote from the Zendavesta a wise and benevolent maxim, which compensates for many an absurdity. ‘He who sows the ground with care and diligence, acquires a greater stock of religious merit, than he could gain by the repetition of ten thousand prayers.’</p>
</blockquote>
<p>While the rejection of asceticism doesn’t exist without the opposing danger of hedonism, and while I’m sure a closer look at Zoroastrianism would reveal many strange features, it is interesting to see a religion that <em>explicitly</em> celebrates the application of our efforts toward positive social utility. If you’re going to be judged by God, it’s nice to be <em>judged by whether you do good in the world</em>, instead of being judged simply by the degree to which you abstain from participating in its evils.</p>
]]></content:encoded></item>
<item><title>Micropayments and the Lightning Network</title><link>https://blu.cx/posts/articles/2023-03-10-micropayments/</link><guid isPermaLink="true">https://blu.cx/posts/articles/2023-03-10-micropayments/</guid><pubDate>Fri, 10 Mar 2023 12:00:00 +0000</pubDate><description>Content monetization on the internet is broken. Credit card fees prohibit the ability to make small payments for individual pieces of content. Creators have to choose between invading user privacy by selling their data, annoying them with a barrage of ads, or putting up paywalls and forcing them to sign up for subscriptions to continue. The Lightning Network offers a new, alternative monetization strategy in the form of micropayments.</description><content:encoded><![CDATA[<p><em>Content monetization on the internet is broken. Credit card fees prohibit the ability to make small payments for individual pieces of content. Creators have to choose between invading user privacy by selling their data, annoying them with a barrage of ads, or putting up paywalls and forcing them to sign up for subscriptions to continue. The Lightning Network offers a new, alternative monetization strategy in the form of micropayments.</em></p>
<p>Check out the <a href="https://voltage.cloud/">Voltage</a> presentation on this topic <a href="https://youtu.be/6Vq6foKst54">here</a>.</p>
<p><em>If you’re interested in learning more about the history of micropayments, check out <a href="https://caseorganic.medium.com/who-killed-the-micropayment-a-history-ec9e6eb39d05">Amber Case’s article</a> and the <a href="https://www.youtube.com/watch?v=9_6cLtpf3AE">Open Index Protocol’s episode</a></em></p>
<h2 id="before-bitcoin">Before Bitcoin<a href="#before-bitcoin" aria-label="Link to heading 'Before Bitcoin'" data-heading-content="Before Bitcoin" class="anchor"></a></h2>
<p>Before the World Wide Web, the internet, or even the personal computer revolution, the concept of making tiny payments in exchange for digital content had already been conceptualized. The term “micropayment” can be traced back to Ted Nelson as early as 1960, when he began brainstorming his ideas for what a globally accessible, computer-based repository of human knowledge could look like, and coined many other terms and ideas that are used on the web today, like “hypertext” and “hypermedia”. While often failing to implement his own vision for the web (and being vocally regretful of how the web took shape), others took many of his ideas and built them into what we know it as today.</p>
<p>In 1992, Tim Berners-Lee, creator of HTTP and HTML, released his <a href="https://www.w3.org/Protocols/HTTP/HTRESP.html">second version of HTTP</a> and the first reference to the status codes now in common use. Among them was a code that Berners-Lee and others thought would one day be used to pay for digital content: <code>402 payment required</code>. Sadly, this status code is officially <a href="https://www.rfc-editor.org/rfc/rfc7231#section-6.5.2">“reserved for future use”</a>, as the various attempts to make micropayments on the web, from its inception, failed to come to fruition. Over 30 years after the invention of the internet, we’re still waiting for one of its major original visions to be fulfilled.</p>
<p>Berners-Lee founded the World Wide Web Consortium (W3C) in 1994 to guide the development of the web, and micropayments were a major consideration from the start. In 1995, Phillip Hallam-Baker, who wrote a number of RFCs on internet security, drafted the <a href="https://www.w3.org/TR/WD-mptp">Micro Payment Transfer Protocol (MPTP)</a>, which never seems to have been implemented. It offers a number of insights on the nature of micropayments that are as relevant today as they were at the founding of the internet:</p>
<blockquote>
<p>There is a large interest in payment systems which support charging relatively small amounts for a unit of information. Here the speed and cost of processing payments are critical factors in assessing a schemes usability. Fast user response is essential if the user is to be encouraged to make a large number of purchases.</p>
</blockquote>
<p>A critical limitation of MPTP, however, was that a third party, referred to as a <em>broker</em>, was explicitly required by the protocol.</p>
<p>At the time, there was no way to make a digital payment without a trusted mediator, so any attempt at a protocol for micropayments had to be developed with some sort of custodianship of the money in mind.</p>
<p>The W3C continued to push for micropayments for a while, in 1998 publishing an <a href="https://www.w3.org/ECommerce/Micropayments/">overview</a> of micropayments and suggesting MPTP as a practical approach and noting:</p>
<blockquote>
<p>Micropayments have to be suitable for the sale of non-tangible goods over the Internet […] With the rising importance of intangible (e.g. information) goods in global economies and their instantaneous delivery at negligible cost, “conventional” payment methods tend to be more expensive than the actual product.</p>
</blockquote>
<p>This echoes Hallam-Baker’s second major concern of transaction costs imposed by technological or overhead costs of available payment mechanisms. His first concern, the need for a “fast user response”, is often overlooked in discussions about the viability of micropayments. But one astute observer, who would later go on to have a strong influence on the development of Bitcoin, was already thinking in depth about this problem. Nick Szabo, creator of the concept of smart contracts and Bitgold, wrote <a href="https://nakamotoinstitute.org/static/docs/micropayments-and-mental-transaction-costs.pdf">“Micropayments and Mental Transaction Costs”</a> in 1999. Szabo states that the <em>primary</em> reason for the failure of micropayments to take hold was not due to technological or overhead costs. It was due to the <em>mental transaction cost</em> experienced by the user of having to decide whether a purchase was worth the price for every interaction on the internet.</p>
<blockquote>
<p>[…] customer mental transaction costs are significant and ubiquitous, so much so that in real world circumstance cognitive costs usually well outweigh technological costs, and indeed technological resources are best applied towards the objective of reducing cognitive costs. […] Micropayment technology efforts which stress technological savings over cognitive savings will become irrelevant.</p>
</blockquote>
<p>A web based on micropayments implies frequent purchases, which implies decision fatigue. It is likely the case that for most micropayments, the mental transaction cost incurred by having to make constant choices about purchases likely outweighs the value of what they’re paying for.</p>
<p>Large companies like Compaq and IBM, and startups like Pay2See, Millicent, iPin, and others, tried their hand at reducing these technological and mental transaction costs for micropayments in the early days, and it was still an assumption that the concept would be a lasting feature from the start.</p>
<p>Perhaps the most notable of these companies, which would go on to have a lasting impact on the Bitcoin community, was DigiCash, spearheaded by David Chaum. Chaum had already formalized many ideas for a <a href="https://nakamotoinstitute.org/literature/computer-systems-by-mutually-suspicious-groups/">blockchain-like data structure</a> and <a href="https://nakamotoinstitute.org/literature/blind-signatures/">secure digital cash</a> in 1982 before going on to found DigiCash in 1989. DigiCash implemented Chaum’s proposals, allowing users to withdraw money (called “eCash”) from a bank and make untraceable digital micropayments with it. Unfortunately, only one bank ever implemented eCash, and in 1998 the company went bankrupt.</p>
<p>Other micropayment efforts were also dissolving around the same time, and the W3C itself <a href="https://www.w3.org/ECommerce/Micropayments/">closed their contributions to micropayment activity</a> in 1998.</p>
<p>The dot-com crash was in full swing, and micropayments were one of the ideas that crashed the hardest. It was a good time to be a critic. In 2000, writer Clay Shirky wrote <a href="http://mx.thirdvisit.co.uk/2002/10/04/theacaseaagainstamicropayments/">“The Case Against Micropayments”</a> in which he boldly declared:</p>
<blockquote>
<p>Micropayment systems have not failed because of poor implementation; they have failed because they are a bad idea. Furthermore, since their weakness is systemic, they will continue to fail in the future.</p>
</blockquote>
<p>His primary argument for their fundamental flaw was not technological or infrastructural — but instead echoed Nick Szabo one year prior: decision fatigue. He goes on:</p>
<blockquote>
<p>In particular, users want predictable and simple pricing. Micropayments, meanwhile, waste the users’ mental effort in order to conserve cheap resources, by creating many tiny, unpredictable transactions. Micropayments thus create in the mind of the user both anxiety and confusion, characteristics that users have not heretofore been known to actively seek out.</p>
</blockquote>
<p>Shirky went on to predict three methods of payment that would become dominant on the web which did not suffer from the decision fatigue problem: aggregation (bundling low-value things into a single higher-value transaction), subscription, and subsidy (getting someone other than the user to pay for the content — today this has manifested itself as the ad model).</p>
<p>By the end of the dot-com crash, Shirky’s predictions were looking more salient. Credit cards, with their infrastructure costs prohibiting payments of less than $1, had become the de-facto method of payment, and passion for the micropayments project was losing steam. What was a self-evident and exciting prospect for the web’s future faded against the backdrop of its increasingly centralized, surveilled, and ad-driven successor — Web 2.0.</p>
<h2 id="bitcoin-and-the-centralized-web">Bitcoin and the Centralized Web<a href="#bitcoin-and-the-centralized-web" aria-label="Link to heading 'Bitcoin and the Centralized Web'" data-heading-content="Bitcoin and the Centralized Web" class="anchor"></a></h2>
<blockquote>
<p>We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible.</p>
</blockquote>
<p>— Satoshi Nakamoto</p>
<blockquote>
<p>The idea driving 402 was that it’s obvious that support for payments should be a first-class concept on the web and it’s obvious that there should be a lot of direct commerce taking place on the web […] In fact, what emerged is a single dominant business model which is advertising. That leads to a lot of centralisation, because you get the highest cost per clicks and with the largest platforms.</p>
</blockquote>
<p>— John Collison, President of Stripe</p>
<p>Satoshi Nakamoto released the Bitcoin whitepaper near the end of 2008, aptly timed in the midst of the U.S. Housing Crisis. He released the original code for it shortly after. Bitcoin was a massive breakthrough, both in the history of computer science and the history of money, and spurred a new wave of interest in the possibilities of the internet. For the first time, there was a permissionless way to transfer value with an <em>internet native</em> currency, without all the inelegant, bloated infrastructure needed to use credit cards.</p>
<p>For awhile, the price of bitcoin was so small that some people did advocate its use for micropayment systems, despite Satoshi admitting that it wasn’t (yet) a great solution to that problem:</p>
<blockquote>
<p>Bitcoin isn’t currently practical for very small micropayments. Not for things like pay per search or per page view without an aggregating mechanism, not things needing to pay less than 0.01.</p>
</blockquote>
<p>But the limits imposed by fees didn’t stop people from dreaming about new possibilities enabled by it. Marc Andreessen, creator of the first popular web browser, <a href="https://archive.nytimes.com/dealbook.nytimes.com/2014/01/21/why-bitcoin-matters/">gave the examples</a> of content monetization and fighting spam:</p>
<blockquote>
<p>One reason media businesses such as newspapers struggle to charge for content is because they need to charge either all (pay the entire subscription fee for all the content) or nothing (which then results in all those terrible banner ads everywhere on the web). All of a sudden, with Bitcoin, there is an economically viable way to charge arbitrarily small amounts of money per article, or per section, or per hour, or per video play, or per archive access, or per news alert.</p>
</blockquote>
<p>This statement is not true today of course (at least, as far as Layer 1 is concerned), but the fees in 2014 were low enough that it was actually possible to build around the concept of micropayments. One interesting project built around this time was <a href="https://www.pcworld.com/article/447463/news-junkies-opensource-project-links-bitcoin-with-publishers.html">Bitmonet</a>, which allowed users to choose their subscription level by paying 10 cents for just one article, 15 cents for an hour of unlimited access to the website, or 20 cents for a day pass. Unfortunately transaction fees are <a href="https://bitinfocharts.com/comparison/bitcoin-transactionfees.html#alltime">no longer low enough</a> to be able to allow for arbitrarily small micropayments, and although the issue was clearly on Satoshi’s mind from the earliest days of Bitcoin, it wasn’t designed to specifically address the micropayment problem.</p>
<h3 id="subscriptions-and-ads">Subscriptions and Ads<a href="#subscriptions-and-ads" aria-label="Link to heading 'Subscriptions and Ads'" data-heading-content="Subscriptions and Ads" class="anchor"></a></h3>
<p>Shirky’s predictions for content monetization were pretty accurate, particularly when it came to subscription and ad models.</p>
<p>In the ad model, content was subsidized by an advertiser, typically through a third-party. From 2014 to 2022, Google and Facebook essentially <a href="https://www.ft.com/content/4ff64604-a421-422c-9239-0ca8e5133042">held a duopoly</a> over the online ad market as the third-party mediator between advertisers and content creators. The two companies (and in reality, most of big tech) <a href="https://www.security.org/resources/data-tech-companies-have/">collected enormous amounts of personal information</a> and simply asked their users to entrust them with the safety of their data, despite numerous breaches. This <a href="https://www.forbes.com/sites/forbestechcouncil/2022/02/16/what-does-big-tech-actually-do-with-your-data/?sh=6a79a424515f">information is used</a> to show targeted ads for products people are more likely to buy. Companies often refer to this model as “free — with ads”. But in reality, users do pay a price. The ad model forces users to trade two things in exchange for the content:</p>
<ol>
<li>Their data, forcing them to give it to third parties, which, as Nick Szabo famously stated, are <a href="https://nakamotoinstitute.org/literature/trusted-third-parties/">security holes</a>.</li>
<li>Their attention. There is a reason for the phrase “pay attention”, and it’s well illustrated here. More time users spend on a site with ads, the more money advertisers, ad platforms, and the content creator makes. Thus a creator is economically incentivized to show as many ads as they possibly can without annoying the user so much that they leave the platform. The currency of the “free with ads” web is your attention. You are the product.</li>
</ol>
<p>What’s clear in the ad model is that the <em>consumer</em> becomes a second-class citizen. Since a layer of abstraction exists between a creator’s revenue and the end user, making a great user experience is not the highest priority. And as more and more <a href="https://backlinko.com/ad-blockers-users">consumers use ad-blockers</a>, content creators are forced to get more aggressive with ads, making using the web a more degraded experience for everyone.</p>
<p>Subscriptions also rose in popularity. Customers showed they were much more willing to pay for bulk access to licensed content like movies and music on a regular basis instead of paying to own individual songs. Although a more honest business model, they can also be very problematic when they are the sole option for payment. And in recent years, as these services become increasingly competitive, more and more people find themselves suffering from subscription fatigue. The inability to access just one (or a few) specific news articles, movies, or songs at any given time forces a suboptimal choice of trying to pay in bulk and optimize the most content for a given subscription.</p>
<p>Take streaming services, for example. Today there are so many streaming services, all battling for content licensing, that users end up paying multiple subscriptions to try to capture a greater subset of the movies and TV shows they want. But all they <em>really</em> want is to watch an incredibly small subset of what’s offered by any given service. When they pick a service for a movie or show they want, it often doesn’t stay long, and will be bounced around unpredictably from company to company as licensing expires and gets updated.</p>
<p>News articles are another example. Companies like The New York Times or The Economist entice readers by allowing them to read just a few seconds of an article before blocking the content with a subscription paywall. Even more so with newspapers than with movies, customers are far more likely to want to pay a small amount for a single article of their choice than for a package deal to articles they don’t want.</p>
<p>While subscriptions offer a more straightforward approach than ads, using them in practice often makes for what is an increasingly costly and stressful management game.</p>
<p>When Clay Shirky was writing about the issues of mental transaction costs, he was writing before the mental costs of subscriptions and ads began to weigh down on people as they do today. Bitcoin gave a solution to the problem of an internet-native currency, but slow processing and high fees quickly became a prohibitive problem for a system supporting micropayments. One more major innovation was needed before true implementations of micropayments technology could take off.</p>
<h2 id="the-lightning-network">The Lightning Network<a href="#the-lightning-network" aria-label="Link to heading 'The Lightning Network'" data-heading-content="The Lightning Network" class="anchor"></a></h2>
<blockquote>
<p>A decentralized system is proposed whereby transactions are sent over a network of micropayment channels (a.k.a. payment channels or transaction channels) whose transfer of value occurs off-blockchain. — Joseph Poon and Thaddeus Dryja, The Bitcoin Lightning Network</p>
</blockquote>
<p>In February 2015, <a href="https://lightning.network/docs/">“The Bitcoin Lightning Network”</a> was published, representing perhaps the biggest advancement in Bitcoin since its inception. Through the clever use of Bitcoin properties, a series of payment “channels” could be constructed such that transactions did not have to be announced to the blockchain but could still retain the lack of trust in a third party, using the Bitcoin blockchain itself for dispute resolution. Payments can either be made directly between two channel partners, or they can be routed across a series of partners, so long as there exists a path of connected partners between sender and recipient. Because of this, it was no longer necessary to pay fees to miners for every transaction, and payments could be settled near-instantly. Since the primary reason micropayments weren’t viable on Bitcoin in the first place was due to high fees and slow transaction processing, the Lightning Network (abbreviated LN or called “lightning” for short) opened up a whole new world of possibilities that are just beginning to be explored.</p>
<h3 id="methods">Methods<a href="#methods" aria-label="Link to heading 'Methods'" data-heading-content="Methods" class="anchor"></a></h3>
<p>The smallest unit of bitcoin is a satoshi (often abbreviated to sat). One bitcoin is equal to 100,000,000 sat. At <a href="https://www.coinbase.com/converter/sats/usd">current conversion rates</a>, 1 satoshi is equal to $0.00023, meaning that the smallest payments that can be made (assuming no routing fees) are currently at a granularity of about <em>two ten-thousandths</em> of a penny, a finer granularity than was ever seriously considered by the web’s early founders. Technically, even finer granularity payments of a thousandth of a satoshi (called millisatoshi, abbreviated msat) can be made within the confines of the Lightning Network, but any sub-satoshi amounts will be rounded down if the money is ever moved back to the blockchain. Therefore, all of the methods of payment discussed below can be used to make micropayments, but different mechanisms allow for a variety of <a href="https://bitcoinmagazine.com/business/can-bitcoin-fix-micropayments">novel payment schemes</a> to be set up. I won’t go into the technical details of how the Lightning Network works here. My intention is to provide a high level overview of some ways micropayments can be made with it, and hopefully to spark people’s imaginations to the possibilities.</p>
<h3 id="invoice">Invoice<a href="#invoice" aria-label="Link to heading 'Invoice'" data-heading-content="Invoice" class="anchor"></a></h3>
<p>The most basic way to make a payment on the Lightning Network is through the <a href="https://www.bolt11.org/">Bolt-11</a> invoice. This is an encoded string of information containing everything necessary for the payment to be made. The invoice is generated by the recipient and then sent to the payee. Typically, from a user’s perspective, the payment string is encoded as a QR code which the payee can scan.</p>
<p><em>Benefits:</em></p>
<ul>
<li>Least abstractions from a technological level</li>
<li>Available by default in LN implementations</li>
<li>Payments occur directly node to node</li>
</ul>
<p><em>Drawbacks:</em></p>
<ul>
<li>A recipient having to first send an invoice can make for awkward UX</li>
<li>Both nodes must be online for a payment to be made</li>
<li>Invoices are non-reusable and must be freshly generated every time</li>
</ul>
<p>Most of the ways to pay on LN today either simply use Bolt-11 invoices or some abstraction of them under the hood.</p>
<h3 id="keysend">Keysend<a href="#keysend" aria-label="Link to heading 'Keysend'" data-heading-content="Keysend" class="anchor"></a></h3>
<p>An alternative to invoices are <a href="https://voltage.cloud/blog/bitcoin-lightning-network/keysend-payments-explained-voltage-technical-series/">keysend</a> payments, which allow for payments directly between two nodes without an invoice. Senders only need a node’s public key. One crucial downside to keysend is the lack of a proof of payment, making it unsuitable for merchants or other systems that rely on proof of purchase. For content monetization, however, keysend pairs nicely with the concept of <a href="https://value4value.info/">value4value</a>: a model which gives content away freely and asks the user to give back the value they felt they received in turn (and requires no proof of payment).</p>
<p><em>Benefits:</em></p>
<ul>
<li>No invoices required, payment can be sent instantly</li>
<li>Available by default in LND, Eclair, and Core-Lightning (but must be manually turned on)</li>
<li>Direct node to node payments</li>
</ul>
<p><em>Drawbacks:</em></p>
<ul>
<li>No proof of payment limits scope of use</li>
</ul>
<p><em>Possible micropayment use cases:</em></p>
<ul>
<li>Streaming sats (Boosting) while listening/watching/reading content</li>
<li>Micro tipping</li>
<li><a href="https://podnews.net/article/how-to-earn-bitcoin-from-your-podcast">Boostagrams</a></li>
</ul>
<p><em>Examples:</em></p>
<ul>
<li><a href="https://podcastindex.org/">Podcast Index</a>/Podcasting 2.0</li>
</ul>
<h3 id="lnurl">LNURL<a href="#lnurl" aria-label="Link to heading 'LNURL'" data-heading-content="LNURL" class="anchor"></a></h3>
<p><a href="https://github.com/lnurl/luds">LNURL</a> is a set of standards designed to expand the capabilities of LN by providing a specification for “out-of-band” or third party communication between nodes. LNURL standardizes a whole set of LN-based interactions for developers. For example, the <a href="https://github.com/lnurl/luds/blob/luds/06.md">LNURL-Pay</a> spec defines how services can create static QR codes that can be paid multiple times. Under the hood, the service is generating a new invoice every time a payment attempt is made.</p>
<p><em>Benefits:</em></p>
<ul>
<li>Static pay/withdrawal links</li>
<li>LN based authentication schemes</li>
<li><a href="https://github.com/lnurl/luds">Many more</a></li>
</ul>
<p><em>Drawbacks:</em></p>
<ul>
<li>LNURL operates “out-of-band” meaning that there is a service between LN nodes creating invoices on a node’s behalf. This can mean that the service must be trusted to stay online in addition to the nodes involved in the payment.</li>
</ul>
<p><em>Possible micropayment use cases:</em></p>
<ul>
<li>Private, printable, reusable QR code for donations on content</li>
<li>Reusability allows for payments to be made even when embedded in images and videos</li>
<li>Withdrawals for content creators who’ve accumulated sats on a given platform</li>
<li>Donations to protestors: Anyone can hold up a sign a protest movement captured on video, and sympathizers can easily donate. To quote Marc Andreessen from <a href="https://a16z.com/2014/01/21/why-bitcoin-matters-nyt/">“Why Bitcoin Matters”</a> in 2014:
<blockquote>
<p>Think about the implications for protest movements. Today protesters want to get on TV so people learn about their cause. Tomorrow they’ll want to get on TV because that’s how they’ll raise money, by literally holding up signs that let people anywhere in the world who sympathize with them send them money on the spot. Bitcoin is a financial technology dream come true for even the most hardened anticapitalist political organizer.</p>
</blockquote>
</li>
</ul>
<p><em>Examples:</em></p>
<ul>
<li><a href="https://satoshkey.com/site/cointrain">Cointrain</a></li>
<li><a href="https://lightningaddress.com/">Lightning addresses</a></li>
<li><a href="https://github.com/lnurl/awesome-lnurl">List of Awesome LNURL things</a></li>
</ul>
<h3 id="lsat">LSAT<a href="#lsat" aria-label="Link to heading 'LSAT'" data-heading-content="LSAT" class="anchor"></a></h3>
<blockquote>
<p>Many speculate that [the 402 status code] was intended to be
used by some sort of digital cash or micropayment scheme, which didn’t yet
exist at the time of the initial HTTP specification drafting.</p>
<p>However, several decades later, we <em>do</em> have a widely used digital cash system:
Bitcoin! On top of that, a new network oriented around micropayments has also
arisen: the Lightning Network.</p>
<p>Olaoluwa Osuntokun, <a href="https://lightning.engineering/posts/2020-03-30-lsat/">LSAT: Authentication and Payments for the Lightning-Native Web</a></p>
</blockquote>
<p><a href="https://lsat.tech/">Lightning Service Authentication Tokens (LSATs)</a> are a protocol created for authentication and paid APIs that leverage the forgotten HTTP error code 402 discussed earlier in conjunction with the lightning network. LSATs can be thought of like reusable tickets that need to be paid for to gain access to a particular resource. But the powerful thing here is that <em>rules</em> can be applied to modify access to resources on a given site, and the tickets can be reused on subsequent visits to the site.</p>
<p><em>Benefits:</em></p>
<ul>
<li>Extremely flexible</li>
<li>Reusable</li>
<li>Allows for highly granular and fine-grained access to digital resources</li>
<li>Finally makes use of 402 error code!</li>
</ul>
<p><em>Drawbacks:</em></p>
<ul>
<li>Difficult version control</li>
<li>No current methods for efficient management (Could be solved by browser extension such as <a href="https://github.com/getAlby/lightning-browser-extension/issues/9">Alby</a>)</li>
<li>Underutilized/underdeveloped despite being around for awhile</li>
</ul>
<p><em>Possible micropayment use cases:</em></p>
<ul>
<li>Pay for single video/podcast/song/article and remember it on subsequent visits</li>
<li>Metered subscriptions of your choice</li>
<li>Custom expiration dates on content</li>
</ul>
<p><em>Examples:</em></p>
<ul>
<li><a href="https://docs.lightning.engineering/the-lightning-network/lsat/aperture">Aperture</a></li>
<li><a href="https://medium.com/tierion/boltwall-middleware-for-lightning-payments-authorization-e3a1dbb54a4c">Boltwall</a></li>
<li><a href="https://github.com/Tierion/lsat-js">lsat-js</a></li>
<li><a href="https://mixtape.albylabs.com/">Alby Mixtape</a></li>
<li><a href="https://nakaphoto.vercel.app/">Nakaphoto</a></li>
</ul>
<h3 id="webln">WebLN<a href="#webln" aria-label="Link to heading 'WebLN'" data-heading-content="WebLN" class="anchor"></a></h3>
<p><a href="https://www.webln.guide/introduction/readme">WebLN</a> is a standard for abstracting away LN interactions by relying on a client (such as a browser extension) to communicate with a WebLN enabled site. The client must have the ability to interact with the user’s lightning node. For example, a site might use WebLN to request a payment from the user when a button is clicked. When clicked, the user’s client extension will popup, providing whatever payment details the WebLN enabled site sent to the client in a simple to use interface. WebLN greatly simplifies the payment flow for browser based interactions, where user’s no longer have to pull out their phones to make QR code based payments.</p>
<p><em>Benefits:</em></p>
<ul>
<li>Better UX</li>
<li>Simple implementation</li>
<li>No third party required</li>
<li>Compatible with LNURL</li>
</ul>
<p><em>Drawbacks:</em></p>
<ul>
<li>Browser client required</li>
<li>Few clients in use</li>
<li>If user doesn’t have a client installed, fallback options (such as LNURL or invoices) must be provided</li>
</ul>
<p><em>Possible micropayment use cases:</em></p>
<ul>
<li>Anything an invoice, LNURL, or Keysend can do!</li>
</ul>
<h3 id="projects-using-micropayments">Projects Using Micropayments<a href="#projects-using-micropayments" aria-label="Link to heading 'Projects Using Micropayments'" data-heading-content="Projects Using Micropayments" class="anchor"></a></h3>
<p>Finally, here is a short list of projects using micropayments that I found interesting:</p>
<ul>
<li><a href="https://micropay.ai/">micropay.ai</a>: Micropayments to generate images using DALLE-2</li>
<li><a href="https://webln.twentyuno.net/">WebLN Experiments</a>: A collection of various WebLN demos</li>
<li><a href="https://lnvpn.net/">LN VPN</a>: Pay as you go VPN service, pay as little as $0.1 for 1 hour</li>
<li><a href="https://lncal.com/">LNCal</a>: Allow people to pay you in bitcoin to schedule meetings</li>
<li><a href="https://btcmap.org/">BTCMap</a>: Map of locations accepting bitcoin. Uses Boosting for favored payments</li>
<li><a href="https://lightning.video/?type=top&amp;nsfw=false&amp;all=true">Lightning Video</a>: Like Youtube, but uses bitcoin micropayments instead of ads</li>
<li><a href="https://www.sats4likes.com/">Sats4Likes</a>: Get micropaid for micro tasks</li>
<li><a href="https://p2ptube.us/">PeerTube Lightning Plugin</a>: Pay PeerTube creators with LN</li>
<li><a href="https://stacker.news/">Stacker News</a>: Reddit-style discussions with micropayment tipping</li>
<li><a href="https://bookmark.org/">Bookmark.org</a>: Pay to archive links</li>
<li><a href="https://www.thndr.games/">THNDR Games</a>: Earn bitcoin while playing games</li>
</ul>
<p>See <a href="http://bolt.fun">bolt.fun</a>, the <a href="https://dev.lightning.community/lapps/index.html">Lightning App Directory</a>, or <a href="https://www.lightning-landscape.net/projects">Lightning Landscape</a> for a larger list of projects being built on Lightning.</p>
<h2 id="conclusion">Conclusion<a href="#conclusion" aria-label="Link to heading 'Conclusion'" data-heading-content="Conclusion" class="anchor"></a></h2>
<p>Micropayments have arrived on the web, and this article just scratches the surface of what’s possible. While there are significant technological and mental barriers remaining, I hope the above examples show how far we’ve already come – and how much further we can go – for a cleaner, freer, and more expressive internet.</p>
]]></content:encoded></item>
</channel>
</rss>
