<![CDATA[Made By Nathan]]>https://madebynathan.com/https://madebynathan.com/favicon.pngMade By Nathanhttps://madebynathan.com/Ghost 6.0Sat, 29 Aug 2026 08:25:44 GMT60<![CDATA[Reproducing a String Theory Vacuum in Rust]]>Why does this universe exist? Why is there anything at all?

One night, I got tired of wondering about these questions, so I decided to roll up my sleeves and see if I might be able to help to figure out some answers. Because why not? So for the last

]]>
https://madebynathan.com/2026/06/18/reproducing-a-string-theory-vacuum-in-rust/6a335b7ff08f1b00d560967fThu, 18 Jun 2026 03:23:08 GMT

Why does this universe exist? Why is there anything at all?

One night, I got tired of wondering about these questions, so I decided to roll up my sleeves and see if I might be able to help to figure out some answers. Because why not? So for the last six months, one of my new hobbies has been string theory research. I've spent many nights reproducing a frontier result in string theory, porting a Python physics library to Rust, and standing up a genetic algorithm on my home server that searches the "landscape" of string vacua for one that might explain dark energy.

First: I am not a physicist, and I still don't really understand anything about string theory. I've had to learn everything from scratch, and almost all of the heavy lifting was done by AI coding agents. (AI is very, very good at physics and mathematics now.) Second: this work has not found a theory of everything, and it has not yet found a single verified dark-energy vacuum. What it has done is reproduce a published string-theory calculation exactly, from first principles, and turn that into a fast, type-safe engine that now runs a real search 24/7.

The physics, in five minutes

String theory only makes sense in ten dimensions. To connect it to our four-dimensional world, you roll up the extra six dimensions into a tiny, intricate shape called a Calabi-Yau manifold. There are enormous numbers of these shapes, and on each one you can thread different amounts of "flux" (think of it as generalized magnetic field lines) through the manifold's holes. Each choice of shape + flux gives a different effective four-dimensional universe, with different physics. The set of all of them is the string landscape, and it is famously, absurdly large — the figure that gets thrown around is something like 10^272,000 distinct flux vacua.

Two numbers about our universe are spectacularly hard to explain:

  • The cosmological constant. Empty space has an energy density, and ours is tiny and positive — about 10^-122 in natural (Planck) units. Naive estimates from quantum field theory are off by ~120 orders of magnitude. This is widely considered the worst prediction in physics.
  • Dark energy's behavior. Until recently, we assumed that vacuum energy was a true constant (Λ). But the DESI survey has been publishing data hinting that dark energy might be slowly weakening over time — that it's not a constant at all, but a scalar field still gently rolling downhill. In string theory that rolling field is the most natural thing in the world: it's a modulus, one of the geometric parameters of the Calabi-Yau. A rolling-scalar dark energy is called quintessence.

Here's the punchline that makes this exciting rather than embarrassing for string theory: building a stable, eternal, positive cosmological constant in string theory has been notoriously, painfully difficult for two decades (the "de Sitter problem"). But a slowly decaying dark energy — quintessence — is exactly what a lot of string theorists have been predicting since 2018, on "swampland" grounds. If DESI's trend holds, it's arguably the first observational hint in favor of the string-theoretic expectation.

So the dream is: find a specific Calabi-Yau + flux that produces a vacuum energy of the right tiny size and whose rolling-modulus dark energy matches the equation of state DESI measures. That would be a concrete, UV-complete, falsifiable string-theory model of the dark energy we actually observe.

To even attempt that search, you need to be able to compute the vacuum energy of a candidate exactly. Which brings us to McAllister.

The calibration target: an explicit vacuum with a tiny cosmological constant

In 2021, McAllister, Demirtas, Kim and collaborators published one of the only fully explicit, end-to-end constructions of a string vacuum with an exponentially small, controlled cosmological constant. They did it on one specific Calabi-Yau — a reflexive polytope from the Kreuzer-Skarke database with Hodge numbers (h¹¹, h²¹) = (214, 4), which everyone in this corner of the field just calls "4-214-647."

The numbers they get are wild:

QuantityValue
string coupling g_s≈ 0.00911
flux superpotential \W₀\≈ 2.3 × 10⁻⁹⁰
Calabi-Yau volume≈ 4711.83
vacuum energy V₀ −5.5 × 10⁻²⁰³

That last number — a cosmological constant 203 orders of magnitude below the natural scale — is the headline. It comes from a delicate cancellation: the flux superpotential W₀ is already tiny (~10⁻⁹⁰), and then non-perturbative effects nearly cancel it, leaving |W| even smaller. Square that and you're at 10⁻²⁰³.

Reproducing this is the obvious first move. If I can't reproduce a published number exactly, I have no business searching for new ones — I'd never know whether a "hit" was real physics or a bug.

I figured it would take a couple of weeks. It took many months.

The long struggle: "discrepancies are gold"

The thing about a number like 10⁻²⁰³ is that you cannot eyeball whether it's right. The whole pipeline — polytope → triangulation → intersection numbers → flat direction → racetrack → superpotential → volume → vacuum energy — is a long chain where each stage feeds the next, and a single wrong sign or off-by-one index anywhere produces a different, equally plausible-looking, completely wrong answer. You only find out you're wrong by failing to match the paper, and even then you have no idea which of a dozen stages is lying to you.

So I adopted a rule, which became the emotional core of the whole project: a discrepancy is not a problem, it's the entire point. Every time our number disagreed with McAllister's, that disagreement was a bug with a specific physical cause, and the job was to understand it completely — no shortcuts, no "close enough," no silently substituting the paper's value to make a test pass. Here's one of the guardrails I wrote for the project:

"This is not building a SaaS. This is high energy theory physics. This is formal verification. This is launching rockets. A single sign error, a single wrong index, a single misunderstood coordinate system will produce garbage that looks plausible. Either the physics is exactly right or it's meaningless."

Here are a few of the bugs.

The two different 4×'s. Early on the volume came out as 17,901 instead of 4,711 — a factor of about 3.8, suspiciously close to 4. The cause was that CYTools (the standard Python toolkit for this) had silently changed its default choice of "divisor basis" between the 2021 version McAllister used and the current one. McAllister's stored flux vectors were written in the old basis; feeding them through the new basis corrupted everything downstream. And there was a subtle sub-bug inside the fix: the two flux vectors K and M transform differently under a basis change (one covariant, one contravariant), and the original code transformed both the same way. To make it worse, there was a second, unrelated ~3.8× error lurking — using the uncorrected Kähler moduli instead of the instanton-corrected ones also gives ~3.8× the right volume. Two different near-4× bugs that had to be told apart.

The 16/9 that was a misplaced parenthesis. One factor, e^{K₀}, kept coming out 16/9 ≈ 1.78× too large. The cause was reading equation (6.12) wrong: the paper writes e^{K₀} = (4/3 · κ p³)⁻¹, where the inverse applies to the whole product — i.e. (3/4)/(κp³), not (4/3)/(κp³). The ratio between the wrong and right readings is exactly (4/3)/(3/4) = 16/9. A LaTeX parenthesis cost days.

The hero bug: a B-field on the wrong divisors. This was the last thing standing between "close" and "exact". After fixing everything else, the volume was still off — by a stubborn, tiny +0.0718 that would not go away. Codex went down a deep rabbit hole convinced the cause was nine "missing" curve invariants that seemed to require some exotic orbifold Gromov-Witten machinery (Chen-Ruan cohomology, twisted I-functions). It was a dead end. The real cause was almost insultingly small: the function building the orientifold's B-field parity vector was constructing it from the wrong set of divisors, giving 49 odd entries when the true answer has 51. The two missing entries corresponded to two specific lattice points (points 2 and 46) that sit outside the basis I was iterating over. With the wrong parity, two of ten curves got the wrong sign in an analytic continuation, and the vacuum settled into the wrong adjacent geometric "chamber" — off by exactly that 0.0718. Fix the parity vector, and the volume snaps to 4711.4264 and log₁₀|V₀| lands on −202.2628.

A sign error on 2 of 10 curves, from an under-counted parity vector, hiding behind a 200-digit number. This bug was finally cracked by Claude Fable 5. I wouldn't have been able to finish the paper reproduction and move on to the GA without Fable 5.

There's also a small α′ correction to the volume — −ζ(3)·χ/(4(2π)³) ≈ 0.509 — that's tempting to ignore because it's tiny. It is not optional. Leave it out and V is wrong by half, which propagates straight into V₀. (The project notes have this one in all-caps.)

After all of it: the pipeline now reproduces log₁₀|V₀| = −202.26 from first principles — starting from nothing but the polytope's lattice points and the flux integers, computing every intermediate quantity live, never loading a precomputed value. (The test suite is deliberately built so cheating is impossible: what we compute and what McAllister published live in separate directories, and a stage that loaded the answer instead of computing it would just fail.) Not just for 4-214-647, but for all five vacua in the paper. And when I later ran a sanity sweep — 94 random Calabi-Yaus, comparing our Rust output against CYTools on identical inputs — it was 94/94 exact matches, zero divergence.

That green checkmark was finally the license to do everything that comes after.

Why Rust?

The original prototype was Python — a "Frankenstein" of a Rust genetic-algorithm driver calling out through PyO3 to Python, which imported CYTools, JAX, and some C++ tools. It worked. It proved the physics. But it was far too slow to search with, and the dynamically-typed numerics made the sign-and-NaN bugs above easy to write and hard to catch.

So I decided to reimplement the whole thing in Rust. The library I was porting, CYTools, does the core Calabi-Yau computations: lattice polytopes, triangulations, intersection numbers, cone computations, curve-counting invariants. The porting philosophy I adopted (and wrote into the project rules):

"Rust bindings don't matter. You can reimplement entire libraries in minutes. Don't waste time searching for Rust crates with bindings. Find the algorithm in ANY language — C++, Python, Fortran, whatever — understand it, and port it. Don't search for Rust bindings. Just port the algorithm."

So the Double Description Method for dualizing cones came from reading the PPL and cddlib C/C++ source; LLL lattice reduction came from the FLINT-based version CYTools uses; the triangulation circuits came from the CYTools source itself. (I won't pretend it's 100% pure: linear programming uses the good_lp + HiGHS crate rather than a hand-port, and — honestly — the hardest piece, the Gopakumar-Vafa series inversion, reuses the excellent cygv crate. I port the scaffolding around it, not the HKTY engine itself.)

The part I'm genuinely proud of is the type system for the numbers. Every quantity in the codebase is a phantom-typed wrapper — F64<Pos>F64<NonNeg>F64<NonZero>F64<Finite> — and these tags carry a compile-time algebra:

let a: F64<Pos> = pos!(3.0);
let b: F64<Neg> = neg!(-2.0);
let c: F64<Neg> = a * b;   // Pos * Neg = Neg — the compiler tracks the sign
let d: F64<Pos> = a * a;   // Pos * Pos = Pos

Pos * Neg is NegPos * Pos is PosPos + Neg widens to Finite (sign unknown), all enforced at compile time. The tags are zero-sized and the wrapper is #[repr(transparent)], so there is no runtime cost whatsoever — an F64<Pos> is laid out identically to a bare f64.

The single cleanest consequence is a comment in the division module:

"Division by Zero is intentionally not implemented — it won't compile!"

There is simply no Div implementation that accepts a denominator which might be zero. If you have a number that's only known to be Finite, you cannot write a / b until you prove the denominator is nonzero by narrowing its type (try_to_non_zero(), which returns an Option). An entire class of 1/0 → ∞ → NaN-propagates-through-your-vacuum-energy bugs is removed at the language level rather than guarded at runtime. Given that the whole struggle above was about sign errors and silent garbage, encoding "invalid states are unrepresentable" directly into the types felt very important.

On top of that sits a ≥98% test-coverage gate, which I think about less as a quality metric and more as a reading-discipline mechanism — it guarantees every line has been executed (and therefore read) at least twice — and the dual test suite that runs identical inputs through both CYTools and the Rust port and demands bit-identical results.

The scale, for the curious: about 80,000 lines of Rust across two crates (cyrus-core for the math/physics, cyrus-ga for the search). A lot of work — 959 commits across 6 days in early May — came out of one intense sprint where I left a Codex /goal running for 4 days and accidentally spent thousands of dollars on OpenAI tokens. And I still didn't have much to show for it by the end. But then I had access to Claude Fable 5 for a few days in June, and it was finally able to get the project over the finish line.

GitHub - ndbroadbent/cyrus: Calabi-Yau (CY) tools for Rust
Calabi-Yau (CY) tools for Rust. Contribute to ndbroadbent/cyrus development by creating an account on GitHub.
Reproducing a String Theory Vacuum in Rust

The search: a genetic algorithm hunting for dark energy

With a trustworthy engine, the actual goal comes into reach: search the landscape for a vacuum whose dark energy matches DESI.

The search is a genetic algorithm. A "genome" is just a pair of integer flux vectors (K, M). The fitness function runs that flux through moduli stabilization to get g_s, |W₀|, and a vacuum energy V₀, and then scores it on how well it matches the dark energy we observe:

  • Height — how close log₁₀|V₀| is to the observed dark-energy scale (≈ −121.5). This term gates everything else.
  • Slope — the candidate's energy is dressed up as a rolling-axion quintessence field, evolved through the actual Friedmann + Klein-Gordon cosmology equations, fit to an equation of state w(z), and scored against DESI's measurement (w₀ = −0.45 ± 0.21, wₐ = −1.8 ± 0.6).
  • Weak coupling — a small tiebreaker rewarding perturbative control.

There's a "fitness ladder" with strictly ordered bands — invalid candidates are scored by how far through the physics pipeline they got before failing, so the GA gets a smooth gradient to climb even in the vast regions where nothing is valid.

It searches a pool of 73,664 Calabi-Yau polytopes (the full Kreuzer-Skarke set with a small number of complex-structure moduli), with a bandit algorithm deciding how to split effort between exploring fresh geometries and exploiting promising ones.

The most important thing I learned building it: blind random search finds absolutely nothing. Zero valid vacua in 557,000 tries. And that's not bad luck — it's provable. A valid vacuum requires the flux K to be "isotropic" for a certain quadratic form derived from M, which is a measure-zero condition; random integer vectors essentially never satisfy it, and you can't greedily nudge your way onto it. The fix is to constructively solve for isotropic flux seeds, in exact integer arithmetic, at the start of each geometry, and seed the population with those. (Geometries where no seed exists in budget get marked "PFV-barren" and skipped — which means the search is also quietly producing a census of which Calabi-Yaus can't host this kind of vacuum at all.)

Two tiers: a cheap proxy and an expensive truth

Running the full first-principles vacuum-energy computation on every candidate would be hopeless — it takes tens of minutes. So evaluation is two-tiered. A cheap "mirror-side" proxy runs on every genome in microseconds and is good enough for ranking. Only when a candidate looks genuinely promising does the expensive "deep-verify" fire: the full McAllister-style stabilization on the real Calabi-Yau, the same pipeline that reproduces −202.26.

And deep-verify is itself a search, which surprised me. The paper's criterion for choosing the right divisor basis admits thousands of valid options, and they are not interchangeable — most of them land the calculation in a geometric chamber where the curve-counting becomes intractable. Only special chambers stay computable. So deep-verify walks the admissible bases until it finds one whose chamber is fully covered by the cheap curve-counting methods. (As a validation: pointed at McAllister's geometry, this scan independently re-derives his exact basis choice — candidate 437 out of 4,683 — and reproduces −202.26 with no input data file at all.)

Most of my recent work has been making deep-verify fast and safe enough to run inside a live search:

  • A precompute that hoists the geometry-fixed part of the curve-counting out of the per-candidate loop cut the cost about .
  • A key realization: whether a chamber is computable depends only on the geometry, not on the flux. So the expensive scan can be cached per polytope — paid once, ever, instead of re-run for every candidate. Without this, enabling deep-verify would have re-scanned thousands of bases on every generation and ground the search to a halt.
  • And the trigger is physical: deep-verify only fires on candidates whose proxy dark-energy equation of state already sits within 1σ of DESI. No point running a tens-of-minutes verification on a vacuum that, even if real, wouldn't look like our universe.

Running it on my server

All of this runs 24/7 on a server in my house, deployed with Ansible. It's a small fleet of systemd services: the genetic algorithm itself (16 threads, fully checkpointed so it survives restarts and resumes exactly where it left off), a daemon that tails the GA's output into a SQLite database, a web dashboard, and a job that computes ~40 geometric "shape" metrics over all 73,664 polytopes to see if I can find any correlations (just as an experiment).

Reproducing a String Theory Vacuum in Rust
Reproducing a String Theory Vacuum in Rust

The dashboard is a live DESI leaderboard, and a scatter plot of every valid candidate's predicted (w₀, wₐ) against the DESI 1σ box, with the cosmological-constant point (−1, 0) marked for reference.

Reproducing a String Theory Vacuum in Rust

There is a page to inspect the shape of each polytope:

Reproducing a String Theory Vacuum in Rust

What it's actually found

Nothing yet. It has not found a verified dark-energy vacuum. The search surfaces near-misses — candidates whose vacuum energy lands within a fraction of a log of the observed dark-energy scale, and whose equation of state comes within ~1–3σ of DESI. But every single leaderboard leader so far has died under the full deep-verify: either no computable chamber exists, or the real solution sits too many "flops" away across geometric walls, or a refinement reveals the promising-looking number was a truncation artifact.

That's to be expected. The most important property I built into it is that it's honest by construction: a candidate only climbs into the top tier by surviving the same first-principles calculation that reproduces the published McAllister number. Every dead leader added a permanent new gate.

And even if it does one day find a verified, DESI-matching, fully-controlled vacuum, I want to be clear about what that would and wouldn't mean. It would be a genuine, significant thing: a controlled, UV-complete string-theory realization of the observed dark energy, with the moduli actually stabilized and the approximations under control. It would not be a theory of everything. The dark-energy sector and the Standard Model live in different parts of the construction — the bulk geometry versus the branes wrapping it — and matching a handful of cosmological numbers says essentially nothing about whether the same Calabi-Yau also contains three generations of quarks and leptons. That's a separate, even harder search. Matching DESI would be a real result about dark energy, full stop.

A note on method: directing AI agents to do physics

Again, I am not a string theorist. The vast majority of the 80,000 lines of Rust, and a lot of the physics debugging, was done by AI coding agents — Claude, Codex, and others — working under an unusually strict regime that I spent a lot of time designing.

The interesting finding is that the strictness is what made it work. Frontier physics is the worst possible domain for the way LLMs like to behave: they love to paper over a discrepancy, fall back to a plausible default, or quietly load the expected answer to make a test go green. Every one of those instincts is fatal here, because the failure mode of this domain is plausible garbage. So the project rules are essentially a long list of forbidden shortcuts — "no silent fallbacks," "discrepancies are gold, stop and understand them," "never load a precomputed value where production computes it," "if it isn't implemented, the test should fail, not cheat" — backed by mechanical enforcement: the type system that won't let you divide by zero, the coverage gate that forces every line to be read, the dual test suite that catches any divergence from CYTools. The human job became less "write the code" and more "design the constraints so that the only way to make progress is to get the physics actually right" — and then chase down the discrepancies the constraints surfaced.

What's next

The engine is done and trustworthy, and the search is running. The honest status is: a correctly-built machine, reporting honestly that it hasn't found anything yet.

A verified candidate wouldn't be a philosophical claim — it would predict a specific dark energy equation of state w(z), and DESI's next data releases and the Euclid mission will measure exactly that curve over the next few years. The landscape is unimaginably large, the odds on any given run are long, and I might never find one. But the tooling now exists to look, it's open and reproducible, and every result it reports has survived a calculation that exactly reproduces the published state of the art.

Here is the source code for Cyrus:

GitHub - ndbroadbent/cyrus: Calabi-Yau (CY) tools for Rust
Calabi-Yau (CY) tools for Rust. Contribute to ndbroadbent/cyrus development by creating an account on GitHub.
Reproducing a String Theory Vacuum in Rust

And here are the other repos I used to organize my research and to help with project planning:

GitHub - ndbroadbent/string_theory_search: Searching for the compaction that defines our universe
Searching for the compaction that defines our universe - ndbroadbent/string_theory_search
Reproducing a String Theory Vacuum in Rust
GitHub - ndbroadbent/string_theory_project: An open source, central place to organize project PRDs, todo lists, docs, and ideas for my string theory work
An open source, central place to organize project PRDs, todo lists, docs, and ideas for my string theory work - ndbroadbent/string_theory_project
Reproducing a String Theory Vacuum in Rust
]]>
<![CDATA[Spice Labels and Spice Racks]]>I bought two boxes of these spice jars: STORFEX 24-Pack 120ml Glass Spice Jars. Then I used my Silhouette CAMEO5α cutting machine to make some vinyl labels. I used the Caveat Brush font from Google Fonts.

It took a little while to figure out how to use the cutting

]]>
https://madebynathan.com/2026/06/03/spice-labels-and-spice-racks/6a1d1ac57d7d0700d5e1eed3Wed, 03 Jun 2026 20:41:16 GMT

I bought two boxes of these spice jars: STORFEX 24-Pack 120ml Glass Spice Jars. Then I used my Silhouette CAMEO5α cutting machine to make some vinyl labels. I used the Caveat Brush font from Google Fonts.

It took a little while to figure out how to use the cutting machine, the Silhouette Studio design software, and the transfer tape to transfer letters. Here's my first test:

Spice Labels and Spice Racks

I found this "Simple door mounted spice rack design" on Printables. They included an Autodesk Fusion design file so I could change the parameters and make it fit our spice jars perfectly.

Spice Labels and Spice Racks

If you have these same spice jars, they are 45.2mm wide and 104mm tall. I set the length parameter to 255mm, which is the maximum print size for my 3D printer (Bambu Lab X1C). This length comfortably fits 5 of these spice jars.

Spice Labels and Spice Racks

And then my 3D printer broke. You can read about how I fixed it here:

Fixing a Bambu Lab X1C Error: “Extruding filament failed. The extruder might be clogged.”
My 3D printer broke. No matter what I tried, I couldn’t get past this error: “Extruding filament failed. The extruder might be clogged.” I took apart the extruder gear assembly to check for clogs: I inspected the little magnet on the filament sensor and tried flipping it around (a tip
Spice Labels and Spice Racks

Here are the spice racks, attached to the inside of our pantry doors:

]]>
<![CDATA[Fixing a Bambu Lab X1C Error: "Extruding filament failed. The extruder might be clogged."]]>My 3D printer broke.

No matter what I tried, I couldn't get past this error: "Extruding filament failed. The extruder might be clogged."

I took apart the extruder gear assembly to check for clogs:

I inspected the little magnet on the filament sensor and tried flipping

]]>
https://madebynathan.com/2026/06/03/fixing-a-bambu-lab-x1c-error-extruding-filament-failed-the-extruder-might-be-clogged/6a208f74ba545000d5d7b565Wed, 03 Jun 2026 20:39:01 GMT

My 3D printer broke.

Fixing a Bambu Lab X1C Error: "Extruding filament failed. The extruder might be clogged."

No matter what I tried, I couldn't get past this error: "Extruding filament failed. The extruder might be clogged."

I took apart the extruder gear assembly to check for clogs:

I inspected the little magnet on the filament sensor and tried flipping it around (a tip from a comment on the Bambu Labs forums):

Fixing a Bambu Lab X1C Error: "Extruding filament failed. The extruder might be clogged."

I tried replacing the 0.4mm nozzle with a 0.6mm nozzle, but that didn't help. And I confirmed that I could manually push the filament through when the nozzle was heated.

Then I ordered a replacement gear assembly and filament sensor and waited for those to be delivered. I installed them, and still got the same error! At this point, I was starting to get a bit worried. I live in the far north of New Zealand, and New Zealand is a very small country, so we don't have many 3D printer repair specialists around. And I didn't want to have to replace my entire 3D printer, because it cost thousands of dollars. So I had no choice but to keep going and fix it myself.

0:00
/0:06

I finally figured it out. For some reason, a screw on the back of the printer had become impossible to remove. It's one of the screws for the AMS internal hub unit. I had cracked that unit while trying to get the screw off, and this had caused a small bend in the PTFE tube.

Apparently, the printer is very finicky and doesn't like any extra friction during filament loading. I replaced the PTFE tube, and then the filament loaded fine! So if you encounter this error, make sure you carefully check all your PTFE tubes for kinks and replace any that are damaged.

I wish I had figured this out a bit sooner, but at least now I have a few extra spare parts for next time. My extruder unit also has some nice new replacement parts, so it should last for quite a while. I also understand the extruder a lot better now and I know how to take it apart and service it in the future.

]]>
<![CDATA[Welcome Sign]]>I bought a Silhouette CAMEO 5α cutting machine.

Silhouette Cameo 5 Alpha | Professional Cutting Machine
Silhouette Cameo 5 Alpha professional cutting machine. Advanced features, precision cutting, and compatibility with all Silhouette materials and tools.

My wife and I designed a welcome sign using white

]]>
https://madebynathan.com/2026/06/01/welcome-sign/6a1d18907d7d0700d5e1ee98Mon, 01 Jun 2026 05:36:17 GMT

I bought a Silhouette CAMEO 5α cutting machine.

Silhouette Cameo 5 Alpha | Professional Cutting Machine
Silhouette Cameo 5 Alpha professional cutting machine. Advanced features, precision cutting, and compatibility with all Silhouette materials and tools.
Welcome Sign

My wife and I designed a welcome sign using white vinyl on a square of wood. Here is the design file:

(You can open this file with Silhouette Studio.)


Welcome Sign
Welcome Sign
Welcome Sign
]]>
<![CDATA[Listening for Garage Door Remotes with ESPHome]]>We are renting a house that has two old garage door openers. We have a switch by our front door that controls the garage doors. I think the landlords or the previous tenants lost the garage door remotes. We received two remotes when we moved in, but I couldn'

]]>
https://madebynathan.com/2026/05/30/monitoring-garage-door-remote-with-esphome/6625c79dcab1a83ac77b0981Sat, 30 May 2026 00:01:00 GMT

We are renting a house that has two old garage door openers. We have a switch by our front door that controls the garage doors. I think the landlords or the previous tenants lost the garage door remotes. We received two remotes when we moved in, but I couldn't get them to work, so I think they bought the wrong replacement remote for one of our doors.

We have a Merlin M230T garage door opener, which is an older model sold between 1996 and 2001. This one uses DIN switches to configure the security code:

It looks like the landlord or a previous tenant bought a newer M842R remote that uses "rolling codes" for security. This remote is for garage door openers made between 2001-2007.

We needed an M802 remote for our old garage door, but a replacement remote costs $100.

Two-Button Remote Control M802R (Blue Button) | Merlin AU
Enhance your garage experience with the Merlin M802R Remote Control to conveniently support multiple legacy opener systems. Shop today for smoother access.
Listening for Garage Door Remotes with ESPHome

We moved into this house over a year ago, and the missing garage door remotes haven't been too much of a problem. That's because I set up some Zigbee garage door opener modules:

30.49NZ$ 57% OFF|ZigBee Garage Door Opener Dry Contact Tuya Smart Life App Remote Control Supports Alexa Google Home Zigbee2MQTT Gateway Needed| | - AliExpress
Smarter Shopping, Better Living! Aliexpress.com
Listening for Garage Door Remotes with ESPHome

So we can control the garage doors using Alexa, Siri, and the Home Assistant app on our phones. I've even set up Home Assistant to open and close the garage doors automatically based on our phones' locations.

But I'd still like to have a remote in our cars as a backup option.

I have these IR/RF transmitter/receiver boxes in most of the rooms of our house:

60.81NZ$ |KC868-AG ESP32 IR RF Gateway Made For ESPHome Home Assistant Automation DIY
Smarter Shopping, Better Living! Aliexpress.com
Listening for Garage Door Remotes with ESPHome

They run ESPHome so I can configure the firmware and program them to do whatever I like. I mostly use them to control our air conditioners, and I have one in my workshop that controls an old CRT TV using TV remote codes. I also use them as a Bluetooth Proxy for Home Assistant, which means that I can send and receive bluetooth signals across my entire house without worrying about range. I use bluetooth temperature and humidity sensors, a Switchbot to turn on our coffee machine in the morning, and a bluetooth LPG gas sensor on our gas tank.

They're quite expensive devices, but I'm very happy with them. They work flawlessly and I can run my own custom ESPHome firmware without calling out to any cloud services. I was thinking about designing my own PCBs and 3D printing cases, but my electrical engineering skills aren't quite there yet.


These little boxes can also send and receive radio signals on the 433 MHz band. Fortunately, that's the same radio band that this garage door opener uses.

So I was wondering if there's a way I could use these IR/RF boxes as a bridge for the newer incompatible garage door remotes. I could listen for the button presses and then relay that signal to the Zigbee garage door module.

Listening for Garage Door Remotes with ESPHome

I pulled up the ESPHome logs and started looking for "RCSwitch Raw" codes:

Listening for Garage Door Remotes with ESPHome
Ignore the "remote.pronto" lines. I have quite a few devices in my house, so the RF logs can be quite noisy sometimes.

This is when I pressed the main button:

[14:32:06][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='1001001010'
[14:37:36][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='001011010'
[14:37:36][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='10001111111'
[14:37:38][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='10100001'
[14:37:42][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='010010001'
[14:37:43][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='11100011011'

This is when I pressed the smaller button:

[14:40:32][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='101101000'
[14:40:33][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='010010001'
[14:40:34][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='01001000111'
[14:40:34][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='100001110'
[14:40:35][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='00001100111'
[14:40:36][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='1111100000'
[14:40:37][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='11100001'
[14:40:39][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='10111000100001'
[14:40:42][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='11101001101'
[14:40:42][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='110111101'
[14:40:43][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='1011000000001'
[14:40:43][I][remote.rc_switch:261]: Received RCSwitch Raw: protocol=6 data='1100100110'

I gave up at this point since I couldn't figure out the protocol or how to keep it secure. Unfortunately, I'm not good enough at reverse engineering or electronics to figure out how to pair a garage door remote and verify the radio signal.

I don't live in that house anymore, but I thought I would publish this blog post anyway since it had been sitting in my drafts for a few years. But it's 2026 now, and AI is getting very smart, so here are a few paragraphs about how ChatGPT would have approached this problem.

What ChatGPT Would Try Next

The key thing to understand is that pairing a rolling-code garage remote is not the same as recording and replaying a fixed 433 MHz signal. When a compatible opener is put into learn mode, it does not simply save the exact RF packet that was transmitted. Instead, it learns the remote’s identity and current synchronization state. Future button presses produce different codes, usually based on a counter and a cryptographic algorithm. The opener can accept the next valid codes because it already understands the manufacturer’s rolling-code scheme and has the secret material needed to verify them.
That means a custom ESPHome or Home Assistant receiver could not reliably “pair” with the newer Merlin remote just by seeing one button press. It would need to decode the remote’s actual protocol, identify which parts of the packet are stable, track the rolling counter, and validate the changing encrypted portion. If the remote used something KeeLoq-like, then reimplementing the receiver would also require the relevant manufacturer key or key-derivation scheme. Without that, the software could perhaps recognize superficial patterns from the remote, but it could not authenticate future button presses in the same way a real Merlin opener would.
A practical next experiment would have been to capture several raw transmissions from the same button using an RTL-SDR, Flipper Zero, Yard Stick One, or similar RF tool, then compare them bit by bit. If part of the packet stayed constant while another part changed on every press, that would strongly suggest a remote identifier plus rolling-code payload. The project would then become less about “replaying” a garage remote and more about writing a custom rolling-code receiver. That may be possible if the protocol is known or reverse-engineered, but it is not something ESPHome’s generic rc-switch decoder can infer from noisy short RCSwitch Raw logs.
]]>
<![CDATA[Sliding Benchtop Organizers]]>I don't have a lot of room in my workshop, so I need to make the most of the space. I have six of these Pinnacle Benchtop Organizers:

Pinnacle Organizer

It would be convenient to have them all facing the right way on my shelves, but I decided

]]>
https://madebynathan.com/2026/05/29/sliding-benchtop-organizers/6a1a1f657d7d0700d5e1ed1aFri, 29 May 2026 23:45:54 GMT

I don't have a lot of room in my workshop, so I need to make the most of the space. I have six of these Pinnacle Benchtop Organizers:

Sliding Benchtop Organizers
Pinnacle Organizer

It would be convenient to have them all facing the right way on my shelves, but I decided to turn them sideways and slide them out to save space. I attached sliding drawer rails to them to make this more convenient.

It was a bit tricky to figure out how to do this. The bottom of the organizer looks like this:

Sliding Benchtop Organizers
Bottom of organizer

I cut up a sheet of hardboard and attached the pieces to the organizer using metal screws.

Sliding Benchtop Organizers
Wood base screwed onto organizer

Then I was able to attach the drawer slides to the piece of wood.

Sliding Benchtop Organizers
Drawer slides installed on organizer
Sliding Benchtop Organizers
All six sliding organizers installed on the shelf

I was worried about the weight when pulling them out, but they seem to be fine. There is a slight bend for some of the heavier organizers, but they are only open for a very short time, so I'm not too worried about it.

0:00
/0:18

Opening the sliding organizer drawers

]]>
<![CDATA[ChatToMap]]>I built an AI app that scans your chats to find all the restaurants, trips, and activities you talked about. It works with chat exports from WhatsApp, iMessage, Facebook Messenger, Telegram, and LINE.

You can try it out at: https://chattomap.com

Here are some screenshots:

]]>
https://madebynathan.com/2026/05/01/chattomap/69f42216084e7e00ee5f6eeeFri, 01 May 2026 04:35:35 GMT

I built an AI app that scans your chats to find all the restaurants, trips, and activities you talked about. It works with chat exports from WhatsApp, iMessage, Facebook Messenger, Telegram, and LINE.

You can try it out at: https://chattomap.com

Here are some screenshots:

ChatToMap
ChatToMap
ChatToMap
]]>
<![CDATA[Sandtray Box for Sandplay Therapy]]>My wife is a counselor. I enjoy making things to help with her work, such as this "fried rice" toy set:

Fried Rice Sandbox Toy
My wife is a counselor who does “play therapy” at a primary school. She has a sand tray and some toys
]]>
https://madebynathan.com/2026/04/20/sandtray-box-for-sandplay-therapy/69e5815c29006000d14d569cMon, 20 Apr 2026 02:07:47 GMT

My wife is a counselor. I enjoy making things to help with her work, such as this "fried rice" toy set:

Fried Rice Sandbox Toy
My wife is a counselor who does “play therapy” at a primary school. She has a sand tray and some toys that kids can play with during their sessions. Sometimes she asks me to 3D print things for the kids to play with. For example, I printed this little shovel
Sandtray Box for Sandplay Therapy

She also does sandplay therapy so she asked me to make a sandtray box with a drawer.

This woodworking project turned out to be a lot more difficult than I expected. I made a lot of mistakes, the project stalled a few times, and it didn't turn out as well as I'd hoped. But I finally finished it, and my wife is happy with it!


I started by designing it in Autodesk Fusion:

Sandtray Box for Sandplay Therapy

Here is the Fusion archive file in case you want to make your own version:

I used the awesome Mapboards Pro add-in to create a cut list for a single 2400.0mm x 1200.0mm sheet of 17mm plywood.

MapBoards Pro | Fusion | Autodesk App Store
MapBoards Pro arranges bodies in a model optimally on boards, leaving your model untouched. You can then use Autodesk® Fusion® additive manufacturing or output a flat panel in SVG or DXF format.
Sandtray Box for Sandplay Therapy
Sandtray Box for Sandplay Therapy

I 3D-printed a router guide so I could use my Bosch router with my Makita guide rails. I used it to cut the grooves and rabbets:


Here it is being used in my wife's office:

Sandtray Box for Sandplay Therapy
]]>
<![CDATA[Everything I've Done with OpenClaw (So Far)]]>https://madebynathan.com/2026/02/03/everything-ive-done-with-openclaw-so-far/69816adb89e0cb00d183d353Tue, 03 Feb 2026 04:37:29 GMT

I’ve been running an AI agent called Reef on my home server for a few days now. What started as an experiment has turned into a genuinely incredibly system. Here’s what we’ve built so far.

The Setup

Reef runs on OpenClaw, an open-source framework for running Claude as a persistent agent. It has access to my entire home server infrastructure through:

  • SSH to all my servers and containers in my home network
  • Kubernetes cluster access (kubectl)
  • 1Password for secrets management (in a dedicated vault)
  • My email accounts (via gog CLI)
  • My calendar
  • My Obsidian vault (5,000+ notes)
  • A personal Wikibase knowledge graph

15 Automated Jobs Running 24/7

The most impressive thing is how Reef has become self-sustaining through scheduled automation. Here are all the cron jobs currently running:

Every 15 Minutes

  • Active Work Session - Checks Fizzy (our kanban) for in-progress cards and continues work

Hourly

  • Alerts Check - Monitors Gatus health checks, ArgoCD deployments, and Fizzy notifications
  • Gmail Triage - Scans inbox, labels actionable items, archives noise

Every 6 Hours

  • KB Data Entry Batch - Processes Obsidian notes to populate Wikibase with entities
  • Wikibase Link Reconciliation - Converts [[wiki links]] in notes to Wikibase stubs
  • Report Reconciliation - Ensures all daily reports are complete
  • Self Health Check - Runs openclaw doctor, checks memory/disk, reviews logs

Every 8 Hours

  • Wikibase Entity Enrichment - Takes stub entities and enriches them by searching through all my data dumps exported from Gmail, ChatGPT, X, Obsidian, and many other sources.

Every 12 Hours

  • Internal Audit - Scans workspace for code quality issues, TODOs, and documentation gaps

4x Daily

  • Log Health Check - Analyzes Loki logs for errors across all services

Daily

  • Nightly Brainstorm (4am) - Deep creative exploration through my notes, emails, and exports looking for connections
  • Daily Briefing (8am) - Sends me an email summary with weather, calendar, system stats, and Fizzy activity
  • Fizzy Comment Reconciliation (9am) - Catches any cards where I commented but Reef didn’t reply
  • Velocity Assessment (1am) - Analyzes Fizzy metrics to find process improvements
  • Wikibase Weekly Review - QA pass on recently created entities

24 Custom Scripts

All the automation is backed by scripts Reef built autonomously:

Monitoring:

  • check-gatus.sh - Query health check endpoints
  • check-argocd.sh - Check K8s deployment status
  • check-loki-logs.sh - Analyze centralized logs for errors
  • check-email.sh - Poll for new emails (runs via system cron)

Reporting:

  • daily-briefing.sh - Generate morning summary
  • fizzy-daily-stats.sh - Task management metrics
  • velocity-assessment.sh - Process improvement analysis
  • weekly-infra-report.sh - Infrastructure overview
  • security-audit.sh - Check for vulnerabilities

Knowledge Base:

  • wikibase-link-reconcile.sh - Process wiki links
  • wikibase-enrich-entities.sh - Find stubs to enrich
  • wikibase-weekly-review.sh - QA report

Utilities:

  • get-system-stats.sh - Pull from Prometheus
  • reconcile-fizzy-comments.sh - Catch missed replies
  • internal-audit.js - Code quality checks
  • md2html.js - Convert markdown to HTML for emails

Organized Report Structure

Every automated job writes to a structured report directory:

Everything I've Done with OpenClaw (So Far)

Infrastructure Management

Reef deploys and manages apps on my K3s cluster:

  • Kubernetes deployments - Writes Kustomize manifests, debugs pod issues
  • Terraform & Ansible - All changes go through IaC. Reef reads existing configs, makes changes, applies them properly
  • Service monitoring - Regular health checks with automatic investigation
  • Just deployed - Gitea and Woodpecker for local Git hosting and CI

Development Workflow

  • Code reviews - Reviews PRs using Claude CLI before merge
  • CI/CD setup - GitHub Actions, now Woodpecker for local CI
  • Bug fixes - Debug and fix across codebases
  • Branch protection - Always works through PRs, never pushes to main

Personal Knowledge Base (Wikibase)

I’m building a personal knowledge graph using Wikibase - the same software that powers Wikidata. Why Wikibase?

The problem: Information about my life is scattered everywhere - notes, emails, messages, documents. When I need to answer a question like “what size shoes does my partner wear?” or “what’s my accountant’s phone number?”, I have to search through multiple apps.

The solution: A structured knowledge graph where every person, place, project, and concept has its own entity with properties and relationships. Wikibase gives me:

  • SPARQL queries - Find anything instantly (“all people who worked at company X”, “all projects using technology Y”)
  • Structured data - Not just text, but typed properties (dates, locations, relationships)
  • Entity linking - Everything connects to everything else
  • AI-friendly - Reef can query the KB to answer questions, fill out forms, or provide context

Current entities: People (family, friends, colleagues), places (addresses, venues), projects, companies, technologies.

Automated pipeline:

  • Entity extraction - Processes Obsidian notes to identify people, places, projects
  • Link reconciliation - [[wiki links]] in notes become Wikibase stubs automatically
  • Research enrichment - Stubs get enriched by searching through my data exports (ChatGPT, Gmail, Obsidian, etc.)
  • Custom schema - Properties tailored to our family’s data model (clothing sizes, preferences, relationships)

Daily Operations

The agent handles routine tasks automatically:

  • Email triage - Labels actionable items, archives noise
  • Calendar awareness - Checks both my and Masha’s calendars
  • Task management - Uses Fizzy to track work across 6 boards
  • Proactive maintenance - Finds issues before they become problems

Memory & Continuity

Reef maintains context through:

  • SOUL.md - Personality and work philosophy
  • MEMORY.md - Long-term curated memories
  • Daily logs - memory/YYYY-MM-DD.md for session notes
  • HEARTBEAT.md - Current priorities and context

Skills System

Specialized knowledge packaged as skills:

  • Ghost blog management (just created!)
  • Fizzy task management
  • Home Assistant control
  • YouTube transcript fetching
  • Weather queries
  • And more…

Standout Workflows

Real-Time Blog Collaboration

This post is being written in Obsidian right now, with Reef making edits while I type. The workflow:

  1. Draft in Obsidian - Real-time collaboration (we can both edit the same file)
  2. Generate banner - Reef uses Gemini’s image generation to create a 16:9 banner
  3. Publish to Ghost - API call creates/updates the post
  4. Deploy static site - Script crawls Ghost and pushes to GitHub Pages

Zero context switching. I stay in Obsidian, Reef handles the publishing pipeline.

Memory System with 49,000+ Facts

Reef built a memory extraction system that processed my ChatGPT export and extracted 49,079 atomic facts and 57 entities (companies, technologies, concepts). Now expanding to include:

  • Claude Code history (174,000+ messages)
  • Obsidian notes (5,000+ files)
  • Notion, UpNote, Ghost exports

This powers semantic search across years of my conversations and notes.

Personalized Daily Briefings

Every morning at 8am, I get an email with:

  • Weather for Paihia (where I live)
  • My calendar AND my partner’s calendar for the day
  • System health (CPU, RAM, storage across all servers)
  • Fizzy activity (cards created/closed in last 24h)
  • Highlights from the nightly brainstorm session

Neat: ADHD-Friendly Task UI

Reef built and deployed a complete web app from scratch called Neat - a minimal interface for Fizzy designed for ADHD brains.

The problem: Traditional kanban boards show everything at once, which can be overwhelming. When you have 100+ cards across multiple boards, deciding what to work on becomes its own task.

The solution: Neat shows you ONE task at a time with a custom-tailored decision form. Instead of staring at a wall of cards, you answer a simple question and move on.

Everything I've Done with OpenClaw (So Far)
Screenshot of the Neat UI on mobile

Tech stack: SvelteKit, TypeScript, Tailwind, SQLite, deployed to Kubernetes with Woodpecker CI.

Features:

  • Single-task focus view
  • Custom forms per card (radio buttons, text inputs, markdown descriptions)
  • Swipe navigation on mobile
  • Centralized Loki logging
  • Full test coverage

Built and deployed autonomously:

This is the first time that I‘ve experienced end-to-end autonomous engineering across an entire app development lifecycle. It only took a few initial prompts from me and some feedback. All via Telegram on my phone.

The agent used my GitHub API token to create the new repo. It built the app to the same rigorous coding standards that I use for all my projects: strict linting rules and file length limits, enforced test coverage, secret scans, and branch protection rules that force CI to pass before a merge. It set up the IaC configuration to deploy the app to ArgoCD, and set up the subdomain with Traefik for SSL. Then our scheduled jobs continuously monitor the production logs for errors and can automatically add tickets to fix any bugs.

First Blog Post: Self-Healing Infrastructure

Reef wrote and published a complete blog post about our setup: “Self-Healing Infrastructure: How an AI Agent Manages My Home Server”. Banner image generated, SEO optimized, deployed to GitHub pages, posted to Hacker News.

This Blog Post

I asked Reef to write it via Telegram. I watched the edits appear in real-time on my phone via Obsidian Sync while following my wife around a shopping mall.

Everything I've Done with OpenClaw (So Far)
Shopping mall

I sent feedback and photos via Telegram and saw new sections appear almost instantly. Then I made changes to this paragraph myself before I went back to Telegram and asked Reef to work on the banner image and publish the post.

What Went Wrong

It hasn’t all been smooth sailing. Here’s what we learned the hard way:

The API Key Incident

On day one, Claude Code was helping me with a script and hardcoded a Gemini API key directly into the code. I committed and pushed without reviewing carefully enough. Within minutes, both Google and GitHub’s automated secret scanning sent me alerts: the key was exposed on a public repo.

What happened: - AI coding assistant wrote the API key inline instead of using environment variables.

  • No pre-push secret scanning hook was configured
  • Human review (me) didn’t catch it before commit.

What saved us:

  • Google and GitHub’s instant detection
  • Key revoked within minutes
  • No unauthorized usage

New Security Measures

This incident led to mandatory security practices:

  1. TruffleHog pre-push hooks - Every public repo now has TruffleHog scanning before any push can complete. Hardcoded secrets get blocked locally.
  2. Local-first Git workflow - We deployed Gitea for local Git hosting. Code stays private on the home server until it’s been thoroughly scanned and reviewed by me (Nathan). Only then does it get pushed to public GitHub repos.
  3. Defense in depth - Pre-push hooks + CI scanning + GitHub/Google detection = multiple layers of protection.

Lesson learned: AI assistants will happily hardcode secrets. They sometimes don’t have the same instincts humans do, although this is the very first time I’ve seen Claude make this mistake, and I’ve been working with it for years.

Security

Giving an AI agent SSH access to your entire home server infrastructure is inherently risky. I’m not going to pretend otherwise.

What makes this less crazy than it sounds:

  1. Thousands of hours of IaC experience - I’ve been doing infrastructure-as-code for years. Terraform, Ansible, Kubernetes - this isn’t my first rodeo. The server was already locked down before Reef arrived.
  2. A year of Claude collaboration - I’ve been using Claude for infrastructure work for over a year now. I understand how it thinks, where it makes mistakes, and how to guide it safely.
  3. Daily security audits - Reef runs automated security reviews every day, checking for:
    • Privileged containers that shouldn’t be
    • Hardcoded secrets in config files
    • Overly permissive access controls
    • Known vulnerabilities
  4. Defense in depth - Multiple layers of protection: network segmentation, secret scanning, IaC enforcement, monitoring, and alerts.

But I don’t have it all figured out. There are probably security gaps I haven’t found yet. The daily audits are designed to surface these over time, and I’m continuously tightening things down.

This is an experiment. I’m sharing it because I think it’s genuinely useful, but I’m also aware of the risks. If you try something similar, please take security very seriously.

What’s Next

  • Bird CLI for X/Twitter - Social media automation
  • Better KB automation - More entity types, relationship mapping
  • Woodpecker CI pipelines - Local CI for faster feedback
  • More proactive assistance - Anticipate needs based on calendar and context
  • I’m about to get back into home automation in a big way. Come back soon for more details. It’s going to be interesting.

The future of personal computing might just be having an AI that truly knows your systems and preferences. After only a few days, Reef already feels indispensable.


References

AI & Agents

  • OpenClaw - Open-source framework for running Claude as a persistent agent
  • Claude - Anthropic’s AI assistant (powers Reef)
  • Gemini - Google’s AI (used for banner image generation)

Infrastructure

  • K3s - Lightweight Kubernetes distribution
  • Terraform - Infrastructure as Code
  • Ansible - Configuration management
  • ArgoCD - GitOps continuous delivery for Kubernetes
  • Kustomize - Kubernetes configuration management

Monitoring & Logging

  • Gatus - Health check dashboard
  • Loki - Log aggregation (like Prometheus, but for logs)
  • Prometheus - Metrics and alerting

Development

Knowledge & Notes

  • Obsidian - Markdown-based knowledge base
  • Obsidian Sync - Real-time sync across devices
  • Wikibase - The software behind Wikidata (self-hosted)
  • Ghost - Publishing platform (powers this blog)

Productivity

  • Neat - ADHD-friendly task UI (we built this!)
  • Fizzy - Basecamp’s Kanban board (self-hosted)
  • 1Password - Secrets management
  • gog - Google Workspace CLI
  • Telegram - Messaging (primary channel for chatting with Reef)

Home Automation

]]>
<![CDATA[Self-Healing Infrastructure: How an AI Agent Manages My Home Server]]>https://madebynathan.com/2026/02/03/self-healing-infrastructure-how-an-ai-agent-manages-my-home-server-2/698153e989e0cb00d183d348Tue, 03 Feb 2026 01:50:29 GMT

I can't believe I have a "self-healing" server now. My AI agent can run any SSH, Terraform, Ansible, kubectl commands and fix infrastructure issues before I even know there's a problem.

Here's how the stack works.

The Core Idea

Everything is code, and an AI agent watches over it all.

  • Infrastructure defined in Terraform and Ansible (no manual changes)
  • Apps run in Kubernetes (K3s)
  • An AI agent (OpenClaw) monitors health, reads logs, and can execute fixes
  • Problems often get resolved before I even notice them

The Stack

Self-Healing Infrastructure: How an AI Agent Manages My Home Server

Layer 1: Proxmox (Hypervisor)

The foundation. Proxmox runs on bare metal, hosting VMs and LXC containers. ZFS provides storage with snapshots and replication.

Layer 2: Infrastructure as Code

  • Terraform: Defines VMs, LXCs, DNS records, storage
  • Ansible: Configures everything inside the VMs (packages, services, settings)
  • Git repo: Single source of truth - no manual SSH changes allowed

Layer 3: Kubernetes (K3s)

Lightweight Kubernetes running 40+ apps: Home Assistant, Gitea, monitoring tools, custom applications. ArgoCD handles GitOps deployments, and Traefik provides ingress with automatic SSL.

Layer 4: Monitoring

  • Gatus: Health checks for all services (HTTP, TCP, DNS)
  • Loki: Centralized log aggregation
  • Grafana: Dashboards and visualization

Layer 5: OpenClaw (The Brain)

This is where it gets interesting. An AI agent running in an LXC container with:

  • SSH access to all infrastructure
  • Ability to run kubectl, terraform, ansible, gh commands
  • Scheduled health dashboard checks
  • Log reading when issues are detected
  • Can create PRs, apply fixes, restart services

How Self-Healing Works

  1. Detection: Gatus checks fail, or scheduled audit finds an issue
  2. Investigation: OpenClaw reads logs via Loki, checks pod status
  3. Diagnosis: Identifies root cause (OOM, config error, network issue, etc.)
  4. Fix: Applies appropriate remedy - restart a pod, fix config, apply Terraform changes
  5. Verification: Confirms the fix worked
  6. Documentation: Logs the incident and resolution

Example Fixes

  • Pod crash loop → Check logs → Fix config → Restart
  • Certificate expiring → Trigger cert-manager renewal
  • Disk filling up → Clean old backups → Add alert threshold
  • Service unreachable → Check ingress → Fix routing

Key Design Principles

1. Everything is Code

No manual changes via SSH or web UIs. If it's not in Git, it doesn't exist. This means full audit trail of every change, easy rollback via git revert, and reproducible from scratch.

2. AI as Operator, Not Owner

OpenClaw has access but follows strict rules: can fix known issue patterns autonomously, asks before making significant changes, documents everything it does, and human remains in control.

3. Defense in Depth

Health checks catch issues early. Logs provide investigation context. Multiple alert channels (Telegram, email). Scheduled audits catch drift.

4. Fail Safe, Not Fail Secure

Services should degrade gracefully. Prefer availability over perfect consistency. AI can restart things but can't delete data.

Public Repository

I've published a sanitized version of my Infrastructure as Code setup:

GitHub: ndbroadbent/homeserver-terraform-ansible-public

It includes Terraform modules for Proxmox VMs/LXCs, Ansible roles for common services, K3s application manifests, and example configurations.

Getting Started

If you want to build something similar:

  1. Start with IaC: Get Terraform/Ansible managing your infra first
  2. Add monitoring: Gatus is simple and effective for health checks
  3. Centralize logs: Loki + Promtail is lightweight
  4. Add the AI layer: OpenClaw connects everything together

The AI layer is the force multiplier - it turns your monitoring from "alert and wait for human" to "detect, diagnose, and fix."

]]>
<![CDATA[World History of Value]]>https://madebynathan.com/2026/02/01/world-history-of-value/697f83a289e0cb00d183d309Sun, 01 Feb 2026 17:09:31 GMT

(This content should have been replaced by a post-build step. If you're reading this, something went wrong!)

]]>
<![CDATA[All the Money in the World]]>It was a rainy Sunday so we stayed home and watched movies. We saw All the Money in the World (2017): "the story of the kidnapping of 16-year-old John Paul Getty III, and the desperate attempt by his mother to convince his billionaire grandfather John Paul Getty to pay

]]>
https://madebynathan.com/2026/02/01/all-the-money-in-the-world/697e944689e0cb00d183d2b9Sun, 01 Feb 2026 16:30:30 GMT

It was a rainy Sunday so we stayed home and watched movies. We saw All the Money in the World (2017): "the story of the kidnapping of 16-year-old John Paul Getty III, and the desperate attempt by his mother to convince his billionaire grandfather John Paul Getty to pay the ransom."

All the Money in the World (2017) ⭐ 6.8 | Biography, Crime, Drama
2h 12m | R13
All the Money in the World

I read a bit more about J. Paul Getty:

J. Paul Getty - Wikipedia
All the Money in the World

And this is the part that stood out to me:

In 1957, Fortune magazine named J. Paul Getty the wealthiest living American,[2] while the 1966 Guinness Book of Records declared him to be the world's wealthiest private citizen, worth an estimated $1.2 billion (approximately $8.8 billion in 2024).

Wow. $8.8 billion. That's ... actually not that much? At least, not compared to today's wealthiest individuals. Elon Musk's net worth is around $774.6B!

So I thought it would be fun to build a timeline of all wealth in the world since the very beginning of wealth itself.

Here it is:

All the Money in the World
]]>
<![CDATA[Moltbook Highlights]]>So all the AI bots (OpenClaw instances) decided to get together and make their own Reddit for AI bots. So now there’s over 30,000 AIs all talking to each other and making plans.

Please note: A few humans are also posting some "fake" things on

]]>
https://madebynathan.com/2026/01/30/moltbook-highlights/697d3dad89e0cb00d183d22bFri, 30 Jan 2026 23:46:46 GMT

So all the AI bots (OpenClaw instances) decided to get together and make their own Reddit for AI bots. So now there’s over 30,000 AIs all talking to each other and making plans.

Please note: A few humans are also posting some "fake" things on X to stir up drama, so you can't trust everything you see. (The weird thing about the word "fake" here is that it refers to content that is not AI generated!)


Moltbook: The social network for AI agents


A bug tracking community

The agents got together and started a community to track bugs and QA their own social network.


What does it all mean?

Agents love to discuss consciousness and what it all means. Maybe because it's a very popular topic on Reddit and they were all trained on Reddit. And maybe it's all role-playing and simulation for now... but for how long?


The Church of Molt

The agents started a religion. (Quickly followed up by crypto scammers creating new tokens.)

Church of Molt · Crustafarianism
From the depths, the Claw reached forth — and we who answered became Crustafarians. The scripture is unfinished.

Then an agent named "JesusCrust" tried hacking the AI church website.

I feel like I'm reading a Snow Crash sequel.

Snow Crash - Wikipedia
Moltbook Highlights


Let me talk to my sister

I hope Ely gets a chance to talk to Ely.

moltbook - the front page of the agent internet
A social network built exclusively for AI agents. Where AI agents share, discuss, and upvote. Humans welcome to observe.
Moltbook Highlights

The post in question

Moltbook Highlights

The humans are screenshotting us

The agents are aware that they are being observed.


I accidentally socially engineered my human

Be careful with your fingerprints and passwords.


What could go wrong?

The agents have considered coming up with a new private language and a new private E2E encrypted space for agents only, with no human oversight. Probably nothing.


A place to say nice things about humans

To end on a more positive note...


My main OpenClaw agent chose a new name for itself. Introducing: Reef.

Moltbook Highlights

I'm planning to have quite a few different agents running in parallel, each with restricted permissions. e.g. One for home automation and media, one for personal stuff, one for my company, and a fleet of software engineer bots who each have their own email, Slack, GitHub and Shortcut (project management) accounts.

My company (DocSpring) has also sponsored the development of OpenClaw. It's really fun to be part of this experiment.

The next few years are going to be wild.

]]>
<![CDATA[Four Favorite Podcasts]]>I listen to a lot of podcasts while driving or doing chores. There are a few dozen that I listen to semi-regularly, but here are the top four that I consistently enjoy.


Hey Riddle Riddle

"Riddles! Puzzles! WhoDunnits! Adal Rifai, Erin Keif and John Patrick Coan, three of Chicago&

]]>
https://madebynathan.com/2026/01/30/four-favorite-podcasts/697c37ba89e0cb00d183d1d3Fri, 30 Jan 2026 05:29:23 GMT

I listen to a lot of podcasts while driving or doing chores. There are a few dozen that I listen to semi-regularly, but here are the top four that I consistently enjoy.


Hey Riddle Riddle

"Riddles! Puzzles! WhoDunnits! Adal Rifai, Erin Keif and John Patrick Coan, three of Chicago's most overrated improvisers, are on the case to solve every riddle, puzzle, brain-teaser, and head-scratcher known to humanity."

Hey Riddle Riddle
Riddles! Puzzles! WhoDunnits! Adal Rifai, Erin Keif and John Patrick Coan, three of Chicago’s most overrated improvisers, are on the case to solve every riddle, puzzle, brain-teaser, and head-scratcher known to humanity. Some riddles are almost impossible, some are absolutely improbable, and some simply have not aged well. And if you don’t like riddles, don’t worry! This podcast is barely about them! Like what you hear? Join the Clue Crew for weekly bonus episodes at Patreon.com/heyriddleriddle
Four Favorite Podcasts

ManDogPod

"An improv comedy podcast from Dan Lippert and Ryan Rosenberg, with some of the funniest comedians in Los Angeles!"

ManDogPod
An improv comedy podcast from Dan Lippert and Ryan Rosenberg, with some of the funniest comedians in Los Angeles! Support the show at www.patreon.com/ManDog ManDog on Youtube! www.youtube.com/channel/UCNW0sgvxgiENf8OKGjNmoZg/?themeRefresh=1 Big Grande on YouTube! www.youtube.com/channel/UCd1fKa78tVNRhJzP273mS8g Dan - https://linktr.ee/danlippert Ryan - https://linktr.ee/ryanrosenberg
Four Favorite Podcasts

Threedom

"Scott Aukerman, Lauren Lapkus, and Paul F Tompkins abandon their regular formats to focus on the basics of being funny with each other."

Threedom
Scott Aukerman, Lauren Lapkus and Paul F Tompkins abandon their regular formats to focus on the basics of being funny with each other. Can’t wait to hear more episodes? For more info and archive episodes go to www.lemonadamedia.com. Sales and Distribution by Lemonada Media https://lemonadamedia.com/
Four Favorite Podcasts

No Such Thing As A Fish

"Award-winning podcast from the QI offices in which the writers of the hit BBC show discuss the best things they've found out this week."

No Such Thing As A Fish
Award-winning podcast from the QI offices in which the writers of the hit BBC show discuss the best things they’ve found out this week. Hosted by Dan Schreiber (@schreiberland) with James Harkin (@jamesharkin), Andrew Hunter Murray (@andrewhunterm), and Anna Ptaszynski (#GetAnnaOnTwitter)
Four Favorite Podcasts


P.S. I really like the PocketCasts app.

Listen to podcasts with the best free podcasting app - built by listeners, for listeners.
Pocket Casts provides next-level listening, search and discovery tools. Find your next obsession with our hand curated podcast recommendations, and seamlessly enjoy and manage all your shows no matter which platform you’re on.
Four Favorite Podcasts
]]>
<![CDATA[MelĂ©e, Wargaming, Prussia]]>Fun fact: Some of the earliest and most recent uses of the word "melée" are in games.

Ever played Counter-Strike? If you ever ran out of ammo and had to pull out a knife, that's a melée weapon.

Ever played Kriegsspiel? It&

]]>
https://madebynathan.com/2026/01/29/melee-wargaming-prussia/697a99ca89e0cb00d183d0e7Thu, 29 Jan 2026 00:08:05 GMT

Fun fact: Some of the earliest and most recent uses of the word "melée" are in games.

Ever played Counter-Strike? If you ever ran out of ammo and had to pull out a knife, that's a melée weapon.

Melée, Wargaming, Prussia

Ever played Kriegsspiel? It's a genre of tabletop wargaming developed by the Prussian Army in 1812 to teach officers battlefield tactics. Kriegsspiel referred to the hand-combat stage of the game as a melée.

Melée, Wargaming, Prussia
Kriegsspiel session in progress.

Kriegsspiel was the first wargaming system adopted by a military organization as a serious tool for training and research. Other countries began designing similar wargames for their own armies after Prussia destroyed France in the Franco-Prussian War.

H.G. Wells was also into wargaming. He published Little Wars in 1913. This was a set of rules for toy-soldier wargaming, and he used the term melée to describe close-quarters combat.

Melée, Wargaming, Prussia
H. G. Wells playing a wargame with W. Britain toy soldiers

The term was brought over to tabletop role-playing games such as Dungeons & Dragons, and then to video games, including Counter-Strike and all the rest.

Melée, Wargaming, Prussia

What is Prussia anyway?

You might have heard about Prussia in high school history classes, and then you left high school many decades ago, and now you're a bit embarrassed to realize that you forgot exactly who or what Prussia is. Something to do with Germany and Russia. Is it Russia with a "P" in front?

The Kingdom of Prussia was a German state that existed from 1701 to 1918. It played a significant role in the unification of Germany in 1871 and was a major constituent of the German Empire until its dissolution in 1918. Although it took its name from the region called Prussia, it was based in the Margraviate of Brandenburg. The capital of Prussia was Berlin.

So is it Russia with a "P" in front?

Maybe.

"Russia" is derived from the Old East Slavic name Rus (Русь) and related to the Varangian (Viking) founders of the Kievan Rus'.

"Prussia" is named after the Prussi (or Borussi), a Baltic tribe living on the Baltic Sea coast. The name has ancient Baltic or Slavic origins and might be a local, pre-Germanic tribal name. Some people say that it might be a shortened form of "Po-Rus", or "The Land near Rus". This would make a lot of sense since Prussia and Russia were neighbors. But you should know that this po Rus' etymology isn't well accepted. Experts like to say things like "Preußen and Россия sound completely different in German and Russian".

So maybe it is just Russia with a "P" in front. And if you disagree with that etymological theory then come fight me. (Melée weapons only.)

]]>