-
The REPL: Issue 136 - December 2025
RFD 576 Using LLMs at Oxide
A thoughtful overview of how Oxide uses AI. It breaks down values, different uses, and reaches solid conclusions. It recognizes AI’s usefulness for some cases while keeping humans firmly responsible.
Has the cost of building software just dropped 90%?
AI’s explosion generates more demand for software by making it easier to build. A developer and business analyst can now ship an incredible amount at lower cost. What once required five people working for months now takes one developer and a part-time business analyst a week. Because creating internal tools and prototypes is cheap, more will get built.
Smaller teams save money another way: less coordination. Fewer status meetings. Of course, developers now coordinate agents instead.
I wonder whether ideas will be less polished when fewer people are involved before building starts. As Tim Cook says:
You can go fast alone. You can go far with a team.
Building the right thing still matters. “Because we can” is not a reason to build something.
Horse
The horse and chess analogies are interesting. They suggest that as new technology improves incrementally (~20% per year), it reaches a tipping point where it clearly becomes better than the older technology, and adoption is immediate.
Cars improved steadily until they were clearly better than horses. On the next “replacement” cycle, most people traded their horse for a car.
Chess programs improved beyond a threshold where they could beat a human most of the time.
Now, regarding jobs and AI: answering questions from a knowledge base is a task where AI excels. As the author acknowledges, that part of his job is no longer something he does. He was “replaced” – but that was only part of his job. In fact, that wasn’t even the principal part:
I was one of the first researchers hired at Anthropic.
Presumably, everyone is happier that he can now focus on actual research instead of answering onboarding questions.
My takeaway: AI is already changing some of our tasks and changing our jobs.
Prediction: AI will make formal verification go mainstream
I’ve always been interested in formal verification. The idea of mathematical proofs for algorithms and software is appealing. As noted, it is highly impractical because writing the proofs themselves is hard and requires “PhD-level training”.
Kleppmann argues that AI can change that, and not only that, can help validate the onslaught of AI-written software that is coming.
-
The REPL: Issue 135 - November 2025
LLMs are steroids for your Dunning-Kruger
LLMs can give you false confidence, because they can reinforce your prior beliefs. They are “eager to please”. Asking leading questions can change the results greatly. I am mindful of asking questions that are neutral.
Using the expand and contract pattern
A good overview of the expand and contract pattern. Handy to keep around to send to others, which comes up about once a month!
We should all be using dependency cooldowns
This is the first time I’ve heard “dependency cooldown” as a term. In a sense, I already implement the cooldown instinctively. I upgrade methodically and purposefully, especially when major versions are concerned.
-
Git: Amend any commit
I recently read The (lazy) Git UI You Didn’t Know You Need and experimented with
lazygit. My usual workflow involves invokinggitdirectly, staging commits withgit addandgit add -p, often selecting files using a fuzzy finder. The article highlightedlazygit’s ability to amend any commit, not just the most recent one, which sparked an idea.Current Workflow for Amending Commits
To amend a commit, I first stage the changes. Then, when I want to amend the last commit, I use:
$ git commit --amendThis opens my editor with the previous commit message, which I usually accept and close without change.
Amending a previous commit is more involved. After staging the changes I:
$ git commit --fixup <sha>(The sha is typically picked through fuzzy finding as well).
Then I rebase to actually apply the fixup those two commits:
$ git rebase -i --autosquash <earlier-sha>The
<earlier-sha>is also selected with a fuzzy finder, and must be a commit prior to the one being fixed.I think we can do better.
git-fix
I realized much of this could be automated. I prompted an agent to create a script for me that would do the following:
# Amends the last commit with staged changes $ git fix # Amends the specific commit with staged changes $ git fix <sha>What about conflicts? Abort and leave the repo in the same state as it was before the command.
I also instructed it to use TDD, which I believe effectively guided the agent.
Specs:
require "spec_helper" require "open3" require "tmpdir" require "fileutils" RSpec.describe "git-fix" do let(:script_path) { File.expand_path("../settings/.bin/git-fix", __dir__) } around do |example| Dir.mktmpdir do |dir| Dir.chdir(dir) do # Initialize a git repo system("git init --quiet") system("git config user.email 'test@example.com'") system("git config user.name 'Test User'") system("git config commit.gpgsign false") example.run end end end it "amends a commit with staged changes" do # Create initial commit File.write("file.txt", "line 1\n") system("git add file.txt") system("git commit --quiet -m 'Initial commit'") # Create a second commit that we'll want to fix File.write("file.txt", "line 1\nline 2\n") system("git add file.txt") system("git commit --quiet -m 'Add line 2'") target_commit = `git rev-parse HEAD`.strip # Now we want to fix the second commit (no commits after it) File.write("file.txt", "line 1\nline 2 fixed\n") system("git add file.txt") # Run git-fix output, status = Open3.capture2e(script_path, target_commit) expect(status.success?).to be(true), "Command failed with output: #{output}" # Verify the commit was amended fixed_content = `git show HEAD:file.txt` expect(fixed_content).to eq("line 1\nline 2 fixed\n") # Verify commit count is still 2 commit_count = `git rev-list --count HEAD`.strip.to_i expect(commit_count).to eq(2) end it "works with short commit references" do # Create initial commit File.write("file.txt", "initial\n") system("git add file.txt") system("git commit --quiet -m 'Initial'") # Create target commit File.write("file.txt", "initial\nv1\n") system("git add file.txt") system("git commit --quiet -m 'Add v1'") target_commit = `git rev-parse --short HEAD`.strip # Stage a fix File.write("file.txt", "initial\nv1 fixed\n") system("git add file.txt") # Run git-fix with short SHA output, status = Open3.capture2e(script_path, target_commit) expect(status.success?).to be true expect(File.read("file.txt")).to eq("initial\nv1 fixed\n") end it "amends a middle commit when changes don't overlap" do # Create initial commit with two files File.write("file1.txt", "content 1\n") File.write("file2.txt", "content 2\n") system("git add .") system("git commit --quiet -m 'Initial commit'") # Modify file1 in second commit File.write("file1.txt", "content 1 modified\n") system("git add file1.txt") system("git commit --quiet -m 'Modify file1'") target_commit = `git rev-parse HEAD`.strip # Modify file2 in third commit (different file, no overlap) File.write("file2.txt", "content 2 modified\n") system("git add file2.txt") system("git commit --quiet -m 'Modify file2'") # Now fix the second commit (file1) File.write("file1.txt", "content 1 fixed\n") system("git add file1.txt") output, status = Open3.capture2e(script_path, target_commit) expect(status.success?).to be(true), "Command failed with output: #{output}" # Verify both files have correct content expect(File.read("file1.txt")).to eq("content 1 fixed\n") expect(File.read("file2.txt")).to eq("content 2 modified\n") # Verify commit count is still 3 commit_count = `git rev-list --count HEAD`.strip.to_i expect(commit_count).to eq(3) end it "defaults to HEAD when no commit is specified" do # Create initial commit File.write("file.txt", "line 1\n") system("git add file.txt") system("git commit --quiet -m 'Initial commit'") # Create second commit File.write("file.txt", "line 1\nline 2\n") system("git add file.txt") system("git commit --quiet -m 'Add line 2'") # Stage changes to fix the last commit (HEAD) File.write("file.txt", "line 1\nline 2 fixed\n") system("git add file.txt") # Run git-fix without specifying commit (should default to HEAD) output, status = Open3.capture2e(script_path) expect(status.success?).to be true # Verify the last commit was amended expect(File.read("file.txt")).to eq("line 1\nline 2 fixed\n") # Verify commit count is still 2 commit_count = `git rev-list --count HEAD`.strip.to_i expect(commit_count).to eq(2) end it "shows error when nothing is staged" do File.write("file.txt", "content\n") system("git add file.txt") system("git commit --quiet -m 'Initial'") target_commit = `git rev-parse HEAD`.strip output, status = Open3.capture2e(script_path, target_commit) expect(status.success?).to be false expect(output).to match(/nothing.*staged/i) end it "aborts rebase and restores staged changes on conflict" do # Create initial commit File.write("file.txt", "line 1\n") system("git add file.txt") system("git commit --quiet -m 'Initial commit'") # Create second commit File.write("file.txt", "line 1\nline 2\n") system("git add file.txt") system("git commit --quiet -m 'Add line 2'") target_commit = `git rev-parse HEAD`.strip # Create third commit that modifies the same line File.write("file.txt", "line 1\nline 2 modified\n") system("git add file.txt") system("git commit --quiet -m 'Modify line 2'") # Try to fix the second commit with a conflicting change File.write("file.txt", "line 1\nline 2 different fix\n") system("git add file.txt") # Run git-fix (this should fail due to conflict) output, status = Open3.capture2e(script_path, target_commit) expect(status.success?).to be false expect(output).to match(/conflict/i) expect(output).to match(/aborted/i) # Verify we're not in the middle of a rebase expect(Dir.exist?(".git/rebase-merge")).to be false expect(Dir.exist?(".git/rebase-apply")).to be false # Verify the fixup commit was removed last_commit_msg = `git log -1 --pretty=%B`.strip expect(last_commit_msg).to eq("Modify line 2") # Verify the changes are still staged staged_diff = `git diff --cached --name-only`.strip expect(staged_diff).to eq("file.txt") # Verify the staged content is what we wanted to fix staged_content = `git diff --cached file.txt` expect(staged_content).to include("-line 2 modified") expect(staged_content).to include("+line 2 different fix") end endCode:
#!/usr/bin/env bash # Amends the referenced commit with the code currently staged # Usage: # git fix [commit-sha-reference] # # If no commit reference is provided, defaults to HEAD (the last commit). # # This script uses git's --fixup and --autosquash features to amend a commit # in your history. It works best when the changes don't overlap with subsequent # commits. If there are conflicts during the rebase, you'll need to resolve them # manually. set -euo pipefail # Default to HEAD if no commit reference is provided commit_ref="${1:-HEAD}" # Check if there are staged changes if git diff --cached --quiet; then echo "Error: Nothing is staged. Stage your changes with 'git add' first." >&2 exit 1 fi # Resolve the commit reference to a SHA before creating the fixup # This is important because creating the fixup will change where HEAD points target_commit=$(git rev-parse "$commit_ref") # Create a fixup commit git commit --fixup="$target_commit" # Get the parent of the target commit to use as rebase base rebase_base=$(git rev-parse "$target_commit^") # Rebase with autosquash (non-interactive) # If the rebase fails, we need to clean up if ! GIT_SEQUENCE_EDITOR=: git rebase --autosquash -i "$rebase_base" 2>&1; then echo "" >&2 echo "Error: Rebase failed due to conflicts." >&2 echo "Aborting rebase and restoring your staged changes..." >&2 # Abort the rebase git rebase --abort # Uncommit the fixup commit but keep changes staged git reset --soft HEAD~1 echo "Aborted. Your changes are still staged." >&2 echo "Please resolve conflicts manually or modify your changes." >&2 exit 1 fiAfter only a few days of use, I can already see me using this regularly.
-
The REPL: Issue 134 - October 2025
Vibing a Non-Trivial Ghostty Feature
Mitchell Hashimoto’s extensive post details his use of AI to code a Ghostty1 feature, mirroring my own experience with AI. It excels at prototyping and boilerplate, and is good at explaining existing code. However, it requires constant supervision; I frequently tweak and guide its output. Sometimes, it misses the mark entirely and gets stuck.
Abstraction, not syntax
Alternative configuration formats solve superficial problems. Configuration languages solve the deeper problem: the need for abstraction.
Abstraction, while simplifying expression, comes at the the cost of generating the configuration file, as the article explains. However, the author omits that YAML supports references, offering a degree of abstraction:
# Define reusable base configurations .defaults: &defaults region: "eu-west" .lifecycle_policies: hourly: &hourly-policy delete_after_seconds: 345600 # 4 days daily: &daily-policy delete_after_seconds: 2592000 # 30 days monthly: &monthly-policy delete_after_seconds: 31536000 # 365 days buckets: - <<: *defaults name: "alpha-hourly" lifecycle_policy: *hourly-policy - <<: *defaults name: "alpha-daily" lifecycle_policy: *daily-policy - <<: *defaults name: "alpha-monthly" lifecycle_policy: *monthly-policy - <<: *defaults name: "bravo-hourly" lifecycle_policy: *hourly-policy - <<: *defaults name: "bravo-daily" lifecycle_policy: *daily-policy - <<: *defaults name: "bravo-monthly" lifecycle_policy: *monthly-policyWhile this is not a while loop, it does remove the repetition, and the need to check each of the values multiple times.
Locating Elements in Hash Arrays Using Pattern Matching in Ruby
Pattern matching in Ruby is still relatively new. I’ve only seen it used sparingly, but this use is concise. I’m still mulling over the syntax. Assigning to feels weird. A few years ago, when numbered parameters (e.g.,
collection.each { _1.do_something }) were introduced, I didn’t care much for them. Now, I am on a team that uses them constantly and the syntax has grown on me. Perhaps pattern matching like this will take hold in time.system = { users: [ { username: 'alice', role: 'admin', email: 'alice@example.com' }, { username: 'bob', role: 'user', email: 'bob@example.com' }, { username: 'charlie', role: 'moderator', email: 'charlie@example.com' } ] } system => {users: [*, { role: 'moderator', email: }, *]} puts email # charlie@example.com -
The REPL: Issue 133 - September 2025
Yet another LLM rant
Dennis Schubert discussed LLMs – like half the internet.
LLMs can be a useful tool, maybe. But don’t anthropomorphize them. They don’t know anything, they don’t think, they don’t learn, they don’t deduct.
This is true. Although they can fake it quite a bit, especially agents that break down the next steps, read files, and come up with a list of todos, etc.
I find myself thinking that AI is both unreasonably hyped-up and incredibly useful. I’ve been able to get proof-of-concept projects working in minutes with only superficial knowledge of the underlying technologies. Denying that agentic AI is powerful seems foolish. Also, thinking that they can replace engineers wholesale is equally foolish.
We are in for a time of disruption.
Pick the wrong tool for the job
Sometimes the wrong tool for the job is right for you at a given moment in time. In this case, the author wanted to use Ruby to be able to iterate faster. Being able to learn what the users really want was more important than having the perfect technical solution