Paul Welty, PhD AI, WORK, AND STAYING HUMAN

Dev reflection - January 28, 2026

So here's something I've been sitting with lately. There's this gap—a subtle one—between a system that's running and a system that's actually working. And I don't mean broken versus not broken. I m...

Duration: 8:52 | Size: 8.12 MB


Hey, it’s Paul. January 28th, 2026.

So here’s something I’ve been sitting with lately. There’s this gap—a subtle one—between a system that’s running and a system that’s actually working. And I don’t mean broken versus not broken. I mean something trickier than that.

Let me give you an example. I had an app in production. All the monitoring was green. Servers running, database connected, everything looking healthy. And yet no one could sign up. New users would hit the registration page and get blocked. The security layer was doing exactly what it was configured to do: treat requests without existing sessions as suspicious. Which makes sense, until you remember that someone trying to create an account doesn’t have a session yet. That’s the whole point.

The system was working perfectly. It was also completely unusable for its primary purpose.

This got me thinking about how we build things. More specifically, how the things we build age. Not code rotting or servers failing. But context shifting. The assumptions we bake into our work made sense at the time. Then time passes, the work evolves, and suddenly those assumptions are quietly working against us.

Here’s what’s interesting. It’s not that anyone made a mistake. The security rule that blocked new signups? Reasonable. If you’re building something where all legitimate users are already logged in, then yeah, treating unauthenticated requests as suspicious is smart. But then you add a public marketing page. Or a health check endpoint that monitoring tools need to hit. Or, you know, a signup page. And now that rule is catching things it was never meant to catch.

The configuration didn’t change. The context did.

I keep seeing this pattern everywhere. A dependency set to “use any version 0.1 or higher,” which was fine when the library was at version 0.1. But then months pass, the library hits version 1.26, it’s gone through a hundred breaking changes, and your project just silently upgraded through all of them. The constraint that once meant “stay current” now means “accept whatever chaos upstream decides to ship.”

Or take something simpler. A feature I built that would automatically save work notes as draft blog posts. Seemed useful at the time. Capture the thinking, maybe develop it later. Except I never developed them later. They just accumulated. Dozens of drafts sitting there, representing not future blog posts but past good intentions. The feature worked exactly as designed. It just wasn’t solving a real problem anymore.

So the question becomes: how do you notice when this happens?

Because these misalignments don’t show up as errors. The blocked signup returned a clean response. The outdated dependency ran fine. The unused drafts saved successfully. Every health check passes. Every log looks normal. The system is working exactly as configured. The problem is that the configuration no longer matches the work.

This is why I think monitoring, at least the way most people do it, is insufficient. We’re good at asking “is the database connected?” and “are the servers responding?” We’re terrible at asking “can a new user actually complete the thing they came here to do?”

Those are different questions. The gap between them is where most of the subtle failures live.

I’ve been thinking about this in terms of what I’d call critical path testing. Not load testing, not uptime monitoring. Something more like: exercise the actual journey someone would take. Load the signup page as an unauthenticated user. Submit the form. Verify the account gets created. That’s different from checking if the signup endpoint exists. It’s checking if the whole circuit works.

And circuits are a good metaphor here. A circuit doesn’t work until the last connection is made. You can have power, you can have all the components wired correctly, but if there’s one gap somewhere, nothing flows. I had a product that shipped to the app store, actually available, actually downloadable, but the website still had placeholder links. The product was real. The path to get it was broken. And that one broken connection meant the whole thing might as well not exist, from a user’s perspective.

This brings up another tension I keep running into: explicit versus discoverable.

Let me explain. When I set up a system to process my work across different projects, I had a choice. I could maintain an explicit list: “here are the five projects I want included.” Or I could let the system discover them: “scan this folder, find anything that looks like a project, include it automatically.”

Both approaches have real tradeoffs. The explicit list documents decisions. It lets you exclude things intentionally. It’s a record of what you chose. But it’s also maintenance burden. Every time you add a project, you have to remember to update the list.

The discoverable approach is lower friction. Structure signals intent. If a project folder exists with the right shape, it gets included. You don’t have to think about it. But you lose the ability to say “yes this exists, but no I don’t want it included.”

The solution I landed on was hybrid: try the explicit list first, fall back to discovery if the list is empty. Which feels right for this case. But the underlying tension doesn’t go away. It shows up everywhere.

Think about how version control works. It discovers all the files in your project automatically. But it requires explicit commits. You have to actively choose what gets saved. That’s a deliberate design decision about where the friction should live.

Or think about trial periods for software. You could set an explicit end date: “trial expires in 14 days.” Or you could leave it open and discover the right moment from behavior: “convert them when they’ve gotten enough value.” The explicit approach is predictable but arbitrary. The discoverable approach is flexible but harder to reason about.

I don’t think there’s a universal answer here. But I do think it’s worth noticing when you’re fighting a system because it’s on the wrong side of that line. If you’re constantly updating a config file to mirror what already exists in your folder structure, that’s a sign. If you’re constantly surprised by what got included automatically, that’s also a sign.

The other pattern I want to talk about is distribution. When information about something lives in multiple places.

A landing page is a good example. You’ve got a hero section at the top with a call to action. You’ve got a product card in the middle with another link. You’ve got a section at the bottom with yet another link. Three different entry points for the same action, because users arrive at different stages of consideration. Someone who’s ready to buy scrolls to the top and clicks immediately. Someone who’s skeptical reads the whole page and clicks at the bottom.

That distribution makes sense. It serves real user needs. But it also means that when you update something, like changing a placeholder link to a real download URL, you have to remember to update it in three places. And if you miss one, you’ve got a page that’s partially functional. Which is almost worse than fully broken, because it’s harder to notice.

I see this with product information across different surfaces too. The homepage says one thing, the products page says another thing, the detail page says a third thing. Each serves a different context, which is good architecture. But it creates coordination friction when anything changes.

The temptation is to centralize everything. Put all the product data in one file, have all the pages pull from there. And sometimes that’s the right call. But it’s not free. You lose flexibility, you add complexity, and if you only have two or three products, the overhead probably isn’t worth it.

This is one of those cases where the right answer depends on where you are in the lifecycle. Early on, a little duplication is fine. You can keep it in your head. Later, when you’ve got a dozen products and a team of people, centralization pays off. The mistake is standardizing too early, before you know what actually varies.

Okay, so where does all this leave us?

I think the core insight is that systems encode assumptions. And those assumptions have a shelf life. The security rule that made sense for an authenticated-only app breaks when you add public pages. The dependency constraint that made sense at version 0.1 becomes dangerous at version 1.26. The feature that made sense when you thought you’d develop those drafts becomes clutter when you realize you won’t.

The system doesn’t know this. It can’t tell you “hey, this rule no longer matches your reality.” It just keeps doing what it was configured to do, faithfully, correctly, and increasingly out of alignment with what you actually need.

So the work, the ongoing work, is noticing when context has shifted. And honestly, I don’t have a great automated solution for that. Most of the misalignments I’ve described, I discovered by accident. By trying to sign up for my own app. By looking at a list of unused drafts. By noticing that a shipped product wasn’t actually linked anywhere.

Maybe that’s okay. Maybe some things require human judgment, the kind that asks not “is this working?” but “is this still the right thing to be doing?”

What I’m trying to build is at least the habit of asking that question. And maybe some lightweight tooling that makes the gaps more visible. Tests that exercise real user journeys, not just infrastructure health. Audits that show how far dependencies have drifted. Reviews that ask “what exists here that we’re not actually using?”

Because the gap between working as configured and working as intended, that’s where the interesting problems are. And closing that gap isn’t a one-time fix. It’s ongoing attention to whether the assumptions you’ve encoded still match the world you’re operating in.

That’s what I’ve been thinking about. Talk to you tomorrow.

· development · daily, reflection, podcast

Featured writing

Why customer tools are organized wrong

This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.

Busy is not a state

We've built work cultures that reward activity, even when nothing actually changes. In technical systems, activity doesn't count—only state change does. This essay explores why "busy" has become the most misleading signal we have, and how focusing on state instead of motion makes work more honest, less draining, and actually productive.

Infrastructure shapes thought

The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.

Books

The Work of Being (in progress)

A book on AI, judgment, and staying human at work.

The Practice of Work (in progress)

Practical essays on how work actually gets done.

Recent writing

Textorium is live on the App Store

Textorium launches on Mac App Store - a native editor for Hugo, Jekyll & Eleventy that manages hundreds of posts with table views and smart filtering.

Dev reflection - January 27, 2026

So here's something I've been sitting with this week. I've been building systems that generate content—podcast scripts, social media posts, that kind of thing—and almost immediately after getting t...

Notes and related thinking

Dev reflection - January 27, 2026

So here's something I've been sitting with this week. I've been building systems that generate content—podcast scripts, social media posts, that kind of thing—and almost immediately after getting t...

Dev reflection - January 25, 2026

I spent part of today watching a game fall apart in my hands. Not because it was broken—technically everything worked fine. It fell apart because I'd confused being clever with being usable.

Dev reflection - January 24, 2026

So here's something I've been sitting with lately. I spent the last couple days working across a bunch of different projects, and I noticed something strange. In almost every single one, the most i...