<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.joshbeckman.org/feed/blog.xml" rel="self" type="application/atom+xml" /><link href="https://www.joshbeckman.org/" rel="alternate" type="text/html" /><updated>2026-04-22T23:49:58+00:00</updated><id>https://www.joshbeckman.org/feed/blog.xml</id><title type="html">Josh Beckman’s Organization | Blog</title><subtitle>Building in the open</subtitle><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><entry><title type="html">Roast Notes: Brazil Cerrado from Printer’s Row</title><link href="https://www.joshbeckman.org/blog/roast-notes-brazil-cerrado-from-printers-row" rel="alternate" type="text/html" title="Roast Notes: Brazil Cerrado from Printer’s Row" /><published>2026-04-19T00:00:00+00:00</published><updated>2026-04-19T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/roast-notes-brazil-cerrado-from-printers-row</id><content type="html" xml:base="https://www.joshbeckman.org/blog/roast-notes-brazil-cerrado-from-printers-row"><![CDATA[<p>I picked up Printer’s Row Coffee Co.’s <a href="https://www.printersrowcoffeeco.com/shop/brazil-cerrado-12oz">Brazil Cerrado</a> only a couple days after it was roasted.
We walked over as a family on Saturday morning and the roastery was <em>busy</em> with other families and couples after a run. The table beside us was writing either a TikTok skit or a screenplay, I couldn’t divine which.</p>

<p><img src="/assets/images/L1000405.jpeg" alt="Bag of Brazil Cerrado from Printer's Row Coffee Co." /></p>

<table>
  <thead>
    <tr>
      <th>Aspects</th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Roast</td>
      <td>Medium/Light</td>
    </tr>
    <tr>
      <td>Origin</td>
      <td>Brazil (Cerrado)</td>
    </tr>
    <tr>
      <td>Process</td>
      <td>Fully washed &amp; sun dried</td>
    </tr>
    <tr>
      <td>Roast Date</td>
      <td>2026-04-15</td>
    </tr>
  </tbody>
</table>

<p>In the smell, I find sweet walnut and chocolate, really like a candy coffee.</p>

<table>
  <thead>
    <tr>
      <th>Brew</th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Grind</td>
      <td>Medium-Fine</td>
    </tr>
    <tr>
      <td>Method</td>
      <td>Chemex pour-over</td>
    </tr>
    <tr>
      <td>Ratio</td>
      <td>1:14</td>
    </tr>
    <tr>
      <td>Water Temp</td>
      <td>199°F</td>
    </tr>
  </tbody>
</table>

<p>This smooth brew tastes immediately like baker’s chocolate and mandarin orange. With such a deep flavor, it still tastes crisp, wafting off my tongue.
I really like this one.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="coffee" /><category term="chicago" /><summary type="html"><![CDATA[I picked up Printer’s Row Coffee Co.’s Brazil Cerrado only a couple days after it was roasted. We walked over as a family on Saturday morning and the roastery was busy with other families and couples after a run. The table beside us was writing either a TikTok skit or a screenplay, I couldn’t divine which.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/L1000405.jpeg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/L1000405.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Automating My Apple Music Library Export</title><link href="https://www.joshbeckman.org/blog/practicing/automating-my-apple-music-library-export" rel="alternate" type="text/html" title="Automating My Apple Music Library Export" /><published>2026-04-16T11:00:00+00:00</published><updated>2026-04-16T11:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/practicing/automating-my-apple-music-library-export</id><content type="html" xml:base="https://www.joshbeckman.org/blog/practicing/automating-my-apple-music-library-export"><![CDATA[<p>Two years ago I wrote <a href="/blog/pulling-fun-insights-out-of-my-apple-music-library">a parser that pulls fun insights out of my Apple Music library</a> and generates my <a href="/music">Music Listening page</a>. The pipeline worked well but it had an annoying manual step: I had to open Music.app, click File &gt; Library &gt; Export Library, navigate to the right directory, and save the XML file before running the script. It was enough friction that I’d forget to do it for months.</p>

<h2 id="the-automation">The automation</h2>

<p>Apple Music doesn’t expose a “library export” command in its AppleScript dictionary, so the only option is GUI scripting through System Events. The script:</p>

<ol>
  <li>Activates Music.app</li>
  <li>Navigates the menu: File &gt; Library &gt; Export Library…</li>
  <li>Uses Cmd+Shift+G in the save dialog to jump to the project directory</li>
  <li>Clicks Save</li>
  <li>Waits for the ~46MB XML file to finish writing (by polling file size until it stabilizes)</li>
  <li>Runs the existing <code class="language-plaintext highlighter-rouge">update_music</code> Ruby script to regenerate the page</li>
  <li>Commits any changes to <code class="language-plaintext highlighter-rouge">blog/</code> and <code class="language-plaintext highlighter-rouge">assets/</code> and pushes to deploy</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./utilities/export_and_update_music
</code></pre></div></div>

<p>The interesting part was discovering that Music.app opens a standalone <code class="language-plaintext highlighter-rouge">window "Save"</code> rather than the more common <code class="language-plaintext highlighter-rouge">sheet 1 of window 1</code> pattern that most macOS apps use for save dialogs. That small difference was the only real debugging needed.</p>

<h2 id="running-it-on-a-schedule">Running it on a schedule</h2>

<p>Since GUI scripting requires a logged-in session with screen access, a plain cron job won’t work. Instead, I’m using a <code class="language-plaintext highlighter-rouge">launchd</code> agent with <code class="language-plaintext highlighter-rouge">LimitLoadToSessionType: Aqua</code>, which ensures it only fires when I’m actually at my Mac.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;key&gt;</span>LimitLoadToSessionType<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;string&gt;</span>Aqua<span class="nt">&lt;/string&gt;</span>
</code></pre></div></div>

<p>It runs weekly on Sunday mornings. Unlike cron, <code class="language-plaintext highlighter-rouge">launchd</code> with <code class="language-plaintext highlighter-rouge">StartCalendarInterval</code> will fire a missed job on wake, so if my laptop was sleeping during the scheduled time, it catches up as soon as I open the lid.</p>

<p>The other <code class="language-plaintext highlighter-rouge">launchd</code> gotcha: the agent runs with a minimal environment, not your shell profile. I had to set <code class="language-plaintext highlighter-rouge">PATH</code> (to find Homebrew’s Ruby and <code class="language-plaintext highlighter-rouge">bundle</code>) and <code class="language-plaintext highlighter-rouge">LANG</code> (to <code class="language-plaintext highlighter-rouge">en_US.UTF-8</code>, since the plist parser chokes on non-ASCII track metadata without it) in the plist’s <code class="language-plaintext highlighter-rouge">EnvironmentVariables</code>.</p>

<h2 id="requirements">Requirements</h2>

<p>The prerequisite is granting Accessibility permissions (System Settings &gt; Privacy &amp; Security &gt; Accessibility). When running from a terminal, your terminal app needs the permission. When running via <code class="language-plaintext highlighter-rouge">launchd</code>, <code class="language-plaintext highlighter-rouge">/usr/bin/env</code> needs it instead. Since the script’s shebang uses <code class="language-plaintext highlighter-rouge">#!/usr/bin/env bash</code>, <code class="language-plaintext highlighter-rouge">env</code> is the parent process that macOS checks for Accessibility access.</p>

<p>The full script is in <a href="https://github.com/joshbeckman/notes/blob/master/utilities/export_and_update_music">the repo</a>.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="practicing" /><category term="code-snippets" /><category term="music" /><category term="automation" /><summary type="html"><![CDATA[Using macOS GUI scripting to fully automate my music stats pipeline]]></summary></entry><entry><title type="html">PG: Psycho Goreman</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1276440874-pg-psycho-goreman" rel="alternate" type="text/html" title="PG: Psycho Goreman" /><published>2026-04-10T00:00:00+00:00</published><updated>2026-04-10T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1276440874-pg-psycho-goreman</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1276440874-pg-psycho-goreman"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/5/9/5/0/6/8/595068-pg-psycho-goreman--0-600-0-900-crop.jpg?v=a658ba21ff" /></p>
<p>This could be described as an elongated and inverted <em>Power Rangers</em> special, where the kids are bad and the parents are toxic and the villains are goofy-gorey and the lessons are the same.<br />It's super fun in a heightened camp sort of way - had me laughing out loud watching it at home - and I appreciate someone stretching the idea as far as it could go.<br />I laughed the most at the throwaway lines ("well, this TV just won't stop bleeding") and background characters (the way Cassius 3000 has an "idle animation", the melted police officer continuously trying to kill himself, etc.) and I think that's where this movie shines: oozing sunny gore into a kid's life. It's sneakily earnest beneath the camp, capping gore-soaked subversion with kids-show morals.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[This could be described as an elongated and inverted Power Rangers special, where the kids are bad and the parents are toxic and the villains are goofy-gorey and the lessons are the same.It's super fun in a heightened camp sort of way - had me laughing out loud watching it at home - and I appreciate someone stretching the idea as far as it could go.I laughed the most at the throwaway lines ("well, this TV just won't stop bleeding") and background characters (the way Cassius 3000 has an "idle animation", the melted police officer continuously trying to kill himself, etc.) and I think that's where this movie shines: oozing sunny gore into a kid's life. It's sneakily earnest beneath the camp, capping gore-soaked subversion with kids-show morals.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/5/9/5/0/6/8/595068-pg-psycho-goreman--0-600-0-900-crop.jpg?v=a658ba21ff" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/5/9/5/0/6/8/595068-pg-psycho-goreman--0-600-0-900-crop.jpg?v=a658ba21ff" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cléo from 5 to 7</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1273854132-cleo-from-5-to-7" rel="alternate" type="text/html" title="Cléo from 5 to 7" /><published>2026-04-08T00:00:00+00:00</published><updated>2026-04-08T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1273854132-cleo-from-5-to-7</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1273854132-cleo-from-5-to-7"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/5/1/6/1/1/51611-cleo-from-5-to-7-0-600-0-900-crop.jpg?v=57e2214b3d" /></p>
<p>So much of the film is perfect alignment or beautiful discord. She descends the staircase on footsteps in perfect rhythm with the score. The framed balance of the final scenes and the chance encounter and the timing of friends arriving shows the world aligning for her, supporting her. She carelessly drags her scarf as she stumbles into unexpected nature. The myriad mirrors in the shop and the jumbled overheard conversations in the cafe and the impulsive merry-go-round bus ride and the grotesque street performers show the world as constantly breaking and folding in upon itself. And Cléo finds her happiness from learning to accept both, woven together as she walks. It reminds me of my best walks.</p>
<p>Good things come to those who go for a walk.</p>
<p>Varda studied photography prior to filmmaking and it shows in the gorgeous framing and visuals throughout the film. The multi-layered mirrors in the shop were mesmerizing. The perfect framing and long shot of the couple leaving the park was decadent. It made me want to pick up a camera and go outside.</p>
<p>It appealed to my voyeuristic soul. I am forever listening in on the floating conversations of the tables around me, and this channeled that pleasure onto screen.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[So much of the film is perfect alignment or beautiful discord. She descends the staircase on footsteps in perfect rhythm with the score. The framed balance of the final scenes and the chance encounter and the timing of friends arriving shows the world aligning for her, supporting her. She carelessly drags her scarf as she stumbles into unexpected nature. The myriad mirrors in the shop and the jumbled overheard conversations in the cafe and the impulsive merry-go-round bus ride and the grotesque street performers show the world as constantly breaking and folding in upon itself. And Cléo finds her happiness from learning to accept both, woven together as she walks. It reminds me of my best walks. Good things come to those who go for a walk. Varda studied photography prior to filmmaking and it shows in the gorgeous framing and visuals throughout the film. The multi-layered mirrors in the shop were mesmerizing. The perfect framing and long shot of the couple leaving the park was decadent. It made me want to pick up a camera and go outside. It appealed to my voyeuristic soul. I am forever listening in on the floating conversations of the tables around me, and this channeled that pleasure onto screen.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/5/1/6/1/1/51611-cleo-from-5-to-7-0-600-0-900-crop.jpg?v=57e2214b3d" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/5/1/6/1/1/51611-cleo-from-5-to-7-0-600-0-900-crop.jpg?v=57e2214b3d" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Roast Notes: Tanzania from Printer’s Row</title><link href="https://www.joshbeckman.org/blog/roast-notes-tanzania-from-printers-row" rel="alternate" type="text/html" title="Roast Notes: Tanzania from Printer’s Row" /><published>2026-04-06T14:04:53+00:00</published><updated>2026-04-06T14:04:53+00:00</updated><id>https://www.joshbeckman.org/blog/roast-notes-tanzania-from-printers-row</id><content type="html" xml:base="https://www.joshbeckman.org/blog/roast-notes-tanzania-from-printers-row"><![CDATA[<p>I’m very lucky that the closest coffee roastery to me specializes in light roasts, my favorites. I know Printer’s Row Coffee Co.’s <a href="https://www.printersrowcoffeeco.com/shop/tanzania-kilimanjaro-pb">Tanzania Kilimanjaro</a> beans as “the bag with the yellow label,” I get their beans so often without thinking.</p>

<p><img src="/assets/images/f5849f83-8463-4f69-806e-d2bc3fa6f9c4.jpeg" alt="Holding the bag of Tanzania from Printer's Row as Spring blooms in the yard" /></p>

<table>
  <thead>
    <tr>
      <th>Aspects</th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Roast</td>
      <td>Light</td>
    </tr>
    <tr>
      <td>Origin</td>
      <td>Tanzania</td>
    </tr>
    <tr>
      <td>Roast Date</td>
      <td>2026-03-25</td>
    </tr>
  </tbody>
</table>

<p>Smelling the beans and the resulting grind, I smell grass and raspberries. It’s very flowery, almost wearable as a perfume.</p>

<table>
  <thead>
    <tr>
      <th>Brew</th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Grind</td>
      <td>Medium-Fine</td>
    </tr>
    <tr>
      <td>Method</td>
      <td>Chemex Pour-over</td>
    </tr>
    <tr>
      <td>Ratio</td>
      <td>1:14</td>
    </tr>
    <tr>
      <td>Water Temp</td>
      <td>199°F</td>
    </tr>
  </tbody>
</table>

<p>Brewed into the Chemex and sipped as I watched the sun drip its way down our walls in the early Spring morning, I taste more grass, black cherry, and almonds. I love light roasts.</p>

<p>The flavors aren’t quite <em>bitter</em>, but they dry out my tongue. It reminds me of the taste and dryness of water immediately leaving my mouth <a href="https://www.joshbeckman.org/blog/traveling/hiking-the-grand-canyon-royal-arch-loop">as we hiked the Grand Canyon</a> in desert heat. The light roast bites on the roof of my mouth and then makes my cheeks clench a bit as the tart kicks in. It’s a good bite, and leaves me smiling.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="coffee" /><category term="chicago" /><summary type="html"><![CDATA[I’m very lucky that the closest coffee roastery to me specializes in light roasts, my favorites. I know Printer’s Row Coffee Co.’s Tanzania Kilimanjaro beans as “the bag with the yellow label,” I get their beans so often without thinking.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/f5849f83-8463-4f69-806e-d2bc3fa6f9c4.jpeg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/f5849f83-8463-4f69-806e-d2bc3fa6f9c4.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">One Spoon of Chocolate</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1268465966-one-spoon-of-chocolate" rel="alternate" type="text/html" title="One Spoon of Chocolate" /><published>2026-04-04T00:00:00+00:00</published><updated>2026-04-04T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1268465966-one-spoon-of-chocolate</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1268465966-one-spoon-of-chocolate"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/1/1/7/3/8/7/7/1173877-one-spoon-of-chocolate-0-600-0-900-crop.jpg?v=5192bfa03f" /></p>
<p>...and one spoon of lemon, and one spoon of oregano, and one spoon of cayenne, and one spoon of...</p>
<p>This film had so many homages and styles strung together that it was too hard for me to find a foothold. The narrative broke down when each scene was playing like an individual track in an album, rather than a story beat.</p>
<p>I was glad to hear RZA's thoughts on the film (in the Q+A immediately following the screening), where he talked about his anchoring idea that movies can just be entertainment. Because that's what I saw: he loves so many different types of film and he wanted to have a clip of each style in <em>his</em> film; he likes sex scenes, so he added sex scenes when things slowed down; he wanted to be entertained. But that potluck approach made each homage feel short-changed and I never picked up a rhythm strong enough to keep me flowing in the film's story. I was entertained, but not much more.</p>
<p>Ned and I were eating at Little Goat Diner down the street before the showing and we were talking about why it's harder to review music than film. And one of my thoughts was that films are consumed as one piece, one package, one arc with a single storyline. And albums have discrete tracks that each rise and fall and so it's harder to assemble a narrative from the multiple pieces. This film echoes that idea, I think, as an example in the opposite direction: each scene was written and shot in a different direction, with only half an eye to the overall story. I kinda think that if RZA had actually had each scene be <em>completely</em> contained and more dramatically differently-styled - even as incongruuous as that would be - it would have paid stronger homage to each source and would have been a stronger statement.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[...and one spoon of lemon, and one spoon of oregano, and one spoon of cayenne, and one spoon of... This film had so many homages and styles strung together that it was too hard for me to find a foothold. The narrative broke down when each scene was playing like an individual track in an album, rather than a story beat. I was glad to hear RZA's thoughts on the film (in the Q+A immediately following the screening), where he talked about his anchoring idea that movies can just be entertainment. Because that's what I saw: he loves so many different types of film and he wanted to have a clip of each style in his film; he likes sex scenes, so he added sex scenes when things slowed down; he wanted to be entertained. But that potluck approach made each homage feel short-changed and I never picked up a rhythm strong enough to keep me flowing in the film's story. I was entertained, but not much more. Ned and I were eating at Little Goat Diner down the street before the showing and we were talking about why it's harder to review music than film. And one of my thoughts was that films are consumed as one piece, one package, one arc with a single storyline. And albums have discrete tracks that each rise and fall and so it's harder to assemble a narrative from the multiple pieces. This film echoes that idea, I think, as an example in the opposite direction: each scene was written and shot in a different direction, with only half an eye to the overall story. I kinda think that if RZA had actually had each scene be completely contained and more dramatically differently-styled - even as incongruuous as that would be - it would have paid stronger homage to each source and would have been a stronger statement.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/1/1/7/3/8/7/7/1173877-one-spoon-of-chocolate-0-600-0-900-crop.jpg?v=5192bfa03f" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/1/1/7/3/8/7/7/1173877-one-spoon-of-chocolate-0-600-0-900-crop.jpg?v=5192bfa03f" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Roast Notes: Fishtown by La Colombe</title><link href="https://www.joshbeckman.org/blog/roast-notes-fishtown-by-la-colombe" rel="alternate" type="text/html" title="Roast Notes: Fishtown by La Colombe" /><published>2026-04-03T14:42:44+00:00</published><updated>2026-04-03T14:42:44+00:00</updated><id>https://www.joshbeckman.org/blog/roast-notes-fishtown-by-la-colombe</id><content type="html" xml:base="https://www.joshbeckman.org/blog/roast-notes-fishtown-by-la-colombe"><![CDATA[<p>I don’t usually buy from La Colombe, but we were killing time before our favorite fish taco place opened up and I thought I might as well try some <a href="https://www.lacolombe.com/products/fishtown">Fishtown</a>.</p>

<p><img src="/assets/images/05ae147f-e8ba-4f2e-b320-b2240e826abe.jpeg" alt="La Colombe Fishtown beans" /></p>

<table>
  <thead>
    <tr>
      <th>Aspects</th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Roast</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td>Origin</td>
      <td>Blend</td>
    </tr>
    <tr>
      <td>Roast Date</td>
      <td>Unspecified</td>
    </tr>
  </tbody>
</table>

<p>Smelling the beans and the resulting grind, I smell white hyacinth and it brings to mind the sight of the yellow forsythia outside, blooming in the early spring. It’s a delicate scent that is brighter than I expected with the medium roast.</p>

<table>
  <thead>
    <tr>
      <th>Brew</th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Grind</td>
      <td>Medium-Fine</td>
    </tr>
    <tr>
      <td>Method</td>
      <td>V60 Pour-over</td>
    </tr>
    <tr>
      <td>Ratio</td>
      <td>1:14</td>
    </tr>
    <tr>
      <td>Water Temp</td>
      <td>199°F</td>
    </tr>
  </tbody>
</table>

<p>Brewed into a big mug and sipped as I look at the wet spring outside, I taste ripe cherries, bergamot, and white chocolate. I like the light flavor, but it fades quickly on my tongue.</p>

<p>Ultimately, these beans are no single origin with no known roast date, so I find it hard to pin this down to specifics. Like its maker, the beans are high quality but ultimately blend in to their surroundings in the shopping strip.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="coffee" /><summary type="html"><![CDATA[I don’t usually buy from La Colombe, but we were killing time before our favorite fish taco place opened up and I thought I might as well try some Fishtown.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/05ae147f-e8ba-4f2e-b320-b2240e826abe.jpeg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/05ae147f-e8ba-4f2e-b320-b2240e826abe.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bant</title><link href="https://www.joshbeckman.org/blog/bant" rel="alternate" type="text/html" title="Bant" /><published>2026-04-02T00:00:00+00:00</published><updated>2026-04-02T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/bant</id><content type="html" xml:base="https://www.joshbeckman.org/blog/bant"><![CDATA[<p><img src="https://bant.kram-beckman.com/bant-lounging.jpeg" alt="Bant lounging" /></p>

<p>Our eldest cat Bant died on March 31st. She was fifteen years old - a sassy tortoiseshell who found us at a shelter in Champaign when she was six months old. She followed us through seven homes, trained us to leave the faucet running, and slept between us every night.</p>

<p>She was named after a Jedi known for agility and friendship, which fit her well. She never used her claws on people, but she had opinions about everything and made sure you heard them.</p>

<p>I’ll miss her <a href="/desk-february-24-2021">warming herself on my laptop</a> and <a href="/desk-may-21-2022">asking for a boost onto my desk</a> for years to come. Aggressive lymphoma took her quickly at the end, and she passed peacefully at home with us.</p>

<p>We built <a href="https://bant.kram-beckman.com">a memorial site for her</a> where you can read more about her life and leave a message if you’d like.</p>

<p>We miss her.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="bant" /><category term="family" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://bant.kram-beckman.com/bant-lounging.jpeg" /><media:content medium="image" url="https://bant.kram-beckman.com/bant-lounging.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Moving the Critic Into My Editor</title><link href="https://www.joshbeckman.org/blog/practicing/moving-the-critic-into-my-editor" rel="alternate" type="text/html" title="Moving the Critic Into My Editor" /><published>2026-03-29T21:46:00+00:00</published><updated>2026-03-29T21:46:00+00:00</updated><id>https://www.joshbeckman.org/blog/practicing/moving-the-critic-into-my-editor</id><content type="html" xml:base="https://www.joshbeckman.org/blog/practicing/moving-the-critic-into-my-editor"><![CDATA[<p>I was editing <a href="/blog/practicing/three-agents-for-a-knowledge-garden">the blog post about my AI writing critic</a> (based on the critique it had generated) when I realized I didn’t want to be incorporating this feedback <em>after</em> I had already published; I wanted the next critique now, against the draft in front of me.</p>

<p>The <a href="/blog/practicing/three-agents-for-a-knowledge-garden#the-critic-walks-beside">Critic agent</a> I built runs on a cron schedule. It monitors my RSS feed, reads new posts, searches my garden for related writing, and emails me a critique. That’s good for reflection. I read the critique over coffee the next morning, think about it, sometimes update the post. But it means publishing first and polishing later. For a post I cared about getting right before it went out, I wanted the feedback loop tighter.</p>

<h2 id="bring-the-feedback-to-the-source">Bring the feedback to the source</h2>

<p>Feedback is more useful the closer it is to the action. An email critique that arrives hours later is a thought-provoker, <a href="https://www.joshbeckman.org/notes/692534908">only affecting future behavior</a>. A critique that appears inline against your prose, while you’re still shaping it, is a collaborator. I wanted something <a href="https://www.joshbeckman.org/notes/888705369">like inline semantic linting</a>.</p>

<p>I already had the infrastructure: the Critic val on <a href="https://val.town">Val Town</a>, the Anthropic API, the garden search tools. I needed two things: a way to critique unpublished drafts, and a way to see the results in my editor.</p>

<h2 id="the-critique-command">The <code class="language-plaintext highlighter-rouge">:Critique</code> command</h2>

<p>I added a <code class="language-plaintext highlighter-rouge">POST /draft</code> endpoint to the <a href="https://www.val.town/x/joshbeckman/criticCron">Critic val</a> that accepts a title and content (password-protected), runs the same critique pipeline, and returns the result. Then I <a href="https://github.com/joshbeckman/dotfiles/blob/a1eeebd11c81c86abf167f8b82c863fd180fdb7e/.config/nvim/init.vim#L347">wired it into a neovim command</a>:</p>

<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">:</span>Critique
</code></pre></div></div>

<p>The command runs asynchronously via <code class="language-plaintext highlighter-rouge">jobstart()</code> (I get my editor back immediately). About a minute later, two things happen: the full critique opens in a background tab, and inline annotations appear in my buffer via <a href="https://github.com/dense-analysis/ale">ALE</a>.</p>

<blockquote class="markdown-alert markdown-alert-note">
  <p><strong class="markdown-alert-title"><svg width="16" height="16" class="octicon octicon-info mr-2" aria-hidden="true" viewBox="0 0 16 16" version="1.1"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</strong></p>

  <p>If you’re not familiar with ALE and/or [neo]vim, think of these inline annotations as the squiggly spellchecker lines you see in your Microsoft Word and Google Docs.</p>
</blockquote>
<p><img src="/assets/images/critique-ale-display.png" alt="Here's how the critique linter displays in my (neo)vim editor" /></p>

<p>The annotations use ALE’s <code class="language-plaintext highlighter-rouge">other_source</code> API, which lets you push linter-style results from any external process. Each annotation has a line number, a severity (<code class="language-plaintext highlighter-rouge">I</code> for suggestions, <code class="language-plaintext highlighter-rouge">W</code> for clarity issues, <code class="language-plaintext highlighter-rouge">E</code> for errors), and a short message. When the feedback targets a specific phrase, ALE highlights just those words rather than the whole line.</p>

<p>With this, I can navigate around the file and read the feedback inline, or <code class="language-plaintext highlighter-rouge">:lopen</code> the location list window to view all of them at once. Or I can tab to the full-prose critique and see the full context from the critic. In practice, I do all these things and then revise, rinse, and repeat.</p>

<p>The annotation step is deliberately separate from the critique step. The Critic agent has a single job: read the post, research the garden, write a critique in prose. A second, cheaper call maps that prose onto line numbers and phrases. The separation matters for several reasons:</p>

<ul>
  <li>I have a hunch that the critique agent produces better output when it’s not distracted by linter formatting</li>
  <li>The critique stays useful as standalone prose; it’s not locked to a display format</li>
  <li>The annotation mapping is composable: I can map any critique-and-source pair, not just ones the Critic generated</li>
  <li>I can use different model tiers for each: Opus for the critique, Sonnet for the mapping</li>
</ul>

<h2 id="two-resolutions-of-the-same-feedback">Two resolutions of the same feedback</h2>

<p>Seeing the critique at two resolutions simultaneously changed how I process it.</p>

<p>The full critique in the background tab gives me the arc: what’s working, what’s missing, how the piece connects to my other writing. The inline annotations give me specific pressure against specific sentences. I read the tab first to understand the big picture, then work through the inline feedback phrase by phrase.</p>

<p>This also taught me to edit in phases. Saving the file clears the ALE annotations (the linter display is tied to the buffer state), so I make several edits before saving. That batch-editing rhythm turns out to be better anyway; I’m responding to a coherent set of feedback rather than fixing things one at a time.</p>

<p>The async email critic still runs for every published post. It’s more of a thought-provoker, and only sometimes pushes me to update something. The inline critic, because it’s right there next to my words, makes me edit more. Proximity matters.</p>

<h2 id="agents-write-to-me-i-edit-my-own-files">Agents write to me, I edit my own files</h2>

<p>A principle I’m finding: agents should communicate <em>to</em> me, not edit over my work. Code is now an agent’s artifact. The critique is the agent’s artifact. Whatever I’m writing is mine: my thinking, possibly refined by the agent’s interrogation. A linter never rewrites your code; it tells you what to reconsider. The Critic works the same way.</p>

<h2 id="how-it-works">How it works</h2>

<p>The Critic val now has three relevant endpoints:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">POST /draft</code> - accepts <code class="language-plaintext highlighter-rouge">{title, content}</code>, runs Sonnet for research (searching the garden, reading linked sources) and Opus for the final critique. Returns the critique as markdown and HTML.</li>
  <li><code class="language-plaintext highlighter-rouge">POST /annotate</code> - accepts <code class="language-plaintext highlighter-rouge">{content, critique}</code>, uses Sonnet to map each critique point to a line number, severity, and optional verbatim phrase from the source. Returns a JSON array of annotations.</li>
  <li><code class="language-plaintext highlighter-rouge">GET /cron</code> - the original: processes new RSS entries and emails critiques.</li>
</ul>

<p>The neovim <code class="language-plaintext highlighter-rouge">:Critique</code> command chains <code class="language-plaintext highlighter-rouge">/draft</code> and <code class="language-plaintext highlighter-rouge">/annotate</code> asynchronously. It writes the critique to <code class="language-plaintext highlighter-rouge">/tmp</code> (so it’s automatically cleaned up), opens it in a background tab, then pushes annotations to ALE. The phrase-to-column resolution happens in vimscript: if the annotation includes a phrase, <code class="language-plaintext highlighter-rouge">stridx()</code> finds it on the target line and sets <code class="language-plaintext highlighter-rouge">col</code>/<code class="language-plaintext highlighter-rouge">end_col</code> for precise highlighting.</p>

<p>The Sonnet-for-research, Opus-for-critique split was originally a performance optimization - the val was timing out on Val Town’s free tier. It turned out to be the right architecture regardless. No noticeable difference in critique quality, meaningfully faster, and cheaper.</p>

<h2 id="whats-next">What’s next</h2>

<p>The composable annotation endpoint opens other possibilities. Any document paired with any feedback (from an agent or a human) could be mapped to inline annotations: code review comments against a diff, editor notes against a manuscript, study questions against a reading. The pattern generalizes beyond my specific critic.</p>

<p>For now, though, the main thing is simpler: I write drafts, I <code class="language-plaintext highlighter-rouge">:Critique</code> them, and I make more edits because the feedback is right there. The agents write to me. I write my own prose.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="practicing" /><category term="ai" /><category term="writing" /><category term="tools" /><category term="vim" /><category term="feedback" /><summary type="html"><![CDATA[What if you could have an AI critic semantically linting your writing, inline?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/critique-ale-display.png" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/critique-ale-display.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Three LLM Agents for My Knowledge Garden</title><link href="https://www.joshbeckman.org/blog/practicing/three-agents-for-a-knowledge-garden" rel="alternate" type="text/html" title="Three LLM Agents for My Knowledge Garden" /><published>2026-03-26T09:40:00+00:00</published><updated>2026-03-26T09:40:00+00:00</updated><id>https://www.joshbeckman.org/blog/practicing/three-agents-for-a-knowledge-garden</id><content type="html" xml:base="https://www.joshbeckman.org/blog/practicing/three-agents-for-a-knowledge-garden"><![CDATA[<p>I was lifting weights when I realized my notes had gotten lazy. Long quotes, short thoughts, no connections drawn. I’d saved a quote about the laziness of forwarding raw AI output and left <a href="https://www.joshbeckman.org/notes/997876311">no thoughts of my own about it</a>. I was hoarding links instead of digesting them.</p>

<p>I’ve been building <a href="/blog/opening-up-my-highlights-notes">this knowledge garden</a> for years, and the collection has grown to thousands of notes, blog posts, highlights, and replies. I know the latent connections between them are there, but tracing the threads takes work. I usually write from personal experience and prior reading/writing, but want to include actual links to relevant work. Additionally, I don’t always remember prior work that would contradict or inform my current thinking. Having someone check for those things would help me improve.</p>

<p>So I’ve been building agents to push my critical thinking and tend the garden alongside me. Three of them now, each with a different role.</p>

<h2 id="the-pre-reader-walks-in-front">The Pre-Reader walks in front</h2>

<p>I built <a href="/pre-read">Pre-Read</a> to help me figure out whether I have an opinion on a piece. It can help before diving into a long article someone sent me or that I found in my feed. I give it a URL and it searches my garden for things I’ve already written or saved that might relate to the piece. It frames what I already know before I start reading.</p>

<p>This is useful when I haven’t quite solidified my opinion on something. The pre-reader jogs loose some thoughts I can then maybe turn into a note. It walks ahead of me on the trail, scouting what’s familiar.</p>

<p>It produces three perspectives on any given piece: a <em>Proponent</em>, an <em>Opponent</em>, and a <em>Questioner</em>. These mirror how I naturally read things. I usually give an author the benefit of the doubt first — I agree, I look for where it reinforces what I already believe. Then I force myself to take the opposing stance and poke holes in the argument. And when I’m really stuck, I try to think of questions for the author that would open up a better conversation around the topic. The three personas externalize that cycle so I don’t get stuck on step one, which is what happens when I’m just saving a quick note.</p>

<h2 id="the-suggester-walks-behind">The Suggester walks behind</h2>

<p>There are already hundreds of notes here that I’ve neglected to write about. I built an <a href="/uncommented/">Uncommented Notes</a> page that surfaces them - notes where I saved a quote or a link but never added my own thoughts. From there, the <a href="/suggest/">Suggest Comments</a> page takes any post and generates suggested comments I might add: connections to other posts, reactions I haven’t articulated, threads I haven’t pulled. Each suggestion comes as a short paragraph with markdown links to related garden posts.</p>

<p>This gives me a starting point to write my own expansion on the note. I don’t want to copy and paste this output mechanically: the point here is to kickstart my own thinking and put my own words into the system.</p>

<p>These are tools for downtime. On the train, waiting in line - I open the uncommented list, pick a note, and let the suggester stimulate a thought. It helps me fill in blanks productively, which is better than scrolling social media. It walks behind me, picking up what I dropped.</p>

<p>The suggester uses the same three personas (Proponent, Opponent, Questioner) applied to my own posts instead of someone else’s. The same reading cycle works in reverse: where do I agree with my past self, where would I push back now, and what questions does the note leave open?</p>

<h2 id="the-critic-walks-beside">The Critic walks beside</h2>

<p>The newest agent is the one I’m most excited about. The <a href="https://www.val.town/x/joshbeckman/criticCron">Critic</a> runs on a cron schedule every few hours. It monitors my site’s <a href="/subscribe">RSS feed</a>, and when I publish something new (a blog post, a note, an exercise log, anything) it reads the post, follows any linked sources (both internal garden links and external pages), searches the garden for related prior writing, and emails me a critique.</p>

<p>It’s a simplistic agent. It’s not constantly expanding its context scope, so it doesn’t <a href="https://www.joshbeckman.org/notes/916587771">fall prey to compounding error rates</a>. This simplicity is a deliberate design choice for this tool. LLMs perform well in this sweet spot where they’re given a tight context window, allowed to agentically operate in that window, and then state is output/saved for recursive processing.</p>

<blockquote class="markdown-alert markdown-alert-note">
  <p><strong class="markdown-alert-title"><svg width="16" height="16" class="octicon octicon-info mr-2" aria-hidden="true" viewBox="0 0 16 16" version="1.1"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</strong></p>

  <p>The critic doesn’t retain memory of past critiques or track whether I acted on its feedback. That compounding memory might be a future improvement, to make it a <a href="https://www.joshbeckman.org/notes/922122090">full entity</a>.</p>
</blockquote>
<p>The critique covers argument strength, writing clarity, connections to other posts, how I engaged with sources, and growth edges. It’s constructive, specific, and it cites passages. It walks beside me: it reads what I’ve just written, and later that day or the next morning over coffee I read a critique as if from a colleague. I consider it and hopefully incorporate it into an edit or a follow-on post. I want it to feel collaborative: <a href="https://www.joshbeckman.org/blog/practicing/feedforward-tolerance-feedback-improving-interfaces-for-llm-agents">a feedforward mechanism</a> for my own writing process.</p>

<p><img src="/assets/images/critic-cron-email-screenshot.png" alt="Critic cron email for an exercise post" /></p>

<p>I chose a strong model for this (Anthropic’s Opus). The other agents use Sonnet, which is fast and good enough for search-and-suggest work. But I want a writing mentor to be as sharp as possible. Lower-quality models still sound like parrots, echoing your context window back at you and telling you what you want to hear. I want genuine pushback. The critic runs infrequently enough that the cost is not a concern.</p>

<h2 id="early-results">Early results</h2>

<p>I’ve been using the suggester for a few days now, and every day I’ve updated at least one note with new comments and links. I’m finding myself using the pre-reader on my backlog of reading - things I would have skimmed in the past, but now I can see them through the lens of what I’ve already written. I’m already getting ideas for how the pre-reader could evolve: giving it more agency to recommend whether I should read the full piece or not, pulling quotes into its summaries.</p>

<p>The critic is newest, but the first critiques have been pointed enough that I’m optimistic. I used it repeatedly on this very post and it pushed me to refine wording, expand things I only gestured toward, and found source links to support my words. It also challenged some of my earlier claims, so that I strengthened them or removed them. It has made me engage much more than I would have previously with this writing.</p>

<p><img src="/assets/images/critique-of-critique-post.png" alt="Here's the critic critiquing this post as I wrote it" /></p>

<p>Importantly, I’m <em>not</em> copying and pasting the output from these agents, or letting them edit my writing directly. I want <em>influence</em> and collaboration, not delegation (feedforward context that shapes environment, not writing output for me). Unlike how I’m using agents to write code on my behalf, I want to be writing this prose myself. I need to actually write things, <a href="https://www.joshbeckman.org/notes/724851287">as output, to have them fully change my thinking</a>. Code was always an intermediary between the system and behavior I was designing and the computer that would enact it, so with coding agents I operate at a system design level, now rarely editing lines of code directly. With prose, I communicate directly with the minds of the audience, and the writing is a <a href="https://www.joshbeckman.org/notes/429253538">tool for my own mind</a>; having an agent write it on my behalf cheats myself (see the SloppyPasta source at the top).</p>

<h2 id="a-modern-website">A modern website</h2>

<p>Any one of these agents is a neat trick, but together, they’re something more: a living feedback loop built into my reading and writing on this site. The pre-reader prepares me before I read. The suggester fills in gaps when I have spare attention. The critic holds me accountable after I publish. A modern blogging website in this age of LLM agents should have AI feedback loops like these built in. I think this is the prose writing equivalent of my claude code software writing feedback loops.</p>

<p>This has been an evolution based on experience, not theory. The three agents replace the <a href="https://www.joshbeckman.org/blog/upgraded-insight-widget-with-mcp-server">Insight widget</a> I built last year (and <a href="https://www.joshbeckman.org/blog/using-an-llmand-rag-to-wring-insights-from-my-posts">the prior RAG-based insights before that</a>), which tried to do everything in one pass.</p>

<p>Using the basic RAG, I eventually found it uncreative. Using the agent, I found two distinct needs: suggesting connections and critiquing writing. Splitting them into dedicated agents with different triggers and interaction patterns serves each need better. It’s also now trivially easy to manage many vals for many distinct purposes when LLM agents are doing the coding, testing, and development for me.</p>

<p>I don’t want to just collect and hoard links. I want to integrate, connect, and digest. I can do that in conversation with colleagues, but I can’t always find a sparring partner at the moment I need one. These agents are tireless, and they know my entire body of work. They fill the gaps between conversations, keeping the garden tended when I’m not paying attention.</p>

<h2 id="how-the-critic-works">How the Critic works</h2>

<p>The whole thing is built on <a href="https://val.town">Val Town</a>, which has become my go-to for this kind of lightweight agent infrastructure. It encourages small compositional units, exposes a nice CLI for managing things, supports email/HTTP/cron as agent triggers, has minimal-but-complete storage, etc. The stack:</p>

<ul>
  <li><strong>RSS feed parsing</strong> to detect new posts</li>
  <li><strong>Blob storage</strong> to track the last processed post and avoid duplicates</li>
  <li>An <strong>agentic tool-use loop</strong> where the LLM can search my garden via <a href="/blog/i-built-an-mcp-server-for-my-site">the MCP server I built for this site</a>, read specific posts, and fetch external pages via <a href="https://jina.ai/">jina.ai</a></li>
  <li><strong>HTML email</strong> with the critique, sent via Val Town’s built-in email service</li>
  <li><strong>HTTP routes</strong> for ad-hoc testing — a <code class="language-plaintext highlighter-rouge">/preview?url=</code> endpoint that renders the critique as a web page</li>
</ul>

<p>I built each interface by orchestrating <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a> agents. I <a href="https://github.com/joshbeckman/dotfiles/tree/master/.claude/skills/val-town-dev">designed a skill</a> for them to know how to create and manage vals on Val Town. They tested and iterated using the <a href="https://github.com/anthropics/anthropic-cookbook/tree/main/anthropic-mcp-client/chrome-devtools-mcp">Chrome DevTools MCP server</a> to render the pages in real time.</p>

<p>All of the source code is public. The <a href="https://www.val.town/x/joshbeckman/criticCron">Critic</a>, <a href="https://www.val.town/x/joshbeckman/preRead">Pre-Reader</a>, and <a href="https://www.val.town/x/joshbeckman/suggestComments">Suggester</a> vals are on Val Town. The <a href="https://github.com/joshbeckman/dotfiles/tree/master/.claude/skills/val-town-dev">Val Town dev skill</a> for Claude Code is on GitHub. If you have a site with an RSS feed and a search index, you could wire up something similar.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="practicing" /><category term="ai" /><category term="writing" /><category term="tools" /><category term="open-source" /><category term="personal-blog" /><summary type="html"><![CDATA[I built three AI agents that tend my knowledge garden in different ways: one walks in front, one walks behind, and one walks beside me.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/critic-cron-email-screenshot.png" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/critic-cron-email-screenshot.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cure</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1256054456-cure" rel="alternate" type="text/html" title="Cure" /><published>2026-03-26T00:00:00+00:00</published><updated>2026-03-26T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1256054456-cure</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1256054456-cure"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/2/8/1/9/5/28195-cure-0-600-0-900-crop.jpg?v=74a48c4849" /></p>
<p>I was mostly bored by this film. The antagonist is supposed to be a fearsome void, but I was mostly annoyed with his questions, like the detective.</p>
<p>After about halfway through and I realized where things were going, my mind started to wander to other, better, examples of psychological control. <em>Hannibal</em> (the TV series) immediately comes to mind; he's able to believably and chillingly control others because he has sunken deep into their wounds and uses an individual's pressure points against them. Or <em>Pusher</em> in <em>The X-Files</em> who has to physically strain to control the minds of others. Or, hell, even <em>The Purple Man</em> from <em>Jessica Jones</em> was more convincing. The guy from <em>Cure</em> just couldn't convince me of his danger; maybe because he just kept asking the same question.</p>
<p>I understand that the banality of the murders and the helpless repetition are the point - what's <em>supposed</em> to scare me - but things felt disconnected and no tension built within me. In a horror movie, I want the threat to feel earned, mechanistically believable even within its own rules.</p>
<p>To try to stand up for the film:</p>
<p>I think it’s trying to say that people ignore the banality of their identities and their failures, and he reminded them of that and so could remove their inhibitions and unlock cruelty within them.</p>
<p>I think it’s saying that when you don’t think critically about yourself and you don’t know your own motivations and accept them for good or bad you are at the edge of being under someone else’s control.</p>
<p>But I'm left with a nagging feeling: is the boredom I felt actually the film failing, or is it the film succeeding at something I don't value?</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[I was mostly bored by this film. The antagonist is supposed to be a fearsome void, but I was mostly annoyed with his questions, like the detective. After about halfway through and I realized where things were going, my mind started to wander to other, better, examples of psychological control. Hannibal (the TV series) immediately comes to mind; he's able to believably and chillingly control others because he has sunken deep into their wounds and uses an individual's pressure points against them. Or Pusher in The X-Files who has to physically strain to control the minds of others. Or, hell, even The Purple Man from Jessica Jones was more convincing. The guy from Cure just couldn't convince me of his danger; maybe because he just kept asking the same question. I understand that the banality of the murders and the helpless repetition are the point - what's supposed to scare me - but things felt disconnected and no tension built within me. In a horror movie, I want the threat to feel earned, mechanistically believable even within its own rules. To try to stand up for the film: I think it’s trying to say that people ignore the banality of their identities and their failures, and he reminded them of that and so could remove their inhibitions and unlock cruelty within them. I think it’s saying that when you don’t think critically about yourself and you don’t know your own motivations and accept them for good or bad you are at the edge of being under someone else’s control. But I'm left with a nagging feeling: is the boredom I felt actually the film failing, or is it the film succeeding at something I don't value?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/2/8/1/9/5/28195-cure-0-600-0-900-crop.jpg?v=74a48c4849" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/2/8/1/9/5/28195-cure-0-600-0-900-crop.jpg?v=74a48c4849" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Sunset Boulevard</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1253033551-sunset-boulevard" rel="alternate" type="text/html" title="Sunset Boulevard" /><published>2026-03-22T00:00:00+00:00</published><updated>2026-03-22T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1253033551-sunset-boulevard</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1253033551-sunset-boulevard"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/5/1/5/2/2/51522-sunset-boulevard-0-600-0-900-crop.jpg?v=75e49b76e3" /></p>
<p>It's amazing to watch a silent-film star - who shaped her life and manner into eccentricities, conveying everything in pure visial drama - operate in the real world. The sweeping gestures! The grandiosity! Eyes wide! </p>
<p>The fact that Gloria Swanson actually plays herself elevates this to near perfection.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[It's amazing to watch a silent-film star - who shaped her life and manner into eccentricities, conveying everything in pure visial drama - operate in the real world. The sweeping gestures! The grandiosity! Eyes wide! The fact that Gloria Swanson actually plays herself elevates this to near perfection.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/5/1/5/2/2/51522-sunset-boulevard-0-600-0-900-crop.jpg?v=75e49b76e3" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/5/1/5/2/2/51522-sunset-boulevard-0-600-0-900-crop.jpg?v=75e49b76e3" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Civil War</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1253025875-civil-war-2024" rel="alternate" type="text/html" title="Civil War" /><published>2026-03-19T00:00:00+00:00</published><updated>2026-03-19T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1253025875-civil-war-2024</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1253025875-civil-war-2024"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/8/3/4/6/5/6/834656-civil-war-0-600-0-900-crop.jpg?v=aefec33dbe" /></p>
<p>Jesse Plemons is consistently terrifying because he has perfected the portrayal of our time's most fearsome person: a man who only cares for himself. He can convey this in a single stare and word, and he wins the scene.</p>
<p>The final set-piece of this film was worth all the rest and honestly could maybe stand on its own. I appreciated the avoidance of political detail as beside-the-point; the goal was to show how it would feel to navigate a divided American landscape.</p>
<p>From my early career in photojournalism, I think I'm a bit pre-disposed to be harsh on this portrayal. I know I shouldn't expect them to get <em>everything</em> right, but I was distracted at times by camera goofs (especially in the climactic scene). I also think this could have dug further into the moral complexity of embedded journalism. The film itself felt a bit voeyeristic on journalism and the twice-removed nature felt shallow at times.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><category term="photography" /><category term="united-states" /><category term="journalism" /><summary type="html"><![CDATA[Jesse Plemons is consistently terrifying because he has perfected the portrayal of our time's most fearsome person: a man who only cares for himself. He can convey this in a single stare and word, and he wins the scene. The final set-piece of this film was worth all the rest and honestly could maybe stand on its own. I appreciated the avoidance of political detail as beside-the-point; the goal was to show how it would feel to navigate a divided American landscape. From my early career in photojournalism, I think I'm a bit pre-disposed to be harsh on this portrayal. I know I shouldn't expect them to get everything right, but I was distracted at times by camera goofs (especially in the climactic scene). I also think this could have dug further into the moral complexity of embedded journalism. The film itself felt a bit voeyeristic on journalism and the twice-removed nature felt shallow at times.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/8/3/4/6/5/6/834656-civil-war-0-600-0-900-crop.jpg?v=aefec33dbe" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/8/3/4/6/5/6/834656-civil-war-0-600-0-900-crop.jpg?v=aefec33dbe" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I know you’re hurting, everyone is hurting, everyone is trying, you have got to try by Joshua Idehen</title><link href="https://www.joshbeckman.org/blog/listening/i-know-youre-hurting-everyone-is-hurting-everyone-is-trying-you-have-got-to-try-by-joshua-idehen" rel="alternate" type="text/html" title="I know you’re hurting, everyone is hurting, everyone is trying, you have got to try by Joshua Idehen" /><published>2026-03-18T12:50:20+00:00</published><updated>2026-03-18T12:50:20+00:00</updated><id>https://www.joshbeckman.org/blog/listening/i-know-youre-hurting-everyone-is-hurting-everyone-is-trying-you-have-got-to-try-by-joshua-idehen</id><content type="html" xml:base="https://www.joshbeckman.org/blog/listening/i-know-youre-hurting-everyone-is-hurting-everyone-is-trying-you-have-got-to-try-by-joshua-idehen"><![CDATA[<p><img src="/assets/images/album_art/joshuaidehen-iknowyourehurtingeveryoneishurtingeveryoneistryingyouhavegottotry.jpg" alt="I know you're hurting, everyone is hurting, everyone is trying, you have got to try by Joshua Idehen" /></p>

<p>I found this album <a href="https://www.fluxblog.org/2026/03/idehen-gorillaz/">through Fluxblog’s mention of it</a> and the initial song is definitely the hook that pulls you through the rest of the songs: finding individual and communal support through dance. This album is a reminder and anchor that dance music can be a therapy.</p>

<p>Personally, though, I’ll mostly come back to this album for the <em>Mum Does The Washing</em> political song and the body-moving bop that is <em>Don’t Let It Get You Down</em>.</p>

<iframe width="100%" height="350" src="https://www.youtube-nocookie.com/embed/PS9Bc_GQBEs?si=_tOsoQo4o5G_0IM1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="listening" /><category term="music" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/album_art/joshuaidehen-iknowyourehurtingeveryoneishurtingeveryoneistryingyouhavegottotry.jpg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/album_art/joshuaidehen-iknowyourehurtingeveryoneishurtingeveryoneistryingyouhavegottotry.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dogtooth</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1240838282-dogtooth" rel="alternate" type="text/html" title="Dogtooth" /><published>2026-03-15T00:00:00+00:00</published><updated>2026-03-15T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1240838282-dogtooth</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1240838282-dogtooth"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/sm/upload/j6/sh/2m/01/xUnbL2Uoh4zoc1hJvIV6MDDJpka-0-600-0-900-crop.jpg?v=f876d41d35" /></p>
<p>I watched this while feeding my infant daughter and reflected on how many lies I’ve already told her.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[I watched this while feeding my infant daughter and reflected on how many lies I’ve already told her.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/sm/upload/j6/sh/2m/01/xUnbL2Uoh4zoc1hJvIV6MDDJpka-0-600-0-900-crop.jpg?v=f876d41d35" /><media:content medium="image" url="https://a.ltrbxd.com/resized/sm/upload/j6/sh/2m/01/xUnbL2Uoh4zoc1hJvIV6MDDJpka-0-600-0-900-crop.jpg?v=f876d41d35" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Madame Web</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1240130630-madame-web" rel="alternate" type="text/html" title="Madame Web" /><published>2026-03-14T00:00:00+00:00</published><updated>2026-03-14T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1240130630-madame-web</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1240130630-madame-web"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/5/6/0/6/9/7/560697-madame-web-0-600-0-900-crop.jpg?v=305a4aead1" /></p>
<p>Maybe this movie is intentionally unintelligible. Maybe the cliches are perfectly chosen and not ineptly plopped on a page. Maybe the bad guy’s lack of motive was the really fearful part. Maybe the girls’ side plot was really relevant as contrast to no plot in the main plot. Maybe the disorientation of sloppily shot sequences and non-sequitur statements were meant to convey the doubt and feeling of Dakota’s character. </p>
<p>No, she went to the eye doctor because she could see the future. This was stupid all the way through.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="letterboxd" /><summary type="html"><![CDATA[Maybe this movie is intentionally unintelligible. Maybe the cliches are perfectly chosen and not ineptly plopped on a page. Maybe the bad guy’s lack of motive was the really fearful part. Maybe the girls’ side plot was really relevant as contrast to no plot in the main plot. Maybe the disorientation of sloppily shot sequences and non-sequitur statements were meant to convey the doubt and feeling of Dakota’s character.  No, she went to the eye doctor because she could see the future. This was stupid all the way through.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/5/6/0/6/9/7/560697-madame-web-0-600-0-900-crop.jpg?v=305a4aead1" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/5/6/0/6/9/7/560697-madame-web-0-600-0-900-crop.jpg?v=305a4aead1" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">README, Don’t AGENTS.md Me</title><link href="https://www.joshbeckman.org/blog/practicing/readme-dont-agentsmd-me" rel="alternate" type="text/html" title="README, Don’t AGENTS.md Me" /><published>2026-03-13T13:04:19+00:00</published><updated>2026-03-13T13:04:19+00:00</updated><id>https://www.joshbeckman.org/blog/practicing/readme-dont-agentsmd-me</id><content type="html" xml:base="https://www.joshbeckman.org/blog/practicing/readme-dont-agentsmd-me"><![CDATA[<p>This is the place where I rant that <a href="https://agents.md/">the <code class="language-plaintext highlighter-rouge">AGENTS.md</code> pattern</a> is a distraction and slows down your software development.</p>

<p>The software engineering industry has had a standard of adding a <code class="language-plaintext highlighter-rouge">README.md</code> file to the root of projects and also the root of any subdirectory of those projects where deemed necessary. These files are easily discoverable by operators of the codebase, and their contents - while not standard - have been embraced as a place to put instructions for patterns and how to operate in the codebase.</p>

<p>with the rise of coding agents over the last couple years, people found that they needed to give them special instructions because they were error prone in certain ways, and we hadn’t built out the harnesses and capabilities for them to replicate how human operators work in a code base. That is no longer true today.</p>

<p>We We have incredibly capable models and the harnesses and tooling that we give them (like shell access and MCPs for browser control, among hundreds of other tools), being that they can do <em>everything</em> that a human operator can do to operate on the code. Everything I would say to a human operator in the codebase, I would say to an LLM agent working in that same codebase.</p>

<p>So, I just revamped my project’s READMEs and symlinked them to the <code class="language-plaintext highlighter-rouge">AGENTS.md</code> location. It’s not useful to separate the instructions for humans from LLM agents. In fact, when we separate them, we <em>increase</em> the likelihood that they will operate in different ways and do things that the other does not expect or intend. This actively slows down development on both sides.</p>

<p>Solidifying a single place - the old <code class="language-plaintext highlighter-rouge">README.md</code> standard, that is present in all modern software - is the path forward. I’m symlinking my READMEs to conform to this standard for now, because it’s free and doesn’t clutter anything for me, but I hope it eventually falls away.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="practicing" /><category term="ai" /><category term="tools" /><category term="software-engineering" /><summary type="html"><![CDATA[This is the place where I rant that the AGENTS.md pattern is a distraction and slows down your software development.]]></summary></entry><entry><title type="html">LCD Soundsystem at Aragon in 2026</title><link href="https://www.joshbeckman.org/blog/attending/lcd-soundsystem-at-aragon-in-2026" rel="alternate" type="text/html" title="LCD Soundsystem at Aragon in 2026" /><published>2026-03-07T20:00:25+00:00</published><updated>2026-03-07T20:00:25+00:00</updated><id>https://www.joshbeckman.org/blog/attending/lcd-soundsystem-at-aragon-in-2026</id><content type="html" xml:base="https://www.joshbeckman.org/blog/attending/lcd-soundsystem-at-aragon-in-2026"><![CDATA[<p>I’ve seen LCDS many times before (<a href="https://www.joshbeckman.org/blog/attending/lcd-soundsystem-at-aragon-ballroom">previously</a>), but not as many as my friend Ned. It’s a wonderful experience going to see this band with Ned. He knows how they structure their shows for different countries, he knows what the set will be, he dances more than anyone else in the crowd, and he makes me have the most fun.</p>

<p><img src="/assets/images/29162046-a8d4-4b1a-9cfa-4d24c38a3e88.jpeg" alt="A disco ball makes every concert better" /></p>

<p>I think <em>Dance Yrself Clean</em> might be the best drop in all of concert music. The bass, the falsetto synth, the minimal screams. Every time it hits the whole crowd goes crazy.</p>

<iframe allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" frameborder="0" style="width:100%;overflow:hidden;border-radius:0.8em;border:1px solid var(--c-bg-alt);" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/dance-yrself-clean/693387639?i=693387645"></iframe>

<p><img src="/assets/images/b8238d45-4d9f-4449-b45a-05376a93d55d.gif" alt="The disco ball spins the crowd" /></p>

<p><a href="https://automatic.band/">Automatic</a> was their opener and I went halfway through their set before realizing that I had listened to their album <em><a href="https://ffm.to/isitnow">Is It Now?</a></em> a <strong>lot</strong> last year. What a lovely surprise.</p>

<iframe allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" frameborder="0" style="width:100%;overflow:hidden;border-radius:0.8em;border:1px solid var(--c-bg-alt);" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/mq9/1819100783?i=1819100785"></iframe>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="attending" /><category term="concerts" /><category term="aragon-venue" /><summary type="html"><![CDATA[I’ve seen LCDS many times before (previously), but not as many as my friend Ned. It’s a wonderful experience going to see this band with Ned. He knows how they structure their shows for different countries, he knows what the set will be, he dances more than anyone else in the crowd, and he makes me have the most fun.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/29162046-a8d4-4b1a-9cfa-4d24c38a3e88.jpeg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/29162046-a8d4-4b1a-9cfa-4d24c38a3e88.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Trust But Verify</title><link href="https://www.joshbeckman.org/blog/practicing/trust-but-verify" rel="alternate" type="text/html" title="Trust But Verify" /><published>2026-03-06T14:19:08+00:00</published><updated>2026-03-06T14:19:08+00:00</updated><id>https://www.joshbeckman.org/blog/practicing/trust-but-verify</id><content type="html" xml:base="https://www.joshbeckman.org/blog/practicing/trust-but-verify"><![CDATA[<p>I’ve been a huge fan of <a href="https://webcomicname.com/">webcomic name</a> for years. Every comic ends with “oh no”. Perfect.</p>

<p>This one popped into my head after seeing some coworkers blindly trusting an LLM/AI agent’s output, which was entirely incorrect.</p>

<p><img width="906" height="308" alt="Trust But Verify" src="/assets/images/955f9c15-077b-4b71-b2ca-0b77b0287e07.png" /></p>

<p>This is my little reminder to always check the output of today’s AI agents. Know the checks applied to their work before it reaches us. Understand the provenance of their output. Their incentives are not your own.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="practicing" /><category term="software-engineering" /><category term="llm" /><category term="ai" /><category term="trust" /><summary type="html"><![CDATA[I’ve been a huge fan of webcomic name for years. Every comic ends with “oh no”. Perfect.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/955f9c15-077b-4b71-b2ca-0b77b0287e07.png" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/955f9c15-077b-4b71-b2ca-0b77b0287e07.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ComEd Hourly Pricing Calendar</title><link href="https://www.joshbeckman.org/blog/practicing/comed-hourly-pricing-as-calendar-events" rel="alternate" type="text/html" title="ComEd Hourly Pricing Calendar" /><published>2026-03-01T01:15:00+00:00</published><updated>2026-03-01T01:15:00+00:00</updated><id>https://www.joshbeckman.org/blog/practicing/comed-hourly-pricing-as-calendar-events</id><content type="html" xml:base="https://www.joshbeckman.org/blog/practicing/comed-hourly-pricing-as-calendar-events"><![CDATA[<p>ComEd electricity prices change every hour — sometimes swinging several cents between midnight and mid-afternoon. After <a href="/notes/2026-03-01-enrolling-in-hourly-pricing-for-comed-electricity">enrolling in hourly pricing</a>, I realized I didn’t want to check a dashboard to know when power is cheap. I wanted to see it on my calendar, right next to the rest of my day.</p>

<p>Same impulse that led me to build <a href="/blog/ical-feeds-for-a-jekyll-site">iCal feeds for my entire blog history</a>. A calendar is the tool I already use for planning around time. Price data <em>is</em> time data — it just happens to come from a utility instead of a CMS. If I can see that electricity drops to near-zero at 2am and spikes at 6pm, I can plan around it the same way I plan around meetings.</p>

<p>So I built a small <a href="https://www.val.town/x/joshbeckman/comed-hourly-pricing-calendar/code/README.md">Val.town server</a> that generates an iCal feed of price changes. It pulls the last 24 hours of 5-minute prices from ComEd’s <a href="https://hourlypricing.comed.com/hp-api/">public API</a>, averages them into hourly buckets, and grabs the next day’s prices from their (undocumented) day-ahead endpoint. Then it compares consecutive hours and emits a calendar event whenever the price shifts by more than a threshold:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>↑ 3.7c/kWh (+0.9c)
↓ 2.1c/kWh (-0.3c)
</code></pre></div></div>

<p>Stable hours produce no event — gaps in the calendar mean the price isn’t moving. The sensitivity, lookback window, and lookahead are all configurable via query parameters, so I (or you!) can tune it to only surface the swings I care about.</p>

<blockquote class="markdown-alert markdown-alert-note">
  <p><strong class="markdown-alert-title"><svg width="16" height="16" class="octicon octicon-info mr-2" aria-hidden="true" viewBox="0 0 16 16" version="1.1"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</strong></p>

  <p>Day-ahead prices aren’t always available — ComEd publishes them on their own schedule, typically in the evening for the following day — so the feed only includes forward-looking events when the data is there.</p>
</blockquote>
<p><img width="582" height="355" alt="The pricing changes display in my calendar app" src="/assets/images/7da27360-4fa4-4eb5-8fbb-9aa80f34e9b7.png" /></p>

<p>The next step is pairing this with batteries to buffer my high-draw appliances — grow lights, the computer desk — into cheap hours automatically. For now, just seeing the price rhythm on my calendar alongside everything else is enough to shift my habits. <a href="/blog/everywhere-a-calendar">Everywhere a calendar</a>.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="practicing" /><category term="tools" /><category term="consumption" /><category term="time" /><category term="open-source" /><summary type="html"><![CDATA[I built an iCal feed of ComEd electricity price changes so I can plan around cheap hours from my calendar.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/7da27360-4fa4-4eb5-8fbb-9aa80f34e9b7.png" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/7da27360-4fa4-4eb5-8fbb-9aa80f34e9b7.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Music Frozen Dancing 2026</title><link href="https://www.joshbeckman.org/blog/attending/music-frozen-dancing-2026" rel="alternate" type="text/html" title="Music Frozen Dancing 2026" /><published>2026-02-21T19:42:58+00:00</published><updated>2026-02-21T19:42:58+00:00</updated><id>https://www.joshbeckman.org/blog/attending/music-frozen-dancing-2026</id><content type="html" xml:base="https://www.joshbeckman.org/blog/attending/music-frozen-dancing-2026"><![CDATA[<p>Of course we went to <a href="https://www.musicfrozendancing.com/">Music Frozen Dancing</a> again this year. And this time we got a spot near the front so we could get our faces visible in the annual photo. A fun bit from <a href="https://www.joshbeckman.org/blog/attending/music-frozen-dancing-2025">last year</a>: I’m kinda featured in the homepage video loop (I’m the guy in the bright yellow hat fighting through the crowd to hug his friends).</p>

<p><img src="/assets/images/c2781a7a-51f1-4832-a643-f5d9e29a3356.jpeg" alt="Blustery snow was falling for the last two acts of the festival" /></p>

<h2 id="body-shop">Body Shop</h2>

<blockquote>
  <p>This is a chord. This is another. This is a third. Now form a band.</p>
  <ul>
    <li>Tony Moon</li>
  </ul>
</blockquote>

<p>I always think of this concept at the first band of this festival. Minimum punk.</p>

<iframe allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" frameborder="0" style="width:100%;overflow:hidden;border-radius:0.8em;border:1px solid var(--c-bg-alt);" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/sex-body/1865581534?i=1865581535"></iframe>

<h2 id="snuffed">Snuffed</h2>

<p>I don’t know how the screamo singers can keep going for a hour.</p>

<iframe allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" frameborder="0" style="width:100%;overflow:hidden;border-radius:0.8em;border:1px solid var(--c-bg-alt);" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/grievances/1621758366?i=1621758370"></iframe>

<h2 id="good-flying-birds">Good Flying Birds</h2>

<p>This one slid off me after they were done. Standard jangly rock.</p>

<iframe allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" frameborder="0" style="width:100%;overflow:hidden;border-radius:0.8em;border:1px solid var(--c-bg-alt);" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/down-on-me/1817694381?i=1817694383"></iframe>

<h2 id="lip-critic">Lip Critic</h2>

<p>I had seen them open for <a href="https://www.joshbeckman.org/blog/attending/fat-dog-at-the-empty-bottle">Fat Dog at The Empty Bottle</a> a year ago and they were even better this time around. Just good heavy synth hard rock with double-drummers.</p>

<iframe allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" frameborder="0" style="width:100%;overflow:hidden;border-radius:0.8em;border:1px solid var(--c-bg-alt);" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/milky-max/1783172639?i=1783172986"></iframe>

<h2 id="los-thuthanaka">Los Thuthanaka</h2>

<p>The final act was interesting for a bit, but there’s only so long that I can enjoy drone-noise-dj-loops while standing in the snowfall. Also they ended early, which was weird.</p>

<iframe width="100%" height="350" src="https://www.youtube-nocookie.com/embed/aVuEQxqL33Y?si=_tOsoQo4o5G_0IM1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="attending" /><category term="music" /><category term="chicago" /><category term="empty-bottle-venue" /><summary type="html"><![CDATA[Of course we went to Music Frozen Dancing again this year. And this time we got a spot near the front so we could get our faces visible in the annual photo. A fun bit from last year: I’m kinda featured in the homepage video loop (I’m the guy in the bright yellow hat fighting through the crowd to hug his friends).]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/c2781a7a-51f1-4832-a643-f5d9e29a3356.jpeg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/c2781a7a-51f1-4832-a643-f5d9e29a3356.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Possessor</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1212868052-possessor" rel="alternate" type="text/html" title="Possessor" /><published>2026-02-20T00:00:00+00:00</published><updated>2026-02-20T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1212868052-possessor</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1212868052-possessor"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/3/6/8/5/4/0/368540-possessor-0-600-0-900-crop.jpg?v=c949ea1fc0" /></p>
<p>I can see this as the stepping stone to <em>Infinity Pool</em> that this would become. </p>
<p>Like <em>Infinity Pool</em>, this perfectly balances world-building reveals through character action and scene make-up. Also similarly, it brings an organic, gooey, gory interface to computers and digital technology. I love those parts.</p>
<p>But this one relies a bit too much on vibes for me. It doesn't have the acting performance punch of <em>Infinity Pool</em>, and feels more like a great short story that was expanded into a full-length film.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="movies" /><category term="art" /><category term="writing" /><category term="entertainment" /><category term="letterboxd" /><summary type="html"><![CDATA[I can see this as the stepping stone to Infinity Pool that this would become. Like Infinity Pool, this perfectly balances world-building reveals through character action and scene make-up. Also similarly, it brings an organic, gooey, gory interface to computers and digital technology. I love those parts. But this one relies a bit too much on vibes for me. It doesn't have the acting performance punch of Infinity Pool, and feels more like a great short story that was expanded into a full-length film.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/3/6/8/5/4/0/368540-possessor-0-600-0-900-crop.jpg?v=c949ea1fc0" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/3/6/8/5/4/0/368540-possessor-0-600-0-900-crop.jpg?v=c949ea1fc0" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Divvy 2025 in Review</title><link href="https://www.joshbeckman.org/blog/divvy-2025-in-review" rel="alternate" type="text/html" title="Divvy 2025 in Review" /><published>2026-02-18T15:01:16+00:00</published><updated>2026-02-18T15:01:16+00:00</updated><id>https://www.joshbeckman.org/blog/divvy-2025-in-review</id><content type="html" xml:base="https://www.joshbeckman.org/blog/divvy-2025-in-review"><![CDATA[<p>Last week I opened the <a href="https://divvybikes.com/">Divvy</a> (bikeshare system for Chicago) app and they prompted me with a 2025 year-in-review. Strange! A bit late? But I appreciate it all the same.</p>

<p><img src="/assets/images/79266feb-73a0-47d8-94fb-d6d8f3718e04.jpeg" alt="My 2025 summary" /></p>

<p>Summary:</p>
<ul>
  <li>144 rides
    <ul>
      <li>This puts me only in the top 20%</li>
      <li>August was the highest with 25 rides (music festival season!)</li>
      <li>~285 miles and 30 hours</li>
    </ul>
  </li>
  <li>62 stations visited
    <ul>
      <li>Apparently I’m in the top 6% of prolific riders</li>
    </ul>
  </li>
  <li>4 days was my longest streak</li>
  <li>56% of rides were on an e-bike
    <ul>
      <li>I took a <em>lot</em> more e-bikes this year because I rode a bunch to doctor appointments where I didn’t want to be sweaty.</li>
    </ul>
  </li>
</ul>

<p><img src="/assets/images/60897b2c-1abc-4cc0-a979-27740a19bad1.jpeg" alt="Rides summary" /></p>

<p><img src="/assets/images/d6e10238-46b8-45fd-8b6a-6d1c3165540f.jpeg" alt="Stations summary" /></p>

<p><img src="/assets/images/fbce13ee-fbb6-48cb-9589-0ef7a2686241.jpeg" alt="E-bike summary" /></p>

<p><img src="/assets/images/868c73d2-5555-4795-bb29-67a7f6fcf6ad.jpeg" alt="Cost-savings summary" /></p>

<p>I’ve been a Divvy annual member for 8 years now and it’s a no-brainer for getting around the city. My friend Ned always says:</p>

<blockquote>
  <p>If you need to guarantee being on time, always take a bike. The train can be delayed, the traffic can slow down cars, but bikes can always get through, at their normal speed.</p>
</blockquote>

<p><img src="/assets/images/00e26fd0-0ec1-4280-b3c0-bab7171c508e.jpeg" alt="Bike Angel summary" /></p>

<p><a href="https://divvybikes.com/bike-angels">Divvy has a “Bike Angel” program</a> that rewards you for bringing bikes to empty stations. Ned treats this like a game and has paid for months of his membership this way. I don’t usually aim to do so, but occasionally it works out that way. It’s paid for a month or two of my membership now.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="year-in-review" /><category term="chicago" /><category term="biking" /><summary type="html"><![CDATA[Last week I opened the Divvy (bikeshare system for Chicago) app and they prompted me with a 2025 year-in-review. Strange! A bit late? But I appreciate it all the same.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.joshbeckman.org/assets/images/79266feb-73a0-47d8-94fb-d6d8f3718e04.jpeg" /><media:content medium="image" url="https://www.joshbeckman.org/assets/images/79266feb-73a0-47d8-94fb-d6d8f3718e04.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Le Samouraï</title><link href="https://www.joshbeckman.org/blog/watching/letterboxd-review-1210579568-le-samourai" rel="alternate" type="text/html" title="Le Samouraï" /><published>2026-02-18T00:00:00+00:00</published><updated>2026-02-18T00:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/watching/letterboxd-review-1210579568-le-samourai</id><content type="html" xml:base="https://www.joshbeckman.org/blog/watching/letterboxd-review-1210579568-le-samourai"><![CDATA[<p><img src="https://a.ltrbxd.com/resized/film-poster/4/8/8/1/7/48817-le-samourai-0-600-0-900-crop.jpg?v=11267d74cc" /></p>
<p><em>This review may contain spoilers.</em></p>
<p>If you spend your days planning the perfect way to kill others, of course you want to control how you die. </p>
<p>This film removes the sexy glamor of contract killing and casts it in drab moody glamor with a perfect ending.</p>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="watching" /><category term="movies" /><category term="entertainment" /><category term="art" /><category term="letterboxd" /><summary type="html"><![CDATA[This review may contain spoilers. If you spend your days planning the perfect way to kill others, of course you want to control how you die.  This film removes the sexy glamor of contract killing and casts it in drab moody glamor with a perfect ending.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://a.ltrbxd.com/resized/film-poster/4/8/8/1/7/48817-le-samourai-0-600-0-900-crop.jpg?v=11267d74cc" /><media:content medium="image" url="https://a.ltrbxd.com/resized/film-poster/4/8/8/1/7/48817-le-samourai-0-600-0-900-crop.jpg?v=11267d74cc" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My Entire Blog History as Calendar Events</title><link href="https://www.joshbeckman.org/blog/ical-feeds-for-a-jekyll-site" rel="alternate" type="text/html" title="My Entire Blog History as Calendar Events" /><published>2026-02-16T17:00:00+00:00</published><updated>2026-02-16T17:00:00+00:00</updated><id>https://www.joshbeckman.org/blog/ical-feeds-for-a-jekyll-site</id><content type="html" xml:base="https://www.joshbeckman.org/blog/ical-feeds-for-a-jekyll-site"><![CDATA[<p>I have over 3,600 posts on this site — blog entries, exercise logs, movie reviews, concert notes — spanning years. I wanted to see all of them on my calendar. Not in a feed reader, where they scroll past and vanish, but on a calendar, where I can see that I ran a half marathon the same week I stopped blogging — or that a burst of note-taking coincided with a conference trip.</p>

<p>RSS couldn’t do it. I’d been using <a href="https://www.rsstocal.com/">RSS to Cal</a> to bridge my feed into my calendar, but RSS only carries the last 25 items. And since the conversion was generic, I couldn’t include richer calendar data like GPS coordinates for my exercise posts or feature images.</p>

<p>So I built a <a href="https://github.com/joshbeckman/notes/blob/master/_plugins/ical_feed.rb">Jekyll plugin</a> that generates <a href="https://en.wikipedia.org/wiki/ICalendar">iCal</a> (<code class="language-plaintext highlighter-rouge">.ics</code>) feeds for this site — every post, with structured calendar metadata. You can subscribe to them from the <a href="/subscribe#ical-feeds">subscribe page</a>.</p>

<h2 id="why-ical-instead-of-just-rss">Why iCal Instead of Just RSS?</h2>

<p>RSS and iCal serve different reading patterns. RSS is a stream — you see the latest items and move on. A calendar is spatial. You can scroll back through months and years, see clusters of activity, notice gaps. Laid out on a calendar, my posts tell a different story than they do in a feed reader.</p>

<p>Two years ago I wrote about wanting <a href="/blog/everywhere-a-calendar">everywhere a calendar</a> — the idea that most of my data has a time component and I should be able to view it in the tools I already use for visualizing time. I subscribe to moon phase, holiday, and astronomy calendars. I already built a <a href="/heatcal">heatmap calendar</a> for this site. But I still couldn’t see my actual posts as calendar events.</p>

<p>I already have a calendar app open all day, on many devices. Adding my posts there means I don’t need a separate app to review what I’ve been up to or match them up to other activities in my life. As always, <a href="/blog/using-open-protocols">your app is not better than an open protocol</a>. When I publish data as iCal, any calendar client can consume it without learning or maintaining a new interface.</p>

<h2 id="how-it-works">How It Works</h2>

<p>The plugin is a single <code class="language-plaintext highlighter-rouge">Jekyll::Generator</code> that runs at build time. It reads the same <code class="language-plaintext highlighter-rouge">feed.categories</code> config that <a href="https://github.com/jekyll/jekyll-feed">jekyll-feed</a> uses, so the iCal feeds mirror the RSS feeds exactly: one <a href="/feed.ics">unified feed</a> and one per category (<a href="/feed/blog.ics">blog</a>, <a href="/feed/notes.ics">notes</a>, <a href="/feed/exercise.ics">exercise</a>, etc.).</p>

<p>Each post becomes a <code class="language-plaintext highlighter-rouge">VEVENT</code> — a one-hour calendar event starting at the post’s exact publish timestamp. The event includes:</p>

<ul>
  <li><strong>Title and description</strong> — the post title as the event summary, with the description frontmatter (or an auto-generated excerpt) as the event description</li>
  <li><strong>URL</strong> — a link back to the post</li>
  <li><strong>Categories</strong> — the post’s tags, which calendar apps can use for filtering</li>
  <li><strong>Image</strong> — a URI reference to the post’s feature image (Apple Calendar renders these)</li>
  <li><strong>Geo coordinates</strong> — for exercise posts that have GPS data (pulled from Strava via frontmatter), so they show up on the calendar’s map view</li>
</ul>

<p>Here’s the core of the VEVENT generation:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">posts</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">post</span><span class="o">|</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"BEGIN:VEVENT"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"UID:</span><span class="si">#{</span><span class="n">ical_escape</span><span class="p">(</span><span class="n">post</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span><span class="si">}</span><span class="s2">@joshbeckman.org"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"DTSTAMP:</span><span class="si">#{</span><span class="n">now</span><span class="si">}</span><span class="s2">"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"DTSTART:</span><span class="si">#{</span><span class="n">post</span><span class="p">.</span><span class="nf">date</span><span class="p">.</span><span class="nf">utc</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s2">"%Y%m%dT%H%M%SZ"</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"DTEND:</span><span class="si">#{</span><span class="p">(</span><span class="n">post</span><span class="p">.</span><span class="nf">date</span> <span class="o">+</span> <span class="mi">3600</span><span class="p">).</span><span class="nf">utc</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s2">"%Y%m%dT%H%M%SZ"</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"SUMMARY:</span><span class="si">#{</span><span class="n">ical_escape</span><span class="p">(</span><span class="n">post</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="o">||</span> <span class="s2">"Untitled"</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>

  <span class="n">desc</span> <span class="o">=</span> <span class="n">build_description</span><span class="p">(</span><span class="n">post</span><span class="p">)</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"DESCRIPTION:</span><span class="si">#{</span><span class="n">ical_escape</span><span class="p">(</span><span class="n">desc</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span> <span class="k">unless</span> <span class="n">desc</span><span class="p">.</span><span class="nf">empty?</span>

  <span class="n">url</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">base_url</span><span class="si">}#{</span><span class="n">post</span><span class="p">.</span><span class="nf">url</span><span class="si">}</span><span class="s2">"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"URL:</span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2">"</span>

  <span class="n">tags</span> <span class="o">=</span> <span class="n">post</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="s2">"tags"</span><span class="p">]</span>
  <span class="k">if</span> <span class="n">tags</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">tags</span><span class="p">.</span><span class="nf">empty?</span>
    <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"CATEGORIES:</span><span class="si">#{</span><span class="n">tags</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">ical_escape</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="si">}</span><span class="s2">.join("</span><span class="p">,</span><span class="s2">")}"</span>
  <span class="k">end</span>

  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"TRANSP:TRANSPARENT"</span>
  <span class="n">lines</span> <span class="o">&lt;&lt;</span> <span class="s2">"END:VEVENT"</span>
<span class="k">end</span>
</code></pre></div></div>

<p>I chose to generate the iCal format directly rather than pulling in a gem. The subset of <a href="https://datatracker.ietf.org/doc/html/rfc5545">RFC 5545</a> I needed is small: a <code class="language-plaintext highlighter-rouge">VCALENDAR</code> wrapper, <code class="language-plaintext highlighter-rouge">VEVENT</code> blocks, and three formatting rules — <code class="language-plaintext highlighter-rouge">\r\n</code> line endings, line wrapping at 75 bytes, and backslash escaping for text values. A library would add a dependency for what ended up being ~130 lines of Ruby.</p>

<h2 id="full-history">Full History</h2>

<p>RSS feeds typically cap at 25 or 50 recent items. That makes sense for RSS — nobody wants to import thousands of items into their feed reader on first subscribe. But the whole point of this calendar was to see my history through the past, so these feeds include every post. The unified feed currently has over 3,600 events.</p>

<p>This pairs well with the <a href="/heatcal">heatmap calendar</a> I built last year. That gives a birds-eye visual of posting density, while the iCal feeds let me drill into specific days and see exactly what I was writing about alongside the rest of my life.</p>

<p>Any by including all my Strava history/posts, I can now replace <a href="/blog/my-exercise-journal">my exercise journal</a> with my calendar. I can see them all alongside the rest of my life.</p>

<h2 id="subscribing">Subscribing</h2>

<p>You can subscribe to any of the feeds from your calendar app:</p>

<ul>
  <li><a href="/feed.ics">All posts</a></li>
  <li><a href="/feed/blog.ics">Blog</a></li>
  <li><a href="/feed/notes.ics">Notes</a></li>
  <li><a href="/feed/exercise.ics">Exercise</a></li>
  <li><a href="/feed/replies.ics">Replies</a></li>
</ul>

<p>In Apple Calendar: File &gt; New Calendar Subscription, then paste the URL. In Google Calendar: Other calendars &gt; From URL.</p>

<blockquote class="markdown-alert markdown-alert-warning">
  <p><strong class="markdown-alert-title"><svg width="16" height="16" class="octicon octicon-alert mr-2" aria-hidden="true" viewBox="0 0 16 16" version="1.1"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</strong></p>

  <p>The unified feed is ~1.4MB — slightly over Google Calendar’s 1MB import limit. The per-category feeds are all well under and are probably more useful for subscribing anyway. Apple Calendar handles the full feed without issue.</p>
</blockquote>
<blockquote class="markdown-alert markdown-alert-note">
  <p><strong class="markdown-alert-title"><svg width="16" height="16" class="octicon octicon-info mr-2" aria-hidden="true" viewBox="0 0 16 16" version="1.1"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</strong></p>

  <p>The feeds include a <code class="language-plaintext highlighter-rouge">REFRESH-INTERVAL</code> hint of one day, though Google tends to refresh on its own schedule regardless.</p>
</blockquote>]]></content><author><name>Josh Beckman</name><email>josh@joshbeckman.org</email><uri>https://www.joshbeckman.org/about</uri></author><category term="blog" /><category term="jekyll" /><category term="personal-blog" /><category term="tools" /><category term="time" /><summary type="html"><![CDATA[I built a Jekyll plugin that generates iCal feeds so I can see my entire posting history in my calendar.]]></summary></entry></feed>