[go: up one dir, main page]

  • How I Review My Agents' Code

    Coding agents have changed how I work. I now give code-review-style feedback to agents many times a day.

    In the before times, I used git diff main... to review my own changes locally before pushing to GitHub. As I noticed things, I wrote a todo list for myself to address. Rinse and repeat.

    These days that feedback goes to an agent, not to my own todo list. I used to copy-paste file names and code snippets into the chat, but that gets old and verbose fast. Now I use revdiff, a TUI built specifically for giving feedback to agents.

    Giving Feedback on Diffs

    At its heart, revdiff opens a TUI that shows you changes in files. As you navigate, press return to write a comment on a line (or several). When you’re done, press q and the program prints your comments in the context of the lines they apply to. From their own example:

    ## handler.go (file-level)
    consider splitting this file into smaller modules
    
    ## handler.go:43 (+)
    use errors.Is() instead of direct comparison
    
    ## handler.go:43-67 (+)
    refactor this hunk to reduce nesting
    
    ## store.go:18 (-)
    don't remove this validation
    

    I review code before every commit, to catch issues as early as possible. The default revdiff invocation shows unstaged changes. It fits my workflow exactly.

    You can also pass git ranges: revdiff main..., revdiff branch-1...branch-2, and so on. That’s especially helpful for reviewing a whole branch.

    Giving Feedback on Plans

    I have the agent write specs and plans first. That alone produces much better code (I use superpowers). I want to give feedback on those plans, but they aren’t diffs of anything, and most of the time I don’t even put them under source control. revdiff has me covered: revdiff --only path/to/plan.md opens the same feedback mechanism, with table-of-contents support for markdown. It beats what I did before: opening the file in my editor, adding comments prefixed with ---> (or similar), then telling the agent to go find them.

    No Skill

    revdiff ships with a skill that opens the feedback for you in an overlay window, supporting many different setups. I haven’t needed it. Because I have my agent set up to prompt me before committing any code, I get a yes/no question when it’s time to review a diff. I find it easy to switch to another terminal window, fire up revdiff, give feedback, and switch back. Then I accept the changes, or select no and paste the output. The skill would require me to stop the agent just to invoke it.

    Copy to Clipboard on Exit

    Shuttling output between windows by hand added friction, so I automated copying the revdiff output to my clipboard every time. My fish shell configuration:

    function revdiff --description "Run revdiff and copy annotations to clipboard"
        set -l tmp (mktemp /tmp/revdiff.XXXXXX)
        command revdiff --output=$tmp $argv
        and cat $tmp
        and pbcopy < $tmp
        rm -f $tmp
    end
    

    I don’t worry about overriding my clipboard, because I use a Raycast’s clipboard manager.

    Config Tweaks

    Lastly, I’ve tweaked the configuration:

    $ cat ~/.config/revdiff/config
    theme = nord
    cross-file-hunks = true
    compact = true
    

    I can’t say enough good things about the Nord theme in dark mode.

    Conclusion

    revdiff has been an immediate productivity boost. I use it many times a day, and it has made interacting with agents much easier.

    Read on →

  • The REPL: Issue 141 - May 2026

    Nate’s Dotfiles

    A couple of nuggets I picked up:

    • mise.local.toml lets me add personal tool configuration to any project – even one whose mise.toml I don’t control, or that has none at all.
    • .git/info/exclude works like .gitignore, but for my eyes only. .gitignore is itself tracked by git; .git/info/exclude ignores files without recording the exclusion in the repository.

    Three Inverse Laws of AI

    In a nod to Isaac Asimov’s laws for robots, the author proposes three laws for the humans using them – incomplete by their own admission, but worth chewing on:

    • Non-Anthropomorphism
    • Non-Deference
    • Non-Abdication of Responsibility

    It all comes down to one habit: keep exercising critical thinking.

    Appearing Productive in The Workplace — No One’s Happy

    What discipline looks like, in this environment, is almost embarrassingly old-fashioned and may seem obvious to most of you until you try to avoid it. Use the tool where you can verify precisely what it produces. Never ask a model for confirmation; the tool agrees with everyone, and an agreement that costs the agreer nothing is worth nothing.

    I’ve been using this example with my non-tech friends: ask the 🤖 for a contract to sell your car in California and it will happily produce one, brimming with confidence that it’s sound. I’m not a lawyer, so I can’t judge it. I can skim it for anything obviously crazy, but I don’t know what’s missing, what it fails to protect me from, or whether every clause is even legal.

    We replaced Redis with MySQL for inventory reservations—and it scaled (2026)

    A clear account of moving the locking mechanism from Redis into the database to guarantee atomicity, with FOR UPDATE SKIP LOCKED put to great effect. The idea new to me was the “replenishing” pool: a set of reservable items large enough to absorb concurrent locks, yet small enough to stay practical.

    Read on →

  • The REPL: Issue 140 - April 2026

    Keeping a Postgres queue healthy — PlanetScale

    Simeon Griggs explains the challenges and dynamics of queue workloads in Postgres. A job queue in Postgres is desirable because of transactionality, and because it spares you from maintaining a separate system. At a certain scale, though, it outpaces the database’s ability to clean up after itself. I haven’t evaluated PlanetScale’s solution to the problem.

    Highlights from Git 2.54

    git is my most used command. I’m glad to see the CLI still improving. git history looks genuinely useful for splitting or rewording commits. The new hook configuration also offers a much cleaner way to declare hooks – clearly needed, judging by the many popular hook-management systems out there.

    Do I belong in tech anymore?

    Ky Decker writes a personal account of disillusionment with the tech sector. In the “The psychic toll of AI” section, Ky describes a thoughtless use of AI in his workplace, where critical thinking has gone out the window and AI is shoved everywhere. A harrowing account. I’m glad I’m not in that situation. In a short time, AI has changed how I plan features, explore alternatives, and code. Yet the technology has limitations and needs a human in the loop to check its output.

    Read on →

  • The REPL: Issue 139 - March 2026

    LLMs can be absolutely exhausting

    The author points out that some of the frustrating things about working with LLMs are derived from how you work with the LLM. The author is not wrong, but those same things he identifies as sources of diminishing returns are true without LLMs too. Fatigue has an impact on output, as do lack of thinking ahead of time about the correct end-state and slow feedback loops. There is nothing new under the sun.

    What is agentic engineering? - Agentic Engineering Patterns

    Agentic Engineering is a new term for me. I like it better than vibe coding, for the same reason that I always describe myself as a software engineer and not a coder. Engineering is designing and building systems. Not just knowing the correct syntax to talk to a computer.

    Writing code has never been the sole activity of a software engineer. The craft has always been figuring out what code to write.

    Exactly. Agents have made coding easier, but where I’ve felt they really increase my productivity is in the long planning sessions, effortless design documents, generating as many diagrams as I want in seconds. This lets me really dig into a design and hone it.

    Warranty Void If Regenerated

    I liked this short story a lot. At first glance a story about farming and AI seems weird. Farming is “close to the earth” and seems not very tech-oriented. Upon closer inspection, farming is an industry that has changed dramatically because of technology. Modern farming makes it possible for 5% of the population to produce all our food. It used to take >95%.

    This story is imagining yet more changes to farming.

    Reports of code’s death are greatly exaggerated

    AI can already write a novel today. You can do it right now. Will that be a great novel? Probably not. And no one seems to be saying that it will lead to novelists being completely out of the job. In fact, I think AI is a mediocre writer, as writers go. But most people are not good at writing. Having an AI proofread or write what they want to say actually improves their communication.

    Is it the same with code? Maybe there is something to that analogy. Today, AI can create great scripts that you can use locally. Even apps for personal use that will truly solve problems for individuals, even small businesses. Does that mean no one will need to know anything about software and vibe code Netflix’s streaming infrastructure? I’m not holding my breath.

    I have a couple of friends that have small businesses and are vibe coding some internal apps. I am truly worried that they will get into trouble by publishing credentials, leaving open infrastructure, etc. And the thing is, they know so little about the risks and how the internet works, that it’s hard to explain to them what they are doing. “What if I ask 🤖 to review for security?”. Sure. It will help, but they probably don’t know enough to prompt it correctly.

    Read on →

  • Stop Using next in Ruby Loops

    LLM-generated Ruby code tends to reach for next inside .each loops. It works, but it’s not idiomatic. When you find yourself using next, there’s almost always a more expressive alternative using Enumerable methods.

    A Simple Example

    Consider this loop that collects names of active users:

    names = []
    users.each do |user|
      next if user.inactive?
    
      names << user.name
    end
    

    The next if is doing filtering, but it doesn’t say filtering. It says “skip this one,” which forces the reader to mentally invert the condition to understand what’s being kept. Compare:

    names = users.reject(&:inactive?).map(&:name)
    

    The intent is explicit: reject inactive users, then map to names. Each method communicates exactly one operation.

    Discrete Steps

    Enumerable methods let you express collection processing as discrete, named steps: select, reject, map, filter_map. Each step communicates intent. When you use next inside .each, you combine filtering and transformation into one opaque block. The reader has to mentally simulate the loop to understand what it does.

    Named methods are also composable. You can add, remove, or reorder steps without restructuring the entire loop. A chain of Enumerable methods reads as a description of what the code does, not how it iterates.

    A More Complex Example

    Here’s a loop that processes orders with multiple conditions:

    summaries = []
    orders.each do |order|
      next if order.cancelled?
      next if order.total < 100
      next unless order.region == "US"
    
      summaries << {
        id: order.id,
        total: order.total,
        customer: order.customer_name
      }
    end
    

    Three next statements, each encoding a different filter. You have to read all of them to understand which orders survive. Compare:

    summaries = orders
      .reject(&:cancelled?)
      .select { |o| o.total >= 100 }
      .select { |o| o.region == "US" }
      .map { |o| { id: o.id, total: o.total, customer: o.customer_name } }
    

    Each step is readable in isolation. The pipeline reads as a description: reject cancelled orders, keep those above $100 in the US region, then transform to summaries.

    One Operation Per Step

    Enumerable has methods like filter_map that combine more than one operation. They can be useful, but at the cost of obscuring intent. Consider:

    results = items
      .select(&:valid?)
      .filter_map { |item| item.compute_value }
    

    filter_map maps and removes nil results in one pass. But the reader has to know that to understand what’s happening. Compare:

    results = items
      .select(&:valid?)
      .map { |item| item.compute_value }
      .compact
    

    Each step does one thing: select valid items, transform them, remove nils. The tradeoff is worth considering – filter_map is more concise, but .map { ... }.compact makes every operation visible.

    What About Performance?

    The obvious objection: chaining creates intermediate arrays, while the .each loop iterates only once. In practice, this rarely matters. But when it does, that’s what lazy is for:

    summaries = orders
      .lazy
      .reject(&:cancelled?)
      .select { |o| o.total >= 100 }
      .select { |o| o.region == "US" }
      .map { |o| { id: o.id, total: o.total, customer: o.customer_name } }
      .to_a
    

    Same expressive chain, single-pass iteration. You get clarity and performance.

    Conclusion

    When you see next in a loop, treat it as a signal. There’s probably an Enumerable method that says what you mean more clearly.

    Read on →