<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Agentic Engineering on Grant Lucas</title>
    <link>https://grantlucas.com/tags/agentic-engineering/</link>
    <description>Recent content in Agentic Engineering on Grant Lucas</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Wed, 29 Jul 2026 19:00:00 -0400</lastBuildDate>
    <atom:link href="https://grantlucas.com/tags/agentic-engineering/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>For Agents, --help Has to Be Enough</title>
      <link>https://grantlucas.com/posts/2026/07/help-has-to-be-enough/</link>
      <pubDate>Wed, 29 Jul 2026 19:00:00 -0400</pubDate>
      <guid>https://grantlucas.com/posts/2026/07/help-has-to-be-enough/</guid>
      <description>&lt;p&gt;&lt;em&gt;Designing an AI-native CLI: usable by humans, and especially usable by coding agents&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/grantlucas/Logger-TXT&#34;&gt;Logger-TXT&lt;/a&gt; started life as a shell&#xA;script over a decade ago. It does one small thing: append a timestamped line to&#xA;a text file so I can remember what I did on that Tuesday two weeks ago. I&#xA;rewrote it in Go recently, and the surface reason was capabilities. Date ranges,&#xA;type and project filters, reverse scanning so a decade-deep log file stays fast.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s not really why I did it, though. The rewrite was an excuse to practise&#xA;designing an AI-native command-line tool: one a coding agent can pick up and&#xA;drive correctly, with no help from me, on the first try.&lt;/p&gt;&#xA;&lt;p&gt;Every loop in the process that follows (the constraint, the testing, the&#xA;rewrites of my own help text) was for the agent. Humans get a better tool out of&#xA;it, and I&amp;rsquo;ll get to why that happens, but that&amp;rsquo;s the dividend rather than the&#xA;motivation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-ai-native-means-here&#34;&gt;What &amp;ldquo;AI-Native&amp;rdquo; Means Here&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#what-ai-native-means-here&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Worth defining, since the phrase gets used for something else entirely.&lt;/p&gt;&#xA;&lt;p&gt;An AI-native CLI isn&amp;rsquo;t a CLI with AI inside it. There&amp;rsquo;s no model in Logger-TXT,&#xA;no natural-language parsing, no &lt;code&gt;logger-txt ask &amp;quot;what did I do Tuesday&amp;quot;&lt;/code&gt;. &lt;strong&gt;It&amp;rsquo;s&#xA;a tool that treats a coding agent as a first-class consumer of its interface,&#xA;alongside the human at the terminal.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;A human explores. They run the command, squint at the output, try a flag, read&#xA;the error, and converge. They carry context between attempts, and they can go&#xA;ask someone or read the source when they&amp;rsquo;re stuck.&lt;/p&gt;&#xA;&lt;p&gt;An agent commits. It reads the interface once, picks the command that best&#xA;matches its intent, and runs it. An error it will read and route around, so a&#xA;command that fails outright is no problem. A plausible-looking result it simply&#xA;accepts, because it has no independent sense that the number should have been 40&#xA;instead of 10.&lt;/p&gt;&#xA;&lt;p&gt;So an agent is functionally a user with no prior context, no accumulated&#xA;intuition about your tool, and no way to ask a clarifying question, who&#xA;nonetheless has to get it right immediately.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-constraint---help-has-to-be-enough&#34;&gt;The Constraint: &lt;code&gt;--help&lt;/code&gt; Has to Be Enough&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-constraint---help-has-to-be-enough&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the rule I gave myself: &lt;strong&gt;an agent handed nothing but this binary should&#xA;reach the right command on the first try.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;No instruction file to load. No skill to install. No MCP server wrapping the&#xA;CLI. No prose in a repo telling the agent which flag to prefer. Everything it&#xA;needs to use the tool correctly ships inside the tool.&lt;/p&gt;&#xA;&lt;h3 id=&#34;why-not-a-claudemd-a-skill-or-an-mcp-server&#34;&gt;Why Not a CLAUDE.md, a Skill, or an MCP Server&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#why-not-a-claudemd-a-skill-or-an-mcp-server&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Those are the normal answers to &amp;ldquo;make this tool legible to an agent,&amp;rdquo; and each&#xA;one puts the knowledge in the wrong place:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; section&lt;/strong&gt; lives in &lt;em&gt;your&lt;/em&gt; repo, not mine. It drifts out of&#xA;date the moment I ship a flag, and it only helps agents that happen to read&#xA;that file, in that repo.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;An MCP server&lt;/strong&gt; is a second artifact to build, version, release, and keep in&#xA;sync with the binary. Now there are two things that can disagree about how the&#xA;tool behaves. It does buy something help text can&amp;rsquo;t, in fairness: a typed&#xA;schema constrains arguments structurally, where help text only asks nicely. I&#xA;went the other way regardless, for the reason below.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;A skill&lt;/strong&gt; has to be installed before it can help, so the tool doesn&amp;rsquo;t work&#xA;until someone has already done setup on the agent&amp;rsquo;s side.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Help text ships with the binary, versions with the binary, and is already the&#xA;first place both consumers look. It&amp;rsquo;s the only documentation surface that can&amp;rsquo;t&#xA;drift from the thing it documents. That matters most in the case the tool&#xA;actually gets used in: someone runs &lt;code&gt;brew install grantlucas/tap/logger-txt&lt;/code&gt; and&#xA;there is no repo, no README, and no source to fall back on. There&amp;rsquo;s a binary and&#xA;its help output, and nothing else.&lt;/p&gt;&#xA;&lt;p&gt;Though the better reason I picked it is that it&amp;rsquo;s a forcing function. If&#xA;&lt;code&gt;--help&lt;/code&gt; is the only channel I&amp;rsquo;m allowed to use, every ambiguity in the tool has&#xA;to get sorted out in the help text or in the design itself. I can&amp;rsquo;t paper over a&#xA;confusing interface with a paragraph somewhere else.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-design-loop&#34;&gt;The Design Loop&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-design-loop&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The constraint makes the workflow concrete, and it ends up looking like&#xA;usability testing with an agent as the test subject:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Hand an agent the binary and nothing but &lt;code&gt;--help&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Watch for the spot where it picks a plausible-but-wrong command&lt;/li&gt;&#xA;&lt;li&gt;Fix the help text, or the design, until a cold read leads somewhere better&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Step 2 is the whole game, because &lt;strong&gt;plausible-but-wrong is the failure mode&#xA;agents produce and humans mostly don&amp;rsquo;t.&lt;/strong&gt; An agent that picks a command which&#xA;errors out is fine, since it reads the error, adjusts, and gets there. An agent&#xA;that picks a command which succeeds and returns believable-but-wrong results has&#xA;no signal that anything went wrong, and neither do I until I notice the numbers&#xA;look off weeks later.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-trap-i-built-myself&#34;&gt;The Trap I Built Myself&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-trap-i-built-myself&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;The best example came from a change that looked purely additive.&lt;/p&gt;&#xA;&lt;p&gt;I added &lt;code&gt;-t&lt;/code&gt; (type) and &lt;code&gt;-p&lt;/code&gt; (project) filters to both &lt;code&gt;show&lt;/code&gt; and &lt;code&gt;search&lt;/code&gt;. Nice&#xA;and consistent: same flags, both commands. Except that quietly created a trap.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-0-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-0-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-0-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-0-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt search WORK   &lt;span style=&#34;color:#6272a4&#34;&gt;# matches &amp;#34;WORK&amp;#34; anywhere in the line&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt show -t WORK  &lt;span style=&#34;color:#6272a4&#34;&gt;# matches only entries tagged WORK&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;These now &lt;em&gt;look&lt;/em&gt; interchangeable, but &lt;code&gt;search&lt;/code&gt; matches the term anywhere in the&#xA;full log line, including the message body, so an entry like&#xA;&lt;code&gt;31/01/26 13:30 -0600 - PERSONAL - Followed up on WORK email&lt;/code&gt; shows up in the&#xA;first command and not the second.&lt;/p&gt;&#xA;&lt;p&gt;Asked to find my work entries, the agent reasonably picked &lt;code&gt;search&lt;/code&gt; and got&#xA;plausible results back. Nothing in the help text would have told it otherwise. A&#xA;human who wrote the tool would never make this mistake, which is exactly why I&#xA;couldn&amp;rsquo;t see it.&lt;/p&gt;&#xA;&lt;p&gt;The fix went into the help string, not into a document telling the agent what to&#xA;do:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-3&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-3&#34;&gt;3&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-4&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-4&#34;&gt;4&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Note: &amp;#34;search TYPENAME&amp;#34; is not equivalent to &amp;#34;show -t TYPENAME&amp;#34;. The search&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;term matches anywhere in the full log line — including message text — so&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;entries that mention the type name in their message body will appear as false&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;positives. Use &amp;#34;show -t&amp;#34; to filter strictly by the type tag.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s deliberately blunt, and it names the wrong choice explicitly, because a&#xA;disambiguation that only describes the correct option is useless to a reader who&#xA;already believes they&amp;rsquo;ve found it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-designing-for-the-agent-produced&#34;&gt;What Designing for the Agent Produced&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#what-designing-for-the-agent-produced&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A handful of patterns fell out of running that loop. Each one is here because of&#xA;a specific way an agent breaks without it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;help-text-that-teaches-rather-than-lists&#34;&gt;Help Text That Teaches Rather Than Lists&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#help-text-that-teaches-rather-than-lists&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Without this, an agent works out the data format by running commands and&#xA;inferring from whatever comes back, which burns turns and produces a wrong&#xA;inference when the sample isn&amp;rsquo;t representative.&lt;/p&gt;&#xA;&lt;p&gt;So the root &lt;code&gt;--help&lt;/code&gt; embeds the log entry format and the full file-resolution&#xA;order. One read establishes where the data lives and what it looks like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-1&#34;&gt; 1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-2&#34;&gt; 2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-3&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-3&#34;&gt; 3&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-4&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-4&#34;&gt; 4&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-5&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-5&#34;&gt; 5&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-6&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-6&#34;&gt; 6&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-7&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-7&#34;&gt; 7&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-8&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-8&#34;&gt; 8&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-9&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-9&#34;&gt; 9&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-10&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-10&#34;&gt;10&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-11&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-11&#34;&gt;11&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Log entry format:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - TYPE - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - (PROJECT) - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - TYPE (PROJECT) - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;File resolution order:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  1. --file flag value&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  2. ./log.txt if it exists in the current directory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  3. LOGGERTXT_PATH environment variable&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  4. ./log.txt (default)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Subcommand examples show their expected output inline:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-3-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-3-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-3-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-3-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-3-3&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-3-3&#34;&gt;3&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  # With both type and project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  logger-txt add -t dev -p acme Fixed login bug&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  # =&amp;gt; 04/03/26 14:30 -0500 - DEV (ACME) - Fixed login bug&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;That &lt;code&gt;# =&amp;gt;&lt;/code&gt; line costs nothing and removes a round trip. It also makes the&#xA;uppercasing visible, since &lt;code&gt;-t dev&lt;/code&gt; comes back as &lt;code&gt;DEV&lt;/code&gt; and &lt;code&gt;-p acme&lt;/code&gt; as&#xA;&lt;code&gt;(ACME)&lt;/code&gt;, instead of leaving it as a surprise to be discovered later.&lt;/p&gt;&#xA;&lt;h3 id=&#34;documented-defaults-not-implied-ones&#34;&gt;Documented Defaults, Not Implied Ones&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#documented-defaults-not-implied-ones&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;An undocumented default gets guessed at, and a guess that happens to return rows&#xA;is indistinguishable from a correct answer. So the ones that could bite are&#xA;spelled out:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A date-only &lt;code&gt;--start&lt;/code&gt; begins at &lt;code&gt;00:00&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;A date-only &lt;code&gt;--end&lt;/code&gt; ends at &lt;code&gt;23:59&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;An active date range &lt;strong&gt;overrides&lt;/strong&gt; the default count of 10&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;That last one especially. &lt;code&gt;show --start 01/03/26 --end 14/03/26&lt;/code&gt; returning&#xA;everything in the range rather than the last 10 is the behaviour I want, and it&#xA;flatly contradicts the count default documented a few lines above it in the same&#xA;help output. That contradiction is exactly why it has to be stated: left&#xA;implicit, an agent summarising two weeks of entries would confidently report on&#xA;ten of them.&lt;/p&gt;&#xA;&lt;h3 id=&#34;no-quoting-ceremony&#34;&gt;No Quoting Ceremony&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#no-quoting-ceremony&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Quoting is one of the most reliable ways to make a generated command fail:&#xA;nested quotes, shell escaping, an apostrophe in the message. So there&amp;rsquo;s nothing&#xA;to quote.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-4-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-4-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt add Had coffee with the team&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Arguments are joined into the message, which removes the whole class of failure&#xA;and is pretty nice to type besides.&lt;/p&gt;&#xA;&lt;h3 id=&#34;a-non-interactive-path-for-everything&#34;&gt;A Non-Interactive Path for Everything&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#a-non-interactive-path-for-everything&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;A blocking prompt is a hung agent. It sits there waiting on stdin that will&#xA;never come, and the turn dies on a timeout with no useful error. So &lt;code&gt;delete&lt;/code&gt;&#xA;asks for confirmation and &lt;code&gt;delete -y&lt;/code&gt; skips it. This is the oldest advice on the&#xA;list and the easiest to forget when you write the interactive path first.&lt;/p&gt;&#xA;&lt;h3 id=&#34;composable-greppable-output&#34;&gt;Composable, Greppable Output&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#composable-greppable-output&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;One entry per line on stdout, plain text on disk:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-5-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-5-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-5-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-5-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt show | grep MEETING&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt show -c &lt;span style=&#34;color:#bd93f9&#34;&gt;50&lt;/span&gt; | wc -l&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;The property I care about here is the escape hatch. When my CLI is the wrong&#xA;tool for the job, the agent can ignore it entirely and read the log file&#xA;directly. That was always the point of the plain-text format, though it pays off&#xA;differently with a consumer that&amp;rsquo;s perfectly happy to &lt;code&gt;grep&lt;/code&gt; a file when that&amp;rsquo;s&#xA;the shorter path.&lt;/p&gt;&#xA;&lt;h3 id=&#34;loud-failure-on-ambiguity&#34;&gt;Loud Failure on Ambiguity&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#loud-failure-on-ambiguity&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Passing &lt;code&gt;--start&lt;/code&gt; without &lt;code&gt;--end&lt;/code&gt; is an error with a clear message, not a&#xA;guessed range. The temptation is to be helpful: assume &amp;ldquo;until now,&amp;rdquo; assume &amp;ldquo;for&#xA;one day.&amp;rdquo; But a guessed range produces results that look correct, which puts us&#xA;straight back into plausible-but-wrong.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Erroring out converts a silent wrong answer into an immediate, self-correcting&#xA;retry.&lt;/strong&gt; For an agent, a good error message beats a permissive default, since&#xA;the error is information and the default is a coin flip.&lt;/p&gt;&#xA;&lt;h2 id=&#34;humans-get-it-for-free&#34;&gt;Humans Get It for Free&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#humans-get-it-for-free&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the part I didn&amp;rsquo;t expect: I never once had to trade one consumer against&#xA;the other. Every change on that list made the tool better for me at the terminal&#xA;too.&lt;/p&gt;&#xA;&lt;p&gt;I think the reason is structural, and it&amp;rsquo;s the most useful thing I&amp;rsquo;ve taken from&#xA;the exercise. &lt;strong&gt;An agent is the worst-case human.&lt;/strong&gt; No context, no accumulated&#xA;intuition about the tool, no memory of the last time it hit this flag, no way to&#xA;ask. Every affordance a human silently supplies from experience, the agent needs&#xA;supplied by the interface.&lt;/p&gt;&#xA;&lt;p&gt;Which means designing for the agent isn&amp;rsquo;t a parallel track to designing for&#xA;humans. It&amp;rsquo;s designing for the strictest possible human, the one on their first&#xA;day, at 2am, with no colleague to ask. Satisfy that reader and the experienced&#xA;one is covered by construction.&lt;/p&gt;&#xA;&lt;p&gt;So there&amp;rsquo;s no &amp;ldquo;agent version&amp;rdquo; of the interface to maintain, and I&amp;rsquo;d push back on&#xA;the idea that this is extra work. It&amp;rsquo;s one interface, held to a standard that&#xA;was always the right one, now with something that reliably tells me when I&amp;rsquo;ve&#xA;fallen short of it. I &lt;em&gt;knew&lt;/em&gt; &lt;code&gt;search WORK&lt;/code&gt; and &lt;code&gt;show -t WORK&lt;/code&gt; differed, since I wrote&#xA;both. My tool&amp;rsquo;s ambiguities were invisible to me precisely because I held all&#xA;the context that resolved them. The agent holds none of it, and it doesn&amp;rsquo;t have&#xA;the good grace to quietly work around my gaps. It takes exactly the wrong turn&#xA;my help text permits, immediately and repeatably, where I can watch it happen.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-is-worth-practising-now&#34;&gt;Why This Is Worth Practising Now&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#why-this-is-worth-practising-now&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Logger-TXT is a personal tool. The stakes are that I mis-tally my hours one&#xA;week, which makes it a pretty good place to practise something that gets&#xA;expensive elsewhere.&lt;/p&gt;&#xA;&lt;p&gt;The ratio is shifting, though. More and more of the tools I build, at work and&#xA;otherwise, are going to spend most of their invocations being driven by&#xA;something that can&amp;rsquo;t ask a clarifying question in Slack, can&amp;rsquo;t absorb the tribal&#xA;knowledge in someone&amp;rsquo;s head, and can&amp;rsquo;t infer intent from a hallway&#xA;conversation. The interface is all it gets, and if the interface is ambiguous&#xA;then the tool is wrong in a way that looks right, at whatever scale the agent is&#xA;operating at.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s a design discipline, and it&amp;rsquo;s worth practising deliberately before it&#xA;matters. Which flags are confusable. Which defaults are guessable. Which prompts&#xA;hang. Which errors teach. None of it is hard, it&amp;rsquo;s just invisible until you put&#xA;something in front of the tool that can&amp;rsquo;t paper over your omissions with&#xA;intuition.&lt;/p&gt;&#xA;&lt;p&gt;If you want to poke at the result, it&amp;rsquo;s on&#xA;&lt;a href=&#34;https://github.com/grantlucas/Logger-TXT&#34;&gt;GitHub&lt;/a&gt;, or&#xA;&lt;code&gt;brew install grantlucas/tap/logger-txt&lt;/code&gt; and run &lt;code&gt;--help&lt;/code&gt;. If you try it with an&#xA;agent and it takes a wrong turn I haven&amp;rsquo;t caught yet, let me know. Those are the&#xA;gaps I can&amp;rsquo;t find on my own, since I already know what my own tool means.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>I Built an App That&#39;s Only Useful Three Weeks a Year</title>
      <link>https://grantlucas.com/posts/2026/02/i-built-an-app-thats-only-useful-three-weeks-a-year/</link>
      <pubDate>Wed, 18 Feb 2026 20:00:00 -0500</pubDate>
      <guid>https://grantlucas.com/posts/2026/02/i-built-an-app-thats-only-useful-three-weeks-a-year/</guid>
      <description>&lt;p&gt;&lt;em&gt;How building from my phone exposed every point of friction in my workflow, and how I can make it better for all devices&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Let me be upfront: the app I&amp;rsquo;m about to describe is useful for &lt;em&gt;maybe&lt;/em&gt; three&#xA;weeks out of the year. &lt;a href=&#34;https://sapcast.ca/&#34;&gt;It helps you figure out when to tap a maple tree for&#xA;sap.&lt;/a&gt; That&amp;rsquo;s it. The rest of the year it just sits there,&#xA;waiting for February to roll around again. I can&amp;rsquo;t think of much else targeting&#xA;such a specific, and time-limited, niche&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;A year ago, I wouldn&amp;rsquo;t have built this. Not because it&amp;rsquo;s hard, but because the&#xA;return on investment is laughable. A few weeks of utility for evenings of&#xA;development time, if I finish it at &lt;em&gt;all&lt;/em&gt;, is not worth it. I&amp;rsquo;d just check the&#xA;weather forecast myself, squint at the overnight lows, and make a guess like I&#xA;did last year. The motivation simply wouldn&amp;rsquo;t survive contact with the reality&#xA;of actually sitting down to build it.&lt;/p&gt;&#xA;&lt;p&gt;At the end of the day or on the weekend, at this point in my life, I &lt;em&gt;just don&amp;rsquo;t&#xA;have the time or remaining brain cycles,&lt;/em&gt; to work through software side projects&#xA;when life&amp;rsquo;s already busy or I spent all day in meetings. But the cost of&#xA;producing code has changed. And that changes which ideas I can tackle in the&#xA;limited time I have available.&lt;/p&gt;&#xA;&lt;p&gt;With &lt;em&gt;many&lt;/em&gt; experiments, learnings, and changes to my flows over the past 6-8&#xA;months, I&amp;rsquo;ve produced more code than in the past 6 years since getting into&#xA;management. Finally a lot of the ideas I&amp;rsquo;ve had can see the light&#xA;of day as I can instead focus on the product/feature, secure deployments, smooth&#xA;DevEx, and architecture of the thing I&amp;rsquo;d like to see. And each time through this&#xA;process, whether at my computer, &lt;em&gt;or now at my phone,&lt;/em&gt; &lt;strong&gt;I find points of&#xA;friction I can smooth out so that &lt;em&gt;next time&lt;/em&gt; it&amp;rsquo;s just a bit easier.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-spark&#34;&gt;The Spark&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-spark&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;d been having ongoing conversations with a family member about the impact of&#xA;AI on software engineering: what&amp;rsquo;s possible now, what&amp;rsquo;s overhyped, where it&amp;rsquo;s&#xA;actually headed. The kind of back-and-forth where you&amp;rsquo;re theorizing about the&#xA;future but don&amp;rsquo;t have a great concrete example to point at. I&amp;rsquo;d been thinking&#xA;about what simple, real app I could build to just &lt;em&gt;show&lt;/em&gt; what&amp;rsquo;s possible&#xA;instead of talking about it abstractly.&lt;/p&gt;&#xA;&lt;p&gt;Then, sitting at dinner, talking about tapping maple trees (because it&amp;rsquo;s that&#xA;time of year), the idea clicked. Last year I&amp;rsquo;d manually checked the forecast&#xA;every day, trying to figure out when the freeze-thaw cycle was right for sap&#xA;to flow. Lots of guessing, lots of doubt. Why not build something that does&#xA;that automatically?&lt;/p&gt;&#xA;&lt;p&gt;I pulled out my phone, opened the Claude iOS app, and started describing what&#xA;I wanted using &lt;a href=&#34;https://www.wispr.com/&#34;&gt;Wispr Flow&lt;/a&gt; for voice dictation. I&#xA;gave it the problem I was trying to solve, a high-level overview of my&#xA;understanding of how maple sap tapping works, and told it to do research so it&#xA;could cite its sources. Then I let it work.&lt;/p&gt;&#xA;&lt;p&gt;By the time I got home, maybe 20 minutes later, I ran &lt;code&gt;npm run dev&lt;/code&gt; and had&#xA;a working application. It detected my location, pulled a 7-day temperature&#xA;forecast, and graded each day as poor, fair, good, or excellent based on the&#xA;freeze-thaw cycle. It had information explaining how the cycle works, how the&#xA;ratings are calculated, and a full section of sources and further reading based&#xA;on the research it had done. Not a skeleton. A real app with real content.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;That was pretty eye opening. Was it perfect? Definitely not. Did I trust it&#xA;yet? Nope. Did it &amp;ldquo;work&amp;rdquo; and could I build from this? Oh yeah&amp;hellip; Let&amp;rsquo;s iterate.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;alert alert-note callout&#34; role=&#34;alert&#34;&gt;&lt;h5 class=&#34;alert-heading&#34;&gt;Note&lt;/h5&gt;This wasn&amp;rsquo;t my first attempt at building from my phone. I&amp;rsquo;d hit some friction on&#xA;a prior project that taught me a fair amount about the workflow, like making&#xA;sure your repo has a &lt;code&gt;main&lt;/code&gt; branch before Claude tries to open PRs. I wrote up&#xA;those learnings separately in &lt;a href=&#34;https://grantlucas.com/posts/2026/02/tips-for-starting-new-projects-with-claude-code-on-ios/&#34;&gt;Tips for Starting New Projects With Claude Code&#xA;on iOS&lt;/a&gt;.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;h2 id=&#34;getting-to-production&#34;&gt;Getting to Production&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#getting-to-production&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Once I was home with the app running locally, the next step was getting it&#xA;deployed. I connected a Cloudflare Worker to my GitHub repo so that every&#xA;merge to &lt;code&gt;main&lt;/code&gt; triggers a production deploy, with preview URLs enabled for&#xA;pull requests. There were a couple of build issues to work through, and I had&#xA;Claude move the project from vanilla JavaScript to TypeScript, which is what&#xA;I would have preferred from the start. I just hadn&amp;rsquo;t caught it during that&#xA;initial phone session because everything was happening so quickly.&lt;/p&gt;&#xA;&lt;div class=&#34;alert alert-note callout&#34; role=&#34;alert&#34;&gt;&lt;h5 class=&#34;alert-heading&#34;&gt;Sidenote&lt;/h5&gt;That&amp;rsquo;s one of the trade-offs of the autonomous mode that Claude Code on iOS runs&#xA;in. It&amp;rsquo;s not asking for permission as frequently as it does on desktop because&#xA;it&amp;rsquo;s working in a sandboxed environment. The upside is speed: it moves fast and&#xA;gets a lot done. The downside is that it might make a bunch of decisions and&#xA;execute on them before you have a chance to review and steer it in a different&#xA;direction. Though once you &lt;strong&gt;do&lt;/strong&gt; steer it, it shifts pretty easily, so I&amp;rsquo;m not&#xA;sure it&amp;rsquo;s actually a drawback so much as something to be aware of. 🤔&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;I was watching the output, bouncing between the Claude app and GitHub on my&#xA;phone to see what commits were getting pushed. That back-and-forth between apps&#xA;is the closest thing to a &amp;ldquo;review loop&amp;rdquo; you get on the phone.&lt;/p&gt;&#xA;&lt;p&gt;One thing I made sure to do was have Claude create unit tests for the rating&#xA;logic. The app was &lt;em&gt;functionally&lt;/em&gt; showing me different ratings for different&#xA;days, but I wasn&amp;rsquo;t confident it was correct until I could read through test&#xA;cases. Once I could see &amp;ldquo;given this overnight low and that daytime high, the&#xA;rating should be X,&amp;rdquo; I had a lot more confidence that it would work in&#xA;reality.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Like a tale as old as time, good, clear unit tests, help enable an Agent to&#xA;prove its work and help us humans build confidence in the underlying code.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;the-iteration-loop-a-few-days-later&#34;&gt;The Iteration Loop, a Few Days Later&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-iteration-loop-a-few-days-later&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A few days passed. I had more ideas. Season timing estimates based on&#xA;latitude, so if you haven&amp;rsquo;t had a good tapping window, you&amp;rsquo;d know when to&#xA;just tap anyway before you miss the season. A map showing your location.&#xA;Better error&#xA;handling if location services weren&amp;rsquo;t working. Analytics and a feedback widget&#xA;through &lt;a href=&#34;https://posthog.com/&#34;&gt;PostHog&lt;/a&gt; so I could get basic analytics and&#xA;collect feedback.&lt;/p&gt;&#xA;&lt;p&gt;By this point, the development flow was established. Here&amp;rsquo;s what the loop&#xA;looked like:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Open Claude on my phone&lt;/li&gt;&#xA;&lt;li&gt;Describe the change I want&lt;/li&gt;&#xA;&lt;li&gt;Claude writes the code, pushes a PR to GitHub&lt;/li&gt;&#xA;&lt;li&gt;Cloudflare builds a preview URL within seconds&lt;/li&gt;&#xA;&lt;li&gt;I review the changes in the preview on my phone&lt;/li&gt;&#xA;&lt;li&gt;If something&amp;rsquo;s off, I go back to Claude, request changes, and it pushes to&#xA;the same PR, and Cloudflare picks up the update automatically&lt;/li&gt;&#xA;&lt;li&gt;Merge the PR, Cloudflare deploys to production&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Rinse and repeat. &lt;strong&gt;The whole thing, from idea to production, could happen in&#xA;minutes.&lt;/strong&gt; And because each change was isolated in its own PR with a preview&#xA;deployment, I could spin up multiple Claude sessions for independent changes and&#xA;review them in parallel: one adding Gitleaks scanning, another adding help docs&#xA;for location service issues, a third integrating PostHog.&lt;/p&gt;&#xA;&lt;p&gt;In this round of iteration, the &lt;em&gt;only&lt;/em&gt; thing I needed my laptop for was&#xA;setting a new secret variable in Cloudflare for the PostHog API key.&#xA;Everything else was done entirely through the feedback loop between Claude on&#xA;iOS, GitHub, and Cloudflare.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-went-well&#34;&gt;What Went Well&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#what-went-well&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Voice-to-code is surprisingly effective for greenfield work.&lt;/strong&gt; Describing a&#xA;new application out loud, conversationally, maps well to how you&amp;rsquo;d explain a&#xA;feature to a colleague. I didn&amp;rsquo;t think about file structure or naming&#xA;conventions. I described the problem and the desired outcome and let Claude&#xA;sort out the implementation.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The research was genuinely good.&lt;/strong&gt; I told Claude to research maple sap&#xA;tapping and cite its sources. The result was an app backed by real references:&#xA;academic papers, extension programs, established guides. For something I&amp;rsquo;m&#xA;still learning about myself, having that research done &lt;em&gt;and&lt;/em&gt; integrated into&#xA;the app was valuable.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The phone-to-production cycle time was genuinely fast.&lt;/strong&gt; Once Cloudflare was&#xA;wired up, making a change and seeing it live took minutes. That&amp;rsquo;s competitive&#xA;with, or better than, a lot of traditional workflows where you&amp;rsquo;d context&#xA;switch to a terminal, run a build, push, wait for CI, and deploy. &lt;em&gt;This was&#xA;somewhat aided by Claude&amp;rsquo;s sandboxed, more autonomous mode.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;PR-based workflow with preview URLs gave me confidence.&lt;/strong&gt; Every change went&#xA;through a PR. Every PR got a preview deployment behind Cloudflare Access. I&#xA;could see exactly what the change looked like in a real environment before&#xA;merging, and share preview URLs for feedback without exposing half-finished&#xA;work.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Unit tests removed the doubt.&lt;/strong&gt; Asking Claude to write tests for the rating&#xA;logic helped the project go from &amp;ldquo;this &lt;em&gt;looks&lt;/em&gt; right&amp;rdquo; to &amp;ldquo;I&amp;rsquo;m&#xA;&lt;em&gt;pretty sure&lt;/em&gt; this &lt;em&gt;is&lt;/em&gt; right.&amp;rdquo; Being able to read through concrete test cases&#xA;grounded the whole thing.&lt;/p&gt;&#xA;&lt;h2 id=&#34;where-there-was-friction&#34;&gt;Where There Was Friction&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#where-there-was-friction&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Greenfield setup from a phone has gotchas.&lt;/strong&gt; Repo creation, default&#xA;branches, missing config files. I covered these in detail in the &lt;a href=&#34;https://grantlucas.com/posts/2026/02/tips-for-starting-new-projects-with-claude-code-on-ios/&#34;&gt;companion&#xA;tips&#xA;post&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The autonomous mode is a double-edged sword.&lt;/strong&gt; Claude on iOS moves fast and&#xA;doesn&amp;rsquo;t ask for permission as often. That&amp;rsquo;s great for momentum but means it&#xA;can make a batch of decisions before you&amp;rsquo;ve reviewed the first one. Bouncing&#xA;between the Claude app and GitHub to watch commits is the workaround, but it&amp;rsquo;s&#xA;not the same as the more interactive desktop experience. This is definitely a&#xA;pro &lt;em&gt;and&lt;/em&gt; a con at the same time.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Secrets management breaks the flow.&lt;/strong&gt; Every time I needed to add a new API&#xA;key or environment variable in Cloudflare, I had to go to my laptop. This&#xA;happened for &lt;a href=&#34;https://pirateweather.net/&#34;&gt;Pirate Weather&lt;/a&gt; (a Dark Sky compatible&#xA;weather API) and again for PostHog. It&amp;rsquo;s a small interruption, but it breaks the&#xA;autonomy of the phone-based loop.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Infrastructure setup is still a laptop task.&lt;/strong&gt; Configuring the Cloudflare&#xA;Worker, setting up the custom domain, wiring up DNS, configuring Cloudflare&#xA;Access for preview environments. All of that was done on the laptop the first&#xA;night. It&amp;rsquo;s not a Claude Code limitation; it&amp;rsquo;s a &amp;ldquo;managing cloud&#xA;infrastructure from a phone browser&amp;rdquo; limitation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;an-app-that-doesnt-really-need-to-exist-but-can&#34;&gt;An App That Doesn&amp;rsquo;t Really Need to Exist (But Can)&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#an-app-that-doesnt-really-need-to-exist-but-can&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The result is &lt;a href=&#34;https://sapcast.ca/&#34;&gt;Sapcast&lt;/a&gt;. It takes your location, pulls&#xA;real forecast data, and tells you when conditions are right to tap your maple&#xA;trees. It rates each day, highlights the best tapping window, estimates season&#xA;timing based on your latitude, and includes educational content with cited&#xA;sources. All in a beginner-friendly way.&lt;/p&gt;&#xA;&lt;p&gt;Last year I was guessing. This year I have an app backed by research that I&#xA;can check in seconds. I&amp;rsquo;m also sharing it with other people who are into&#xA;tapping to get feedback. I&amp;rsquo;m not 100% confident in my own understanding of all&#xA;this, so getting real-world feedback matters.&lt;/p&gt;&#xA;&lt;p&gt;Does this app &lt;em&gt;need&lt;/em&gt; to exist? Honestly, no. People have been tapping maple&#xA;trees forever without issue. But a year ago, the effort-to-payoff ratio would&#xA;have killed this idea before I finished dinner. Now, the idea survived because&#xA;the cost of going from &amp;ldquo;that would be neat&amp;rdquo; to &amp;ldquo;it&amp;rsquo;s live at a URL&amp;rdquo; has&#xA;dropped to the point where three weeks of utility is &lt;em&gt;enough.&lt;/em&gt; I didn&amp;rsquo;t have&#xA;to carve out a weekend. I didn&amp;rsquo;t have to weigh it against other projects&#xA;competing for my limited free time. I described it out loud at dinner and had&#xA;something real by the time I got home.&lt;/p&gt;&#xA;&lt;p&gt;That shift, from &amp;ldquo;not worth building&amp;rdquo; to &amp;ldquo;why not,&amp;rdquo; is the thing I keep&#xA;coming back to. It&amp;rsquo;s not about this app specifically. It&amp;rsquo;s about all the&#xA;small, niche, only-useful-to-a-few-people ideas that now clear the bar.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-phone-is-just-a-litmus-test&#34;&gt;The Phone is Just a Litmus Test&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-phone-is-just-a-litmus-test&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t want to do all my development from my phone. That&amp;rsquo;s not the point.&lt;/p&gt;&#xA;&lt;p&gt;Building from a phone is an &lt;em&gt;extreme&lt;/em&gt; scenario, and &lt;strong&gt;extreme scenarios are&#xA;useful because they amplify every point of friction.&lt;/strong&gt; On a desktop, you can&#xA;work around a snag quickly enough that you don&amp;rsquo;t even register it as friction.&#xA;On a phone, there&amp;rsquo;s nowhere to hide. Every rough edge stops you cold. Each of&#xA;the friction points I listed above is a small inconvenience on a desktop but a&#xA;real blocker on a phone.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s what makes it a useful experiment. If I can smooth out friction in an&#xA;environment where &lt;em&gt;any&lt;/em&gt; friction is hostile to the development flow, removing&#xA;that same friction in a more amenable environment becomes trivial.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Template repos with sensible defaults&lt;/li&gt;&#xA;&lt;li&gt;Automated preview deployments&lt;/li&gt;&#xA;&lt;li&gt;PR-based workflows with visual review&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;All of that makes the desktop experience better too. The learnings transfer&#xA;directly.&lt;/p&gt;&#xA;&lt;p&gt;For me, this is a learning process I can apply back to my career. I&amp;rsquo;ve been&#xA;thinking a lot about developer experience and reducing the distance between&#xA;having an idea and seeing it running. The phone is just a litmus test. If&#xA;the workflow holds up there, it&amp;rsquo;ll hold up anywhere.&lt;/p&gt;&#xA;&lt;p&gt;The side benefit is that I can finally build some of the side projects I&amp;rsquo;ve&#xA;been sitting on. The cost of going from idea to working software has dropped&#xA;enough that even something useful for three weeks a year is worth doing.&#xA;And the next time someone asks me what&amp;rsquo;s &lt;em&gt;actually&lt;/em&gt; possible with AI and&#xA;software engineering, I won&amp;rsquo;t have to theorize. I&amp;rsquo;ll just show them.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
