I’ve switched to Amp full time for all of my build things needs. I used to swing between Claude Code, Codex, and Amp. Amp was always my favorite, but the usage pricing was too restricting to have it as my default.
I didn’t want to commit to one model/provider to get an understanding what their strengths and weaknesses are. Sure, there were some differences. I preferred Codex over Claude for UI tasks, and Claude over Codex for heavy thinking. But with the latest iterations of Opus & GPT? The differences are fading away.
Quoting Adam Wathan: “Feels like we’re about 6 months away from a token is a token is a token. Just gas at the gas station.” Amp switched their default model from Opus to Sol overnight without saying anything. No one complained. I’m at a point where I don’t care about which model I’m using. I do care about the user experience, and Amp is way ahead the competition there.
I love Amp for it’s clarity (both in output language and UI) and that it just works. I don’t feel like I need to spend time configuring it too much to use it. Amp also got me into running agents in the cloud, because their Orbs setup make it seamless.
As of last month, you can use your ChatGPT subscription in Amp. This gets rid of most usage costs (you only pay when Amp decides to use a non-OpenAI model, which is rare).
My biggest gripe with Amp (although I’ve heard about this problem with LLMs in general) is the obsession with verification. When you’re throwing a task at your agent this is a good thing. But when you’re having a back-and-forth coding session it can be a nuisance. Do I have to wait on an end-to-end test suit and compiler check because I asked to change the emoji in the footer? I prefer fast feedback from the agent since 90% of the time the verification turns out to be all clear. And if it isn’t, CI stands ready as the last line of defense.
This seems to have improved since I’ve set up my Amp-specific AGENTS.md:
- Only run tests, typechecks, verifications, etc. when working on big chunks of work, not small tasks like updating display values.
- Don’t add unit tests for local presentational changes, like renaming a label in a specific form, or reordering table columns.
Amp is the first harnass to give me a coding experience in the new age that makes me want to be there. My next step? Drop the decade-old habit of defaulting to local developmen. Orbs, orbs, orbs.
I came across a subtle Octane but last week. We needed to disable Inertia’s history encryption in some places in the app. By default, it’s enabled everywhere. So we added a route check to the middleware to turn it off.
class HandleInertiaRequests
{
public function handle(Request $request, Closure $next)
{
if ($request->is('admin/*')) {
Inertia::encryptHistory(false);
}
// …
}
}
The problem is, if you’re running Octane and make a request to an admin/* route, history encryption will be disabled, and there’s nothing to re-enable it for the next request.
The fix is to make sure both sides of the statement are executed.
class HandleInertiaRequests
{
public function handle(Request $request, Closure $next)
{
if ($request->is('admin/*')) {
Inertia::encryptHistory(false);
} else {
Inertia::encryptHistory(true);
}
// …
}
}
Or more succinct:
class HandleInertiaRequests
{
public function handle(Request $request, Closure $next)
{
Inertia::encryptHistory(! $request->is('admin/*'));
// …
}
}
As shared by on Simon Willison’s blog, Zig has an interesting anti-LLM policy for contributions in their code of conduct.
Zig values contributors over their contributions. Each contributor represents an investment by the Zig core team - the primary goal of reviewing and accepting PRs isn’t to land new code, it’s to help grow new contributors who can become trusted and prolific over time.
I don’t think an anti-LLM policy makes sense for most projects. But a programming language requires considerate intent and deep knowledge for changes. It’s an interesting fit there.
In addition:
If a PR was mostly written by an LLM, why should a project maintainer spend time reviewing and discussing that PR as opposed to firing up their own LLM to solve the same problem?
We’ve historically preferred PR contributions over issues, because it puts the contributor in the position to do most of the work. But if a PR is just a contributor prompting an LLM, is a well-written issue preferable? When writing code becomes cheaper, might as well let the maintainer get it done using their taste.
I’ve always been intrigued by Go. It’s powerful, fast (I really like fast), and simple. The tradeoff? It’s simple. Coming from higher level languages like PHP & JavaScript, Go can feel crude.
As I’ve recently written, this blog went back to Hugo. Hugo is also fast and crude. But with LLMs, the crudeness of things doesn’t hurt as much as before. I don’t need to deal with writing it anymore, and crude is still readable.
From Just Fucking Use Go:
The boring choice is the right choice. It always was.
I agree. More than ever, I have an urge to dabble into lower level languages. AI is a huge layer of complexity we’re adding to our tooling. Let’s use it to trim the fat from our outputs.
This is an older post from Jim Nielsen on building user interfaces I came back to last week.
With software, the fact is that sometimes there are just too many variables to know and test and smooth out. So I click around, using the UI over and over, until I finally cannot give myself any more splinters.
I feel this. I’m better at my job when I’m actually using something. Even better when I’m using something because I want to use it.
I’ve been building a side project on and off for a few months now. Having it my phone’s homescreen and actually using it every day gives me a different perspective on it than just building it and shipping it to others, like is often the case for client work.
Sand it, feel the grain, get a splinter, sand again, and repeat until smooth.
When I experimented with Elixir a few years ago, I fell in love with the pipe operator |>. Since then, I’ve always dreamt of a similar tool in PHP.
Last year, we finally got it! Unfortunately, the standard array & string manipulation functions in PHP aren’t exactly known for their API consistency, which makes the pipe operator awkward to use.
I ported Laravel’s excellent collection & string helpers to a pipe operator-optimized API. Piper is an attempt to wrap the standard library to make it compatible with the pipe operator.
use function Spatie\Piper\Arr\{filter, join, map};
use function Spatie\Piper\Str\{prefix, suffix};
[1, 2, 3, 4, 5, 6]
|> filter(fn (int $i) => $i % 2 === 0)
|> map(fn (int $i) => pow($i, 2))
|> join(', ', ', and ')
|> prefix('The winning numbers are ')
|> suffix('.');
// "The winning numbers are 4, 16, and 36."
Read more
This blog has gone through a few technical iterations. Most recently: from a Laravel app with Sheets, to a static site with Hugo, to a CMS-driven site with Statamic. And now, back to Hugo!
I love Hugo. It’s purposefully built with the right abstractions, and incredibly fast. The kind of fast that makes you wonder why other software can be that fast. Fast software, the best software. But the templating language… it might be an acquired taste, but I never acquired it. Updating my theme or adding a custom page was always a chore. I got tired of it. Coincidentally, I wanted to experiment with more dynamic workflows on here (posting to social media, more control over scheduling…) so I migrated to Statamic (a lovely CMS!)
Fast forward three years and a blogging hiatus, and I started to crave the simplicity of the good old static site. Having a dynamic system was fun, but it requires maintenance and having it made me realize I didn’t need it. Add to that we’ve commoditized robots building things for us, and switching back to Hugo was the right move. A single prompt and 15 minutes later, my entire blog was translated from Statamic to Hugo. The best part: I don’t need to care about the templates anymore. Sure, I’ll dive in occasionally for some surgical styling tweaks. But generally, changes are handled with a quick prompt.
GitHub pings my webserver to auto-deploy on push. Scheduling is solved by running the deploy script every hour. As a static site, everything’s fast by default and there are almost no points of failure. Life is simple. All that remains is to write!
I have a blank canvas problem. I’m way better at staying in flow when I have something to work through, so these days, the first thing I do is ramble.
I open Codex or Spiral, turn on Monologue (my preferred text-to-speech tool), and dump everything I can think of on the topic. Before Monologue, I never considered typing speed to be a bottleneck. My fingers move faster than the speed I shape ideas. But when I want to vomit a bunch of unstructured thoughts onto a canvas, they suddenly feel slow and error-prone.
Instead, I can start a task by talking through it to generate a transcript. Then I ask an agent to structure my ramblings (the other day, the agent responded with “Your brain dump is rich."—probably the nicest compliment I’ve ever gotten from a robot!). This gives me a great starting point to really dig into the problem. Before I know it, I will have rewritten all that rambling into coherent a string of thought.
I still can’t shake the dystopian feeling of talking to my computer. But text-to-speech has made some tasks feel so natural, I almost can’t imagine working without it anymore.
I wrote a piece on the Spatie blog about the place packages/library have in the ecosystem when first-party code is cheaper than ever with AI.
Scrolling to the end:
The question before every composer require is no longer “is there a package for this?” Almost certainly there is. The new question is: “do I want to own this problem?”
This thought came across my mind as I was reading Every’s philosophy on compound engineering:
The first three steps (plan, work, review) produce a feature. The fourth step produces a system that builds features better each time. […]
- Capture the solution. Ask yourself: What worked? What didn’t? What’s the reusable insight?
- Make it findable. Add YAML frontmatter to make sure it is tagged with the right metadata, tags, and categories for retrieval.
- Update the system. Add new patterns into CLAUDE.md, the file the agent reads at the start of every session. Create new agents when warranted.
- Verify the learning. Ask yourself: Would the system catch this automatically next time?
As virtuous developers we try to adhere to the boyscout rule: leave the campsite better than you found it. We do this so our codebase doesn’t start rotting because we slowly let the trash take over.
When agents are writing our code, these effects are multiplied. Every time we make an explicit improvement to the system, agents can pick the pattern up and apply it to both old and new. The campsite will slowly but surely start cleaning itself up, and fresh trash will be thrown straight into the bin.
Andy Matuschak’s notes on working in public. Reading this post was part of the spark that got me blogging again.
It needs to both support tasks that are due today and tasks that are due “sometime this week”.
I like this distinction and wish task managers had this concept by default. Overall a refreshingly simple approach to personal task management.
Alex Woods:
The goal of writing is not to have written. It is to have increased your understanding, and then the understanding of those around you. When you are tasked to write something, your job is to go into the murkiness and come out of it with structure and understanding. To conquer the unknown.
The second order goal of writing is to become more capable. It is like working out. Every time you do a rep on the boundary of what you can do, you get stronger. It is uncomfortable and effortful.
Letting an LLM write for you is like paying somebody to work out for you.
This comes close to what I was talking about in my last newsletter:
I don’t want AI to replace my thinking, and this is easier said than done. Thinking now requires self-discipline. It’s easier to effortlessly prompt the machine and forwarding the output as is. I don’t want to aim for good enough. I want AI to be a lever for better work.
It’s time to forget about the limits we’re used to working in. We need to step out of our magic circle.
To really take advantage of what AI can do, developers need to start thinking about software differently— not as the means to but as the ends. Don’t deliver tools that allow users to achieve outcomes. Just deliver the outcomes.
[…] Instead of requiring users to learn a templating system and string replacements, you can simply write custom messages en mass.
Markdown.new is a nice little tool to convert a webpage into Markdown without any fuss.
To make it even easier, I created a bookmarklet to instantly convert the page you’re viewing to Markdown.
javascript:(()=>{location.href='https://markdown.new/'+location.href})()