Engineering
-
Boring Is a Feature
What does boring look like in the age of AI? And I’m not talking about uninteresting. I’m talking about highly maintainable.
JavaScript?
I mean I guess models are good at it. Everybody knows it. It runs everywhere. The biggest problem with JavaScript is that TypeScript is better.
Certainly it’s better than picking a novelty language that you haven’t built anything with before. Does anybody on the team actually know Haskell? And how long ago did they know Haskell? You need to evaluate the cost of adopting it just as you would evaluate how long it would take to learn it and train the team on it.
The upfront cost can be easier to measure. But the recurring ones are much harder to predict. What happens when a maintainer moves on from a package that everyone uses, and the speed it takes to find a new maintainer is not as fast as you need it to be?
Boring tools are ones where the recurring cost of maintenance is as close to zero as you can get it. If you come back to it in eight months, it should work the way you remember it. This is a fairy tale that we tell ourselves, that nothing ever changes and we can control that change.
Is boring even possible in the age of AI, when it feels like everyone has their own particle beam cannon that they can point at your codebase?
I think it’s worth talking about what I mean by boring, because it can be used as a synonym for old, but that’s not what I mean. Boring means predictability.
Take all your npm packages. Can you answer these questions about all of them? Probably not.
- How often do the release notes contain the word “breaking”? Skim a year of them. This is the single best signal available and it takes ten minutes.
- How many people can merge? One is a risk regardless of how good that one person is. People change jobs, burn out, and lose interest.
- What happens to old versions? A project that supports the previous major for a while is telling you something about how it thinks about your time.
- Can you read the source? Not all of it. Enough to fix something yourself when you’re blocked and nobody’s answering.
Learn the new tool. Experiment. Try new things. Stay passionate about software. Just because you can use the new thing doesn’t mean you should.
Don’t always pick the boring option, just like you don’t always pick the new option. It takes wisdom to know what the right answer is.
You have to understand your failure modes, and when it’s an appropriate time to take a risk, and the scale of the risk.
Pick boring for the parts you don’t want to think about. Save the interesting decisions for the places where being interesting is the point.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
DevOps Developer-tools Software-development Engineering Tooling
-
The Human Job Is Choosing What Not to Build
Coding agents have made one word much harder to say.
No.
You describe a feature, and the agent can already see the model, the migration, the command, the tests, and the docs it needs to touch. The whole thing sounds like an afternoon instead of a week.
So why not build it?
That question used to contain its own answer. We didn’t have the time or the people, and the feature wasn’t worth interrupting everything else for two weeks. Now the implementation might take twenty minutes. The old constraint is gone, but the need to choose is not. If anything, choosing matters more, because we can say yes faster than we can understand what all those yeses are doing to the product.
The agent’s job is to make the thing we asked for. The human job is deciding whether the thing should exist.
Yes Produces Better Receipts
Building something leaves evidence. There’s a diff. Tests pass. A new command shows up in the help output. You can take a screenshot, close an issue, and point at the feature. Work happened, and the artifacts prove it.
Choosing not to build produces almost nothing. Maybe you leave the idea in a notebook. There’s no demo for the dependency you avoided or the interface you never had to support.
That makes restraint feel less like engineering.
It can look like indecision, or lack of ambition, or an empty afternoon of planning.
A good “no” can preserve more value than a clean implementation.
You protected the shape of the product. You kept it understandable. You left room for the features and the users that actually matter.
Agents Make Local Ideas Look Great
Coding agents are strongest when the task is concrete. Add this flag. Support this file format. Create an adapter for this provider. Cache this response. Put a dashboard on top of these logs.
Given one of those, the agent inspects the local code and produces a reasonable path forward. It sees where the new feature fits.
What it doesn’t automatically carry is the opportunity cost.
You can put that context in the prompt, but somebody still has to decide how much product is enough.
A human has to protect the product.
Cheap Is Not the Same as Free
I recently argued that code is cheap now, but decisions are not. The mistake is pricing a feature only by how long the first implementation takes.
A twenty-minute feature can create a permanent interface, another concept every future agent has to understand, or a second way to do a task when the first way was already fine.
The implementation estimate answers: how quickly can we make this work?
The product decision asks: is this worth changing what the system is?
Four Ways to Say No
Not building something doesn’t always mean rejecting it forever. Here are four versions of no.
No, this is not the product
Some ideas are useful and still don’t belong. A small command-line tool doesn’t automatically need a web dashboard. A personal publishing pipeline doesn’t need multi-tenant permissions because it could theoretically serve a team. A library doesn’t need a plugin system before a second plugin exists.
These features may solve real problems. But they solve somebody else’s version of the product.
This is the cleanest no.
Write down the boundary and move on.
Not yet, we don’t have the evidence
Sometimes the problem is plausible but unproven. The database might need caching. The API might need another abstraction. Users might want a second export format. “Might” is doing all the work.
Wait for the system to produce evidence. Measure the slow query. See a second provider show up. Hear the same request from someone who actually has the problem. The agent will still be there when the need is real.
Deferral is only useful when it has a condition: “Revisit imports when we have enough data to support the decision.”
All the “maybe laters” are not backlogs; they are fossils that deserve to be buried.
Yes, but smaller
Plenty of ideas contain one valuable piece surrounded by a feature-shaped cloud.
Build something that you think is useful. Not everything needs a dashboard.
You don’t need a generic provider framework until you have a reason for the generic provider to exist.
You don’t need a rules engine if you’ve only got three rules.
Ask for the smallest change that provides the most value.
Narrow the solution before it becomes a broad one.
No longer
The hardest no is the one aimed at code that already exists. How do you determine whether it’s actually being used or not? This is how we get features that outlive the reason they were built. You added an experiment that became a supported path, and now everybody’s afraid to remove it.
Prune the branches of the product tree. Before you decide what to build next, make sure you’re not letting the system grow forever.
You should be asking yourself: what choices are you deciding against? Sometimes it’s important to know what already exists before deciding what should exist.
My Filter Before I Say Yes
Product committees suck, especially for small changes. Before I accept an idea as a feature, I ask myself the following questions. Or I should ask myself. Or I hope I ask myself. Whatever version of that makes sense for the day.
- What problem gets easier?
- Why now?
- What new promise does this create?
- What gets harder after this exists?
- Can a smaller change prove the value?
- What would make us remove it?
These help you tell the difference between what’s easy to generate and what’s worth owning. Sometimes they help you solve for a hypothetical problem. It’s a good thing to try to figure out whether the tests will pass before you build them.
Taste Is the Remaining Bottleneck
As implementation gets cheaper, the scarce skill becomes taste.
Not taste as in fonts and rounded corners. Taste is recognizing when a product has enough concepts. It’s choosing the boring interface people can understand. It’s seeing that a flexible abstraction makes the current problem worse. It’s knowing which rough edge gives the tool character and which one just wastes time.
Then decide.
The future of software isn’t a world where we finally build every idea in the backlog.
That sounds exhausting.
The future of software is a world where we get to decide, and be honest about which ideas were actually good to begin with.
Don’t forget you’re allowed to say no.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
Code Is Cheap Now. Decisions Aren't
I’ve spent a lot of time working on software backlogs with coding agents. Pick an issue, inspect the code, make the change, run the tests, close it, move to the next one.
The strange part is how often writing the code is no longer the hard part.
An agent can add a field to a model, update the database, wire it through a client, and write a pile of tests before I check on it again. That would have been hours or days of time before. Now it happens in one focused session.
Great.
But the feature still needs someone to decide what the field means. Is it nullable? Which system owns it? Can it be inferred from another value? What happens when the provider stops sending it? Does an older local value survive, or should it be cleared?
The typing got cheaper. The decisions did not.
The Keyboard Was Never the Whole Job
We’ve spent decades measuring software work by its visible output. Lines of code, commits, pull requests, story points, features shipped. Code was expensive to produce, so counting the artifacts at least felt related to the effort.
That relationship is falling apart.
You can ask an agent for three implementations of the same feature and get all three before lunch. You can generate a REST API, a migration, a test suite, and a deployment manifest in one sitting. If you don’t like the architecture, ask for another one.
Now you have four implementations and a new problem: which one should exist?
Software development was never just converting requirements into syntax. It was deciding which requirements were real, where the boundaries belonged, what failure looked like, and which tradeoffs you were willing to own. Code generation doesn’t remove that work. It removes the part that used to hide it.
One Small Change, Twelve Real Questions
Let me give you an example.
Say an external API adds an optional date. Adding it to your application sounds trivial. Nullable column, property on the model, parse the response, write a migration, done.
Except the provider already has another date with a similar name. One is when the item was published. The other is when the user interacted with it. They’re both dates. They are not interchangeable.
So now the real questions start:
- Do we store only the explicit value, or infer it when it’s missing?
- Is a missing field the same as a field containing
null? - If the remote value disappears, do we delete the local value?
- Does this field participate in unchanged-record detection?
- Will a dry run exercise the same comparison path as a real sync?
- Can older installations run safely before the migration is applied?
- What happens when a second provider represents the same idea differently?
None of those are unique to any language. The agent can explain the options and implement whichever answer you pick, but it can’t discover your intended data contract from the shape of a JSON response.
If you skip those decisions, you still get code. You just get code that quietly invents policy for you.
Cheap Code Creates Expensive Options
This is the part that you might underestimate.
When implementation was slow, the cost naturally limited how many ideas made it into a codebase. You might sketch three approaches, but you probably built one. The friction forced a little restraint.
Agents remove that friction. A feature that would have been rejected as “not worth a week” now looks attractive because the first version only takes an hour. A speculative abstraction feels harmless because the agent can generate it in minutes. A new internal tool seems reasonable because the prototype already works.
It worked. But “it works” and “it’s a good idea” are two very different things.
The generated code still has to be reviewed. Its dependencies still need updates. Its behavior still needs tests. Someone still has to understand it when the surrounding system changes. If it handles credentials or customer data, somebody owns that risk too.
The implementation cost may be close to zero. The ownership cost is not.
This is how you end up with a backlog full of individually reasonable features that collectively make the project worse. Each one was cheap enough to add. Nobody stopped to price the maintenance costs associated with each successive feature.
Decision Debt
We already have a name for shortcuts in implementation: technical debt. You move quickly today and accept that the code will cost more to change later.
Agentic development creates another kind. Call it decision debt.
Decision debt is what happens when code gets produced faster than anyone can answer why it exists, what promise it makes, and who is responsible for it. The implementation is complete, but the boundaries are fuzzy. The tests prove what the code currently does, but nobody has decided whether that’s what it should do.
You can see it in questions like these:
- Are we supporting this behavior or merely tolerating it?
- Is this data authoritative, derived, or cached?
- Is this workflow meant for one person or every user?
- Is this abstraction solving a repeated problem or predicting one?
- What would make us remove this feature?
An agent can help you reason through every one of those. That’s one of the best uses of the technology. Ask it to inspect the repository, find conflicting assumptions, model failure cases, and challenge the proposed design.
Just don’t confuse receiving an answer with making a decision.
The Human Work Moves Upstream
If agents keep getting better at implementation, the valuable human work moves toward choosing and framing the work.
That means writing a clear definition of done before generating code. Deciding which system is the source of truth. Recognizing when two similar concepts need separate names. Looking at a working implementation and saying, “No, this does not belong here.”
It also means treating restraint as engineering work.
Closing a stale issue without implementing it can be more valuable than shipping the feature. Reusing an existing boundary can beat introducing a cleaner new abstraction. Deleting a half-maintained tool can improve a system more than generating its replacement.
None of those choices produce an impressive diff. That’s fine. The diff was never the product.
What Good Looks Like Now
I don’t think the answer is to slow agents down, or to pretend generated code is somehow less legitimate than code typed by hand. The code doesn’t care who wrote it. If it’s correct, understandable, tested, and worth owning, ship it.
The change is in where we spend our attention.
Spend less time admiring how quickly the implementation appeared. Spend more time checking the assumptions it smuggled in. Ask what new state the system owns, what promise the interface makes, what can fail, and whether the feature deserves to survive its prototype.
Agents make it possible to build almost anything you can describe. Our job is deciding what should still exist six months later.
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
AI Software-development Engineering Agentic-coding Technical-debt
-
Day 12: Earth to Mars, Come In
NASA’s Mars mission control runs on Mars time, not Earth time.
For the first few months of every landed mission (Curiosity, Perseverance, Insight), the scientists shift their work schedules by 39 minutes every day.
They go to sleep 39 minutes later, wake up 39 minutes later, do all their meetings 39 minutes later than yesterday.
After a few weeks they’re working at 3 AM Earth time. After a month they’re working at noon. After two months they’re back to where they started.
This is what living on Mars time looks like to humans. It is exhausting and demonstrably bad for sleep. They do it anyway because the rovers don’t care about Earth’s schedule.
Let’s talk about why time on Mars is hard.
The sol
A Martian solar day is called a sol, and it’s about 24 hours, 39 minutes, 35 seconds.
That’s almost an Earth day. Close enough that NASA’s first instinct in the 1970s was to use Earth time for Mars missions anyway.
Bad idea. Within a single Martian month, your Earth-time schedule drifts nearly a full day out of phase with the local Martian one.
The rover’s morning camera shots happen during your meeting. Its solar panels charge while you’re trying to sleep.
So NASA started using Mars time for the human side of mission ops. They built Mars-time wristwatches in the 1990s, actual mechanical watches modified to run 2.7% slower, and gave them to mission controllers.
They built Mars-time-aware mission scheduling software. They renamed “day” to “sol” so nobody got confused.
It worked. It was also brutal on the humans, because human circadian rhythms evolved for Earth’s 24-hour day, not Mars’s 24-hour-39-minute one.
Mars time shifts your sleep 39 minutes later every day, which is roughly equivalent to flying west across two time zones, every day, forever.
After a few months I can see everyone getting their schedules wrecked.
Each rover has its own time zone
Mars doesn’t have just one time. Each rover gets its own time zone, called Local Mean Solar Time (LMST), based on its specific longitude on Mars.
Curiosity is in Gale Crater. Perseverance is in Jezero Crater. They are about 3,700 kilometers apart on Mars, and they keep local solar times that differ by about four hours.
Curiosity is roughly four hours later in its day than Perseverance, because Gale Crater is 60 degrees east of Jezero.
If you’re a rover and you want to know when the sun will rise tomorrow, you use your LMST, not the other rover’s.
There’s also a coordinated reference: Coordinated Mars Time (MTC), anchored to Airy Crater, which is roughly the Mars equivalent of Greenwich.
Airy is where Mars’s prime meridian sits by IAU convention, and MTC is the mean solar time at that location. Most planetary scientists default to MTC when reporting Mars events without specifying a longitude.
So Mars has its own day (the sol), its own time zones (LMST per location), its own Greenwich (Airy Crater), and its own UTC-analog (MTC).
The whole planet has built up a parallel set of timekeeping conventions, derived from the same basic problem Earth solved: a rotating body needs a way to talk about when things happen.
The Moon is being figured out right now
In 2024, the White House Office of Science and Technology Policy directed NASA to establish Coordinated Lunar Time (LTC) by 2026.
The Artemis program needs it. So does every commercial lunar lander launching this decade: SpaceX, Blue Origin, ispace, Astrobotic.
All of them need to coordinate communications, navigation, and surface operations on the Moon, and they need a shared time standard to do it.
The Moon’s timekeeping problem is harder than Mars’s, for two reasons.
First, the Moon’s day is 29.5 Earth days long (synodic). A lunar “noon” lasts 14 Earth days, and so does the night.
The whole concept of “day” as a unit of human activity falls apart. Lunar mission ops will likely use Earth-anchored time for everything and ignore the local sun.
Second, relativity matters more than you’d think. Clocks on the Moon run about 58 microseconds per day faster than clocks on Earth’s surface, due to the Moon’s weaker gravity well.
That’s bigger than GPS’s 38 µs/day. If you want a lunar communication network to synchronize with Earth-side networks, you have to bake in the correction the same way GPS did, but more aggressively.
LTC is being designed right now. The current proposal is an atomic timescale traceable back to TAI, with relativistic corrections applied at the lunar surface.
It will probably be ready before Artemis 3 lands humans?
Deep space and the light-delay problem
Beyond the Moon and Mars, time becomes a different problem entirely: light delay.
- Round-trip to Mars: 6 to 44 minutes, depending on orbital geometry
- Round-trip to Jupiter or Saturn: hours
- Round-trip to Voyager 1, currently 24 billion kilometers from Earth: about 46 hours
You can’t run NTP to a spacecraft beyond the Moon. The sync protocol assumes round trips of milliseconds, and the universe doesn’t oblige.
The Deep Space Network (NASA’s array of giant antennas at Goldstone, Madrid, and Canberra) sends time-tagged commands to spacecraft, and spacecraft tag their telemetry with their own onboard atomic clocks.
The clocks have to be reliable for years or decades without correction, because by the time a round trip resolves, you’ve moved on to the next problem.
For interplanetary work, physicists use three relativistic coordinate timescales:
- TCB (Barycentric Coordinate Time): a clock that lives at the center of mass of the solar system. The natural frame for tracking planets, asteroids, comets.
- TCG (Geocentric Coordinate Time): a clock at Earth’s center. The frame for tracking Earth satellites and orbital mechanics close to Earth.
- TT (Terrestrial Time): a clock on Earth’s geoid. What UTC is derived from. What humans live in.
TCG drifts about 22 milliseconds per year from TT. TCB drifts nearly half a second per year from both.
Most humans never encounter this.
Anyone doing calculating planetary calculations does.
The deeper point
“The day” is parochial. It works only on the body where it’s defined.
Earth time scales fine on Earth. GPS scales fine in Earth orbit. Mars time scales fine on Mars. None of them scale to each other.
Every body in the solar system has its own “now,” and there’s no single instant that applies everywhere at once.
It’s a consequence of relativity. Time literally runs at different rates at different gravitational potentials and different velocities.
A clock at the solar system barycenter ticks differently than a clock on Earth. There is no “true” rate. There are only frames.
So how do we coordinate? The standard answer, for spacecraft and astronomers, is to pick a coordinate frame, anchor everything to it, and convert as needed at the destination. TCB for solar-system work. UTC for Earth civilians. GPS for navigation. LTC (coming) for the Moon. MTC for Mars.
The clocks themselves are “easy” but the coordination between them is the not.
The civilian question, what time is it if I want to call my friend on Mars, has no clean answer.
You pick a coordinate frame, you both agree to use it, and you live with the conversion. There’s no “Mars time on your phone” because there’s no Mars infrastructure to sync your phone with.
And even if there were, you’d still have to handle the light delay.
Tomorrow we come back to Earth, and to the most ubiquitous time format in human history: the number of seconds since midnight, January 1, 1970.
Sources
- Timekeeping on Mars — Wikipedia
- Coordinated Lunar Time — Wikipedia
- Barycentric Coordinate Time — Wikipedia
- Geocentric Coordinate Time — Wikipedia
- Terrestrial Time — Wikipedia
- Curiosity rover — Wikipedia
- Perseverance rover — Wikipedia
- InSight — Wikipedia
- NASA Deep Space Network — Wikipedia
- Voyager 1 — Wikipedia
- Clockmaker Helps Mars Rover Keep Mars Time — NPR
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
-
Using Claude to Think Through a Space Elevator
When I say I wanted to understand the engineering problems behind building a space elevator, I mean I really wanted to dig in. Not just read about it. I wanted to work through the challenges, piece by piece, with actual math backing things up.
So I decided to see what Claude and I could do with this kind of problem.
Setting it Up
I have an Obsidian vault that Claude Code/CoWork has access to, and I started by asking it to help me understand the core challenges of building a space elevator. First things first: clearly state all the problems. What are the engineering hurdles? What makes this so hard?
From there, I started asking questions. Could we use an asteroid as the anchor point and manufacture the cable in space? How would we spool enough cable to reach all the way down to Earth? Would it make more sense to build up from the ground, down from orbit, or meet somewhere in the middle?
I’ll admit I made some mistakes along the way. I confused low Earth orbit with geostationary orbit at one point but Claude corrected me and explained the difference. That’s part of what makes this approach work. You’re not just passively reading; you’re actively thinking through problems and getting corrected when your mental model is off.
Backing It Up With Math
Here’s where it got really interesting. I told Claude: don’t just describe the problems. Prove them. Back up every challenge with actual math and physics calculations.
I also told it not to try cramming everything into one massive document. Write an overview document first, then create supporting documents for each problem so we could work through them individually.
So Claude started writing Python code to validate all the calculations. I hadn’t planned on that initially, but once it started writing code, I jumped in with my typical guidance. Use a package manager, write tests for all the code.
What we ended up with is a Python module covering about 12 of the hardest engineering challenges for a space elevator. There’s a script that calls into the module, runs all the math, and spits out the results. It’s not a complete formal proof of anything, but it’s a structured way to think through problems where the code can actually catch mistakes in the reasoning.
And it did catch mistakes. That’s the whole point of this approach, you’re using the calculations as a check on the thinking, not just trusting the narrative.
Working Through Problems Together
As we worked through each challenge, I kept asking clarifying questions. What about this edge case? How would we handle that constraint?
It was genuinely collaborative, me bringing curiosity and some engineering intuition, Claude bringing the ability to quickly formalize ideas into code and calculations.
The code isn’t public or anything. But the approach is what I think is worth sharing.
The Hard Part Is Still Hard
My main limiting factor is time. The math looks generally fine to me, but if I really wanted to verify everything thoroughly, I’d need to spend a lot more time with it. A mathematician or physicist who’s deeply familiar with these calculations would be much faster at spotting issues. Providing guidance like, “no, you shouldn’t use this formula here, that approach is wrong.”
I can do that work. It’s just going to take me significantly longer than someone with that specialized background.
This is what I mean when I talk about working with agentic tools on hard problems. It’s not about asking an AI for the answer. It’s about using it as a thinking partner; one that can write code, run calculations, and help you check your reasoning as you go.
For me, that’s the real power of tools like Claude. Not replacing expertise, but amplifying curiosity.
-
The World's Strongest Cable Is One Atom Thick
I fell down a rabbit hole this morning. It started with a simple question: how far are we really from a space elevator? This really is a feasibility question, and I"m convinced that the answer is probably no, but for a really fascinating reason.
The Basic Problem
A space elevator needs a cable stretching from Earth’s surface to geostationary orbit, so about 36,000 kilometers up. That cable has to hold its own weight, plus whatever cargo you’re lifting, plus deal with a counterweight extending beyond geostationary orbit that’s constantly trying to pull away to balance everything out.
The heavier the cable, the more stress it puts on itself. So you need something impossibly light and impossibly strong. Carbon nanotubes were the go-to candidate for years, but even their theoretical limits fall short of what’s needed. The forces involved are just too extreme.
Enter Graphene
Graphene is similar to carbon nanotubes, but instead of a tube, it’s a flat hexagonal lattice of carbon atoms… so essentially a really long sheet, or aka a ribbon.
The world’s strongest cable isn’t round. It’s flat, and it’s one atom thick.
That’s kind crazy to think about, that the strongest material we can come up with is literally as thin as matter gets.
Why It Still Won’t Work (Probably)
Even if graphene has the right properties on paper, we have a number of impossibly hard engineering problems.
Manufacturing: We can’t produce a continuous ribbon of graphene anywhere close to the lengths needed. We’re talking thousands of kilometers of perfectly formed, single-atom-thick material. So not happening.
You don’t want one ribbon anyway: Even if you could manufacture a single ribbon that long, you wouldn’t want to. You need redundancy. If one section gets damaged — and at 36,000 km of exposure to space debris, micrometeorites, and atmospheric forces, damage is a when, not an if. You need backup ribbons to bear the load. So you’d need separate sections, separate lengths.
The bonding problem: And this is where I think the whole concept falls apart. There’s no reliable way to bond or clamp separate graphene ribbons together. Think about it… how do you reliably verify that something one atom thick is actually bonded properly? Every single joint, every repair, becomes a critical failure point. You can’t just slap a section in place and hope for the best.
To do that in a reproducible, verifiable way, at scale, in space, repeatedly. I think that’s the thing that makes the space elevator functionally impossible. Not conceptually impossible, but practically impossible.
The Climber Problems
I’m deliberately ignoring all the problems the climber introduces (the mechanism that actually lifts cargo up the cable). There’s a whole separate set of headaches there. But the cable itself is the fundamental challenge, and it comes down to this:
We might have identified the right material. Graphene’s properties are genuinely remarkable. But knowing what to build something out of and knowing how to build it are two very different things.