{
  "authors": [
    {
      "name": "Pau Friedman",
      "url": "https://pau.fm/"
    }
  ],
  "description": "Essays, notes, and useful links by Pau Friedman.",
  "feed_url": "https://pau.fm/feed.json",
  "home_page_url": "https://pau.fm/blog",
  "items": [
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I wonder how the Kafka microservices for that low-volume CRUD app are going.</p>\n",
      "date_published": "2026-09-15T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/15/kafka-microservices",
      "summary": "I wonder how the Kafka microservices for that low-volume CRUD app are going.",
      "tags": [
        "short"
      ],
      "title": "I wonder how the Kafka microservices for that low-volume CRUD app are going.",
      "url": "https://pau.fm/blog/2026/09/15/kafka-microservices"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Inspired by\n<a href=\"https://www.vals.ai/blogs/fable-solves-cyphral-distich\">Claude Fable 5.1 solving the 370-year-old Cyphral Distich</a>,\nI asked it to decrypt a message from my good friend Ted Cruz.</p>\n<div class=\"short-media\" data-count=\"2\"><a class=\"short-media__item\" data-pswp-height=\"1242\" data-pswp-width=\"1104\" href=\"https://pau.fm/images/blog/ted-cruz-cipher.webp\"><img src=\"https://pau.fm/images/blog/ted-cruz-cipher.webp\" alt=\"The Zodiac Killer’s 340-character cipher: a dense grid of hand-drawn letters, circles, triangles, and crosshair symbols\" decoding=\"async\" height=\"1242\" loading=\"lazy\" width=\"1104\"></a><a class=\"short-media__item\" data-pswp-height=\"1242\" data-pswp-width=\"1104\" href=\"https://pau.fm/images/blog/ted-cruz-cipher-claude.webp\"><img src=\"https://pau.fm/images/blog/ted-cruz-cipher-claude.webp\" alt=\"Claude Code running Fable 5.1. The prompt reads “solve this message that ted cruz sent me” with an attached image. The reply is “API Error: 400 Output blocked by content filtering policy”. Baked for 39s.\" decoding=\"async\" height=\"1242\" loading=\"lazy\" width=\"1104\"></a></div>\n\n",
      "date_published": "2026-09-14T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/14/a-message-from-my-good-friend-ted-cruz",
      "summary": "Inspired by Claude Fable 5.1 solving the 370-year-old Cyphral Distich, I asked it to decrypt a message from my good friend Ted Cruz.",
      "tags": [
        "short"
      ],
      "title": "Inspired by Claude Fable 5.1 solving the 370-year-old Cyphral Distich, I asked…",
      "url": "https://pau.fm/blog/2026/09/14/a-message-from-my-good-friend-ted-cruz"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I came up with a phrase recently, while thinking about the price of AI tokens: <strong>commodities want to\nbe cheap</strong>.</p>\n<p>I mean it in the neoliberal sense. Once something becomes standardized, substitutable, scalable, and\nwidely produced, competition starts attacking the margin. Producers can differentiate, constrain\nsupply, bundle, brand, regulate, or monopolize, but the underlying pressure remains. A commodity\nwants to collapse toward the lowest sustainable price.</p>\n<p>The phrase obviously inherits its structure from Stewart Brand’s “information wants to be free.” I\nhad always understood Brand’s line mostly as a claim about censorship: information is easy to copy\nand distribute, therefore attempts to contain it eventually fail. But the original argument was more\neconomic. Information wants to be expensive because it is valuable, and free because the cost of\nreproducing and distributing it keeps falling.</p>\n<p>Tokens are in exactly that position. They are becoming a commodity. The expensive part is creating\nthe frontier model; once a capability exists, inference gets cheaper, hardware improves, models get\nsmaller, distillation transfers capability downward, and competitors attack whatever margin remains.\nChinese labs add another source of pressure: whatever American frontier labs spend discovering,\nfirms with lower costs and fewer reasons to protect incumbent margins have every incentive to\nreproduce and commoditize.</p>\n<p>This is why the current argument over slowing AI development is economically interesting. Dario\nAmodei’s recent call to\n<a href=\"https://darioamodei.com/post/we-must-pace-the-frontier\">“pace the frontier”</a> explicitly asks\ngovernments to require common standards and enable coordination among frontier labs; a less\ncharitable reading is that the incumbents are asking the state to protect them from the economics of\ntheir own product. Sam Altman and Elon Musk have publicly supported the proposal, which makes sense:\nall three are vulnerable to the same commoditization pressure.</p>\n<p>The labs spend enormous amounts of energy resisting this process. They differentiate models, create\nswitching costs, build ecosystems, control distribution, restrict access to weights, and try to\npreserve technological leads. They do this for the same reason every producer resists\ncommoditization: tokens are a commodity, and commodities want to be cheap.</p>\n",
      "date_published": "2026-09-14T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/14/commodities-want-to-be-cheap",
      "summary": "Frontier models are expensive to create and cheap to reproduce, so tokens are becoming a commodity. Commodities want to be cheap, and the labs know it.",
      "tags": [
        "essay"
      ],
      "title": "Commodities want to be cheap",
      "url": "https://pau.fm/blog/2026/09/14/commodities-want-to-be-cheap"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<aside><p><strong>Ed. note:</strong> This post was <a href=\"https://sonora.cx/blog/engineering-discipline-in-the-slop-era\">originally published on Sonora</a>.</p></aside><p>Software has entered the Slop Era.</p>\n<p>The cost of producing plausible code has collapsed, but the cost of knowing whether that code\nbelongs in your system has not. Every engineering team using coding agents now trivially generates\nmore code than it can responsibly absorb.</p>\n<p>Slop is code that is locally reasonable but globally careless: the fifth copy of a helper function,\na try/catch block that hides an error from the error reporter, a session-scoped lock behind a\ntransaction pool, a migration that runs cleanly yet undermines the application’s security model.\nSlop survives casual review because every line looks plausible, but introduces risk because it lacks\nknowledge of the system around it.</p>\n<p>These problems existed before the Cambrian explosion of code generation. The naive response then, as\nnow, was to demand more careful review. That does not scale. <strong>The answer to the Slop Era is not\nmore careful engineers, but engineering discipline made executable.</strong></p>\n<p>By discipline, I don’t mean heroics, process theater, or a longer approval chain. I mean taking the\njudgment a strong engineer carries in their head and putting it into the environment, dependency\ngraph, lint rules, types, tests, comments, and deployment path. The system should supply missing\ncontext, reject known mistakes, contain the blast radius, and preserve the reason behind old\ndecisions.</p>\n<p>The answer to the slop problem ends up looking a lot like what the best engineering teams have been\ndoing for the past decade: turning convention into constraints. This is not a special set of\nguardrails for agents - what’s good for humans is good for machines. Both make locally plausible\nmistakes when they lack context; the difference in the Slop Era is that agents hit these problems\noften enough to make the return on fixing them impossible to ignore.</p>\n<p>Sonora tests whether that discipline can keep up with output. Over the last sixty days, I merged 257\npull requests into <code>main</code>, over six per day on working days. Across the team, 761 PRs landed, an\naverage of sixteen per shipping day, with a peak of 34. We are a team of five engineers. I remember\nentire months at a prior startup, with a much larger team, that did not ship close to what we now do\nin a day.</p>\n<p>Six PRs in a day would be meaningless if they were six typo fixes. Sonora’s codebase is barely a\nyear old and already runs close to 1 million lines of TypeScript across thirty packages, and a\nsingle PR routinely spans a migration, worker jobs, UI, tests, and docs. That is the real stress\ntest: can the system absorb a change that wide without requiring me to inspect every boundary by\nhand? I’m not typing six times faster. The repository is doing more of the remembering, checking,\nand rejecting before the change reaches me.</p>\n<p>The governing idea is <strong>make it cheap to be wrong</strong>. AI did not invent this discipline, but it\nchanged the discount rate: work that used to pay back once a sprint now pays back dozens of times a\nday.</p>\n<h2 id=\"whats-good-for-humans-is-good-for-machines\">What’s good for humans is good for machines</h2>\n<p>Fast feedback. One command that does the thing. Errors that explain what to do next. Comments that\nrecord why an obvious alternative failed. These were good engineering practices before coding\nagents, but agents make their value visible because they exercise the system far more often.</p>\n<p>Start with the development environment. An agent will confidently reason from whatever world you\ngive it. If that world has the wrong port, a drifting Node version, or somebody else’s database, a\ngreen test can certify fiction.</p>\n<p>Our environment is a <a href=\"https://nixos.org\">Nix</a> flake. Node, Postgres, Redis,\n<a href=\"https://www.pulumi.com\">Pulumi</a>, gcloud, kubectl, <a href=\"https://www.shellcheck.net\">ShellCheck</a>, and the\nrest of the toolchain are pinned. Entering the repo starts its services. Ports are allocated per\ncheckout and exported as environment variables, so multiple copies of Sonora can run side by side\nwithout sharing a database or guessing who owns port 5432.</p>\n<p>This is what loading the repo looks like:</p>\n<div class=\"expressive-code\"><link rel=\"stylesheet\" href=\"https://pau.fm/_astro/ec.snl5l.css\"><script type=\"module\" src=\"https://pau.fm/_astro/ec.g1fg5.js\"></script><pre data-language=\"text\"><code><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">╭──────────────────────────────────────────────────────────────╮</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│ 🦊  sonora dev                                               │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│                                                              │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│   ▴ Postgres   127.0.0.1:5432                                │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│   ▴ Redis      127.0.0.1:6379                                │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│   ▴ Mailpit    127.0.0.1:1025  →  http://127.0.0.1:8025      │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│   ▴ App        http://app.sonora.localhost:3000              │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│   ▴ Worktrees  grove · grove --once snapshot                 │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│   ▴ Turbo      shared · read-only                            │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│                                                              │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">│ ✓ ready to build  ·  run pnpm dev                            │</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#e6edf3;--1:#1f2328\">╰──────────────────────────────────────────────────────────────╯</span></div></div></code></pre></div>\n<p>The banner is for humans, and we suppress it in non-interactive shells, but the port assignments are\nstill exported as <code>$PGPORT</code>, <code>$REDISPORT</code>, and <code>$PORT</code>. Our <a href=\"https://agents.md\"><code>AGENTS.md</code></a> tells\ncoding agents to use those variables instead of guessing the defaults. Before every shell command, a\nhook puts the agent inside <code>nix develop</code>, so when it runs <code>pnpm test</code>, it gets the same pinned Node\nand dependencies I do, along with that worktree’s own Postgres.</p>\n<p>Humans are pretty good at noticing when an environment is wrong. We see an unexpected port or\nversion, swear a little, and adjust. But agents are more likely to accept what they see as fact and\nbuild the next ten steps on top of it. A passing test in the wrong environment is worse than a\nfailing one because it sends the rest of the work in the wrong direction.</p>\n<p>This is also why I have become more opinionated about monorepos. An agent can work only with what\nexists in its checkout. Put the worker in one repository and the app in another, and any change that\nspans both becomes a coordination problem. Humans compensate with memory, Slack, and meetings, while\nagents guess. Neither is a good use of time.</p>\n<p>Sonora keeps the application, worker, database package, infrastructure, documentation, supporting\ntools, and marketing site in one repository. Even this post is a Markdown file in the same repo. The\napp, worker, database package, and Pulumi infrastructure are all TypeScript, so changes across those\nboundaries stay in one type system. One recent change added cost visibility to workflows. It touched\n27 files across a database migration, worker code, pricing logic and tests, the workflow builder,\nthe credits settings page, and two architecture docs. It was one product change, so it was one PR. I\ndid not need to coordinate three repositories or remember which part had shipped.</p>\n<p>Keeping everything together does not mean every change runs every test or deploys every application.\n<a href=\"https://turborepo.com\">Turborepo</a> already knows the dependency graph. CI uses it to determine which\npackages need checking, and the deployment pipeline uses it to determine whether production code\nchanged. Editing this post republishes the marketing site and stops there. Without that filtering,\nchanging this sentence would run the entire application suite. We lean on the dependency graph to\nkeep changes isolated and checks targeted.</p>\n<h2 id=\"conventions-are-stronger-as-constraints\">Conventions are stronger as constraints</h2>\n<p>Conventions aren’t actionable in their natural state. They’re just a shared understanding; a piece\nof advice. Teams have spent a decade trying to make a codebase read as though one person wrote it.\nThat consistency now has a functional purpose: agents learn from the nearest example. Engineering\ndiscipline becomes executable when violating the convention produces immediate, specific feedback.\nThe job is to move judgment out of reviewers’ heads and into mechanisms that can reject a change. We\nuse this ladder:</p>\n<p><img src=\"https://pau.fm/images/blog/engineering-discipline-inline.webp\" alt=\"Five paper markers on a folded landscape, each carved with a shape and labelled prompt, skill, hook, lint rule, and type, with a fox on the path running left to right\"></p>\n<p>A prompt is advice for one task. A skill is reusable advice. A hook runs without being asked. A lint\nrule rejects a bad pattern. A type makes the invalid state unrepresentable. When the same correction\n<a href=\"https://blog.codinghorror.com/rule-of-three/\">appears three times in review</a>, I try to move it one\nrung to the right.</p>\n<p>Every coding agent starts by reading <code>AGENTS.md</code>. It is the standing prompt for the repository, a\nway to give every agent the same starting context, but still only the first rung on that ladder.\nOurs is 356 lines, most of them recording decisions we do not want to relitigate: <code>/api/*</code> is\nreserved for a future public API; every form prevents duplicate submissions; use this primitive\nrather than inventing another one.</p>\n<p>As a prompt, <code>AGENTS.md</code> can explain what to do and why. But it can’t stop anyone from doing the\nopposite. For decisions we care enough about, the build says no. We have 22 custom\n<a href=\"https://oxc.rs/docs/guide/usage/linter.html\">oxlint</a> rules in a package called <code>vibes</code>, including:</p>\n<ul>\n<li><code>no-catch-500</code> stops a handler from swallowing an error and hiding it from Sentry.</li>\n<li><code>require-job-group-in-loop</code> catches queue fan-outs that could let one tenant monopolize the\nworkers.</li>\n<li><code>no-session-advisory-lock</code> rejects session-scoped Postgres locks because we run\n<a href=\"https://www.pgbouncer.org\">PgBouncer</a> in transaction mode.</li>\n<li><code>no-as-unknown-as</code> rejects the <code>as unknown as T</code> double assertion, which disables type checking\nwithout narrowing anything.</li>\n</ul>\n<p><code>any</code> is also an error outside tests. Both are ways to opt out of the type checking we chose\nTypeScript for. The linter does not care whether the code came from me, a teammate, or an agent. It\nfails in the same place and explains both the danger and what to do instead. Once we have paid to\nlearn a rule, enforcing it should not depend on the next reviewer remembering it.</p>\n<p>Exceptions are explicit. A one-line suppression names the rule and gives the reason:\n<code>// oxlint-disable-next-line jsx-a11y/no-autofocus -- intentional focus on dialog open</code>.</p>\n<p>The most common signature of machine-generated slop is entropy. The fifth copy of a helper function\ncan be correct and still make the system worse because it introduces more maintenance cost, CI\nexecution time and surface area for drift. Yet no human reviewer remembers every similar helper\neither. Our <code>vibedupes</code> utility, built on <a href=\"https://github.com/mizchi/similarity\">similarity-ts</a>,\ncompares function syntax trees to detect functionally duplicate code. It checks changed functions on\neach PR and runs a full-repository scan weekly. We tuned it until a match meant “fork” rather than\n“similar shape,” because an ignored check is worse than no check at all.</p>\n<p>That tuning threshold is still a judgment call, and the next person needs to know why we drew the\nline at 90% similarity. Which brings me to comments.</p>\n<p>I owe comments a mea culpa. For most of my career, I believed good code should explain itself and\nthat a long comment was evidence of a bad abstraction. I still believe that when the comment merely\nnarrates what the code does. But no function name can tell you why four test shards are faster than\neight in this repository, why a threshold is <code>0.90</code> instead of <code>0.85</code>, or which production incident\nmade a particular constraint necessary. Some of the comments I value most today are longer than\nyounger me would have tolerated because they preserve history, not mechanics.</p>\n<p>There is no clever solution to comment rot. We make comments local, concrete, and falsifiable. A\nuseful comment sits beside the decision and names the benchmark, incident, or rejected alternative\nbehind it. Whenever the constraint can be expressed as a test, lint rule, or type, that mechanism\ndoes the enforcing; the comment explains why it exists. “Be careful here” will rot. “Four shards,\nnot eight, because the critical path moves below 56 seconds” gives the next engineer something they\ncan verify.</p>\n<p>The threshold in <code>vibedupes</code> has exactly that kind of comment:</p>\n<!-- prettier-ignore -->\n<div class=\"expressive-code\"><pre data-language=\"ts\"><code><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#8B949E;--1:#616972\">// Tuned against this repo: at 0.85/5 lines the report fills with `toNumber` and</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#8B949E;--1:#616972\">// one-line chart validators. Raising both leaves the forked-function findings.</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#8B949E;--1:#616972\">// 0.90 is where the pairs still read as copies rather than as functions that</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#8B949E;--1:#616972\">// merely share a shape — every fork family we set out to catch scores above it</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#8B949E;--1:#616972\">// (the three rate limiters are 99.33%, the four Snowflake JWT builders 99.59%).</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#FF7B72;--1:#CA212D\">const</span><span style=\"--0:#E6EDF3;--1:#1F2328\"> </span><span style=\"--0:#79C0FF;--1:#0550AE\">DEFAULT_THRESHOLD</span><span style=\"--0:#E6EDF3;--1:#1F2328\"> </span><span style=\"--0:#FF7B72;--1:#CA212D\">=</span><span style=\"--0:#E6EDF3;--1:#1F2328\"> </span><span style=\"--0:#A5D6FF;--1:#0A3069\">\"0.90\"</span><span style=\"--0:#E6EDF3;--1:#1F2328\">;</span></div></div><div class=\"ec-line\"><div class=\"code\"><span style=\"--0:#FF7B72;--1:#CA212D\">const</span><span style=\"--0:#E6EDF3;--1:#1F2328\"> </span><span style=\"--0:#79C0FF;--1:#0550AE\">DEFAULT_MIN_LINES</span><span style=\"--0:#E6EDF3;--1:#1F2328\"> </span><span style=\"--0:#FF7B72;--1:#CA212D\">=</span><span style=\"--0:#E6EDF3;--1:#1F2328\"> </span><span style=\"--0:#A5D6FF;--1:#0A3069\">\"15\"</span><span style=\"--0:#E6EDF3;--1:#1F2328\">;</span></div></div></code></pre></div>\n<p>Without that history, lowering the threshold looks prudent. A new engineer and an agent share the\nsame failure mode here: locally sensible reasoning without the history. The comment puts the history\nat the one place both are guaranteed to see it.</p>\n<p>Documentation works the same way. It lives beside the code, and source comments link to it by path.\nThe job-group rule points to the concurrency architecture doc. A person or agent can follow that\nlink while working instead of guessing what somebody once wrote in a wiki. Context that cannot be\nreached from the checkout is not dependable context.</p>\n<h2 id=\"fast-feedback-is-a-safety-feature\">Fast feedback is a safety feature</h2>\n<p>Executable discipline has to be fast. When feedback is slow, people batch changes and work around\nchecks; agents burn cycles or form wrong conclusions. A high-output repository must say no quickly,\nspecifically, and as close as possible to the mistake. We make checks fast not by dropping them, but\nby testing the narrowest set of things that a change could break, and by not recomputing answers we\nalready trust.</p>\n<p><code>pnpm check</code> is one command. CI uses Turborepo’s <code>--filter='...[origin/main]'</code> to test only affected\npackages rather than the entire monorepo. A marketing-site change does not wait for more than a\nthousand application test files. Locally, <code>pnpm check:min</code> applies the same idea to the current\ndiff. It asks Turborepo which packages the change can affect, then lets Vitest follow the module\ngraph to the relevant tests. Changing a single utility might run only one test file, taking just a\nsecond or two, while changing a root configuration file will run the whole suite. Skipping\nunnecessary work is the programmer’s kind of laziness.</p>\n<p>When the application suite does need to run, its thousand-plus test files are sharded and run in\nparallel across multiple jobs, each with its own Postgres instance. This gives us parallelism\nwithout shared-database contention or cross-test interference. Nine out of ten PRs are green or red\nin under six minutes; the median CI run takes about three and a half minutes.</p>\n<p>The other large win is refusing to repeat work. CI publishes successful task results to a shared\nTurborepo cache that developers and agents can read but not write. A passing check from my Mac\nshould not be allowed to convince Linux CI that it can skip the same check. After every merge,\nanother workflow warms the cache for the exact tree on <code>main</code>, so pulling an unchanged main branch\nand running <code>pnpm check</code> takes about a second.</p>\n<p>Fast feedback keeps changes small. Fast checks catch the failures we know how to test. For\neverything else, we bias toward changes we can reverse. Feature flags make large changes reversible\nin production. Every feature flag records the date it entered the registry; after thirty days, an\nexperiment is marked stale unless it is explicitly permanent, a category reserved for entitlements\nand policy controls. The default assumption is ship it, judge it, then delete it or make it the\ndefault.</p>\n<p>We apply the same idea to schema changes: add a nullable column, backfill it, then enforce\n<code>NOT NULL</code>. We use two or three PRs even when the whole change would compile as one. Each step is\neasier to test, review, pause, and reverse.</p>\n<p><strong>The decomposition that makes a change safe is the same decomposition that makes it\nparallelizable.</strong> A change small enough to reason about is small enough to hand to an agent, review\nin minutes, and revert without ceremony. The careful version is the one that moves faster.</p>\n<h2 id=\"every-pull-request-gets-an-automated-review\">Every pull request gets an automated review</h2>\n<p>Static checks work best when we already know how to name the failure. There is still a middle ground\nbetween “the compiler rejects it” and “a human happens to notice”: a change can compile, pass every\ntest, and still leave a stale assumption somewhere else in the repository. That is where automated\ncode review earns its keep.</p>\n<p><a href=\"https://www.greptile.com/\">Greptile</a> reviews every Sonora pull request against the rest of the\nrepository. It reads <code>AGENTS.md</code>, architecture docs, and rules retained from previous reviews\ninstead of treating the diff as its whole world. When a new picker component called the React Router\n<code>fetcher.submit</code> from native buttons, it checked the implementation against the form-submission\nrules in <code>AGENTS.md</code> and caught that duplicate submissions were not prevented. The next commit moved\nthose rows to our shared <code>SubmitButton</code> with an explicit pending state. In another PR, it connected\nan additional database checkout to a previous, documented pool-exhaustion incident and told us to\nshare an existing transaction. Both are mistakes a line-by-line reading of the diff could easily\nmiss.</p>\n<p>It also gets things wrong. It once complained that 200ms and 300ms animation durations bypassed our\nmotion vocabulary when those were exactly the values the vocabulary prescribed. We replied, and it\nwithdrew the comment. Automated review is a second reader, not an oracle. A finding has to describe\na real failure mode well enough for us to verify it.</p>\n<p>Review quality falls when a PR bundles unrelated work. Keeping each change cohesive gives Greptile,\nand the human after it, less irrelevant context to sort through.</p>\n<p>A good automated-review finding should eventually make itself obsolete. If it catches the same class\nof mistake more than once, that is evidence to move the rule to the right on the ladder: from\nrepository guidance to a hook, lint rule, or type. Automated review is useful in the gap, while the\nrule still depends on context we have not yet learned how to encode.</p>\n<h2 id=\"make-the-repository-remember\">Make the repository remember</h2>\n<p>Engineering discipline compounds when each failure changes the path of the next change. On August 3,\nroughly 9,500 ungrouped sibling jobs contended on one database row. They exhausted the statement\ntimeout, and their retained error text filled <a href=\"https://valkey.io\">Valkey</a> until workers could no\nlonger boot.</p>\n<p>The postmortem changed the system in several places. The fan-out path got a grouping primitive. The\nconcurrency behavior got an architecture doc. <code>require-job-group-in-loop</code> now rejects the pattern\nthat caused the incident, and its header comment records the date and consequence. The next engineer\nsees that history while writing the loop, not after opening another postmortem.</p>\n<p>That is discipline made executable: the incident changed the queue primitive, the documentation, and\nthe set of programs the linter will accept. We paid for the lesson once. Not every failure deserves\na lint rule; the durable response may be a type, a test, a different queue topology, or a smaller\ndeployment. “Be more careful next time” is not durable.</p>\n<p>That is how my job has changed. The repository settles formatting, imports, and approved primitives\nbefore a PR reaches me, leaving review for the spec and intent. The more important question is why\nthe repository needed me to catch something.</p>\n<p>Six merged PRs a day is not the target. It is a side effect of making confidence cheaper. The metric\nI care about is the time between “I know what I want” and “it is in production, and I am not\nnervous.”</p>\n<p>The Slop Era will reward the team with the best engineering discipline, not the best prompt. What’s\ngood for humans is good for machines: trustworthy evidence, explicit decisions, fast feedback,\nisolated work, and a codebase that remembers why. Machines do not need a separate discipline. They\nneed ours, made executable. Build the discipline into the system.</p>\n<p><em>Thanks to Gabriel Al-Harbi and Max Schwenk for providing feedback on drafts of this essay.</em></p>\n",
      "date_published": "2026-09-10T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/10/engineering-discipline-in-the-slop-era",
      "summary": "Coding agents produce more code than your team can safely absorb. The answer is not more careful engineers: it is engineering discipline made executable, for humans and machines.",
      "tags": [
        "essay"
      ],
      "title": "Constraint over convention",
      "url": "https://pau.fm/blog/2026/09/10/engineering-discipline-in-the-slop-era"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Paying for Spotify subsidizes Europe for being awful at technology.</p>\n",
      "date_published": "2026-09-09T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/09/subsidizing-europe",
      "summary": "Paying for Spotify subsidizes Europe for being awful at technology.",
      "tags": [
        "short"
      ],
      "title": "Paying for Spotify subsidizes Europe for being awful at technology.",
      "url": "https://pau.fm/blog/2026/09/09/subsidizing-europe"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p><a href=\"https://openai.com/index/navier-stokes-solution/\">OpenAI’s Millennium Problem result</a> demonstrates\nthat AI is advancing fastest in domains with strong validation. In math, a proposed result can be\nchecked, and formal proofs make that validation nearly perfect. The model can search an enormous\nspace of possible constructions, discard failures, and eventually produce something provably\ncorrect. The same pattern explains progress in coding (the Bun Rust rewrite), games (unambiguous win\nor loss), and other domains where there is a cheap, reliable signal for success.</p>\n<p>But validation is not the same thing as understanding. The actual result of Navier–Stokes is\neffectively inconsequential.<sup><a href=\"#user-content-fn-result\" id=\"user-content-fnref-result\" data-footnote-ref aria-describedby=\"footnote-label\">1</a></sup> We care because trying to answer the question might produce a\ntheory of why solutions behave as they do. Mathematics, like computing, advances by building\nabstractions that compress many particular observations into a smaller number of concepts: the right\ndefinition, invariant, equivalence, or structure suddenly makes a whole family of results\nintelligible.</p>\n<p>This is not what happened here. OpenAI spent an enormous amount on compute, reportedly millions,\npursuing a perfectly verifiable objective, and its model searched its way to a gigantic, bespoke\nproof without discovering much reusable conceptual structure at all. In fact, earlier work reduced\nthe search space enough to make the entire project tractable.<sup><a href=\"#user-content-fn-credit\" id=\"user-content-fnref-credit\" data-footnote-ref aria-describedby=\"footnote-label\">2</a></sup> The result is still\nknowledge—we know something we did not know before—but it may be a strange kind of knowledge:\nexpensive to produce, difficult to understand, and not obviously useful for making future problems\nsubstantially easier.</p>\n<p>AI seems superhuman in domains with verifiers. It’s far from clear whether it can build useful\nabstractions where no such verifier exists. Human intellectual progress is not usually a sequence of\nisolated correct answers. We invent models that make the world simpler to reason about. If AI can\nreliably discover concepts and theories that reorganize entire fields, it will represent a much\ndeeper form of intelligence than producing arbitrarily difficult, formally validated results.</p>\n<section data-footnotes class=\"footnotes\"><h2 class=\"sr-only\" id=\"footnote-label\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-result\">\n<p>We do not really care whether the answer was “true” or, as it turned out, “false.” <a href=\"#user-content-fnref-result\" data-footnote-backref=\"\" aria-label=\"Back to reference 1\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n<li id=\"user-content-fn-credit\">\n<p>That earlier work came from several human mathematicians whom OpenAI decided not to credit. <a href=\"#user-content-fnref-credit\" data-footnote-backref=\"\" aria-label=\"Back to reference 2\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2026-09-08T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/08/correctness-is-not-intelligence",
      "summary": "AI excels where answers can be verified, but real intellectual progress depends on abstractions that make the world easier to understand.",
      "tags": [
        "essay"
      ],
      "title": "Correctness is not intelligence",
      "url": "https://pau.fm/blog/2026/09/08/correctness-is-not-intelligence"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I don’t have code access yet. But I have reason to believe they’re on Ruby 1.9.3 and Rails 2.3.</p>\n",
      "date_published": "2026-09-03T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/09/03/ruby-1-9-3",
      "summary": "I don’t have code access yet. But I have reason to believe they’re on Ruby 1.9.3 and Rails 2.3.",
      "tags": [
        "short"
      ],
      "title": "I don’t have code access yet.",
      "url": "https://pau.fm/blog/2026/09/03/ruby-1-9-3"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I asked Claude to do something and it produced a twelve-week plan. I asked again later. Two and a\nhalf to three and a half hours.</p>\n",
      "date_published": "2026-08-28T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/08/28/claude-estimates",
      "summary": "I asked Claude to do something and it produced a twelve-week plan. I asked again later. Two and a half to three and a half hours.",
      "tags": [
        "short"
      ],
      "title": "I asked Claude to do something and it produced a twelve-week plan.",
      "url": "https://pau.fm/blog/2026/08/28/claude-estimates"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Our new quality person believes quality is everyone’s responsibility. I, too, believe that my job is\neveryone else’s responsibility.</p>\n",
      "date_published": "2026-08-21T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/08/21/quality-is-everyones-responsibility",
      "summary": "Our new quality person believes quality is everyone’s responsibility. I, too, believe that my job is everyone else’s responsibility.",
      "tags": [
        "short"
      ],
      "title": "Our new quality person believes quality is everyone’s responsibility.",
      "url": "https://pau.fm/blog/2026/08/21/quality-is-everyones-responsibility"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Engineers at Percy got a rubber stamp for their one-year anniversary gift. Perfect.</p>\n",
      "date_published": "2026-08-15T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/08/15/engineer-anniversary-gift",
      "summary": "Engineers at Percy got a rubber stamp for their one-year anniversary gift. Perfect.",
      "tags": [
        "short"
      ],
      "title": "Engineers at Percy got a rubber stamp for their one-year anniversary gift.",
      "url": "https://pau.fm/blog/2026/08/15/engineer-anniversary-gift"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>If you ever used data structures and algorithms interviews to hire software engineers, your fraud is\nnow exposed.</p>\n<p>DSA interviews were never a good proxy for engineering, and this was blindingly obvious to anyone\nwho had ever shipped software. They were a crutch: a way to avoid the hard work of judging whether a\nperson actually brought useful skills to a team. Google designed them for a specific problem—hiring\nengineers at enormous scale to work on enormous-scale problems. You copied the visible part, and\nLeetCode became a substitute for any critical thinking you might otherwise have had to do.</p>\n<p>Candidates responded rationally, of course. They studied the test. An entire curriculum emerged\naround problems most engineers rarely encountered in actual work and now, increasingly, never will.\nSuccess reflected time, coaching, pedigree, and familiarity with the ritual at least as much as\nengineering ability.</p>\n<p>The fact that you could “study” for the interview should have made the farce obvious. Instead,\nprogrammers wrapped the whole thing in meritocratic language and enjoyed the smug satisfaction of\nadmitting only people willing and able to jump through their inane hoops into the little cabal.</p>\n<p>How many lines of code have you written in the past year? If it’s more than zero, ask yourself why.\nThe valuable work was already problem framing, systems understanding, judgment, verification, and\ntradeoffs. Now that is increasingly the whole job. Why are you hiring a human based on their ability\nto perform a task that machines have effectively solved?</p>\n<p>If you are still running DSA screens in 2026, you are admitting that you never had a serious theory\nof how to hire engineers in the first place.</p>\n<p><em>Disclaimer: as a hiring manager, I have never once given a candidate a DSA interview, because I\nalways thought they were incredibly stupid. I was right all along and now there is incontrovertible\nproof.</em></p>\n",
      "date_published": "2026-08-14T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/08/14/if-you-ever-used-dsa-interviews-your-fraud-is-now-exposed",
      "summary": "DSA interviews replaced judgment with ritual. Now that machines have solved the ritual, companies still using it are exposing that they never knew how to hire engineers.",
      "tags": [
        "essay"
      ],
      "title": "If you ever used DSA interviews, your fraud is now exposed",
      "url": "https://pau.fm/blog/2026/08/14/if-you-ever-used-dsa-interviews-your-fraud-is-now-exposed"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Two tech workers live together long enough and their sprints start to synchronize.</p>\n",
      "date_published": "2026-08-08T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/08/08/sprints-synchronize",
      "summary": "Two tech workers live together long enough and their sprints start to synchronize.",
      "tags": [
        "short"
      ],
      "title": "Two tech workers live together long enough and their sprints start to…",
      "url": "https://pau.fm/blog/2026/08/08/sprints-synchronize"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I like to imagine these huts are New York’s solution to the housing crisis.</p>\n<div class=\"short-media\" data-count=\"1\" data-orientation=\"portrait\" style=\"--short-media-aspect: 1350 / 1800; --short-media-intrinsic-width: 1350px\"><a class=\"short-media__item\" data-pswp-height=\"1800\" data-pswp-width=\"1350\" href=\"https://pau.fm/images/blog/new-york-housing-solution.webp\"><img src=\"https://pau.fm/images/blog/new-york-housing-solution.webp\" alt=\"Two rooftop water towers outside a New York apartment window\" decoding=\"async\" height=\"1800\" loading=\"lazy\" width=\"1350\"></a></div>\n",
      "date_published": "2026-08-02T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/08/02/new-york-housing-solution",
      "summary": "I like to imagine these huts are New York’s solution to the housing crisis.",
      "tags": [
        "short"
      ],
      "title": "I like to imagine these huts are New York’s solution to the housing crisis.",
      "url": "https://pau.fm/blog/2026/08/02/new-york-housing-solution"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>At some point, “tech podcast” came to mean “Android phone show.” Specifically, shitty Android phones\nyou’ll never buy.</p>\n",
      "date_published": "2026-07-26T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/07/26/tech-podcasts",
      "summary": "At some point, “tech podcast” came to mean “Android phone show.” Specifically, shitty Android phones you’ll never buy.",
      "tags": [
        "short"
      ],
      "title": "At some point, “tech podcast” came to mean “Android phone show.” Specifically…",
      "url": "https://pau.fm/blog/2026/07/26/tech-podcasts"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Imagine if engineers pretended to be salespeople. They would write random sales pitches they stole\nfrom SEO blogspam, insist they be emailed to customers, then hold a mandatory sales hackathon twice\na year.</p>\n",
      "date_published": "2026-07-20T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/07/20/mandatory-sales-hackathon",
      "summary": "Imagine if engineers pretended to be salespeople. They would write random sales pitches they stole from SEO blogspam, insist they be emailed to customers, then hold a mandatory sales hackathon twice a year.",
      "tags": [
        "short"
      ],
      "title": "Imagine if engineers pretended to be salespeople.",
      "url": "https://pau.fm/blog/2026/07/20/mandatory-sales-hackathon"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Python has load-bearing whitespace.</p>\n",
      "date_published": "2026-07-13T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/07/13/load-bearing-whitespace",
      "summary": "Python has load-bearing whitespace.",
      "tags": [
        "short"
      ],
      "title": "Python has load-bearing whitespace.",
      "url": "https://pau.fm/blog/2026/07/13/load-bearing-whitespace"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Love hack weeks. Very inspiring to half-ass something you know will get thrown away.</p>\n",
      "date_published": "2026-07-07T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/07/07/hack-weeks",
      "summary": "Love hack weeks. Very inspiring to half-ass something you know will get thrown away.",
      "tags": [
        "short"
      ],
      "title": "Love hack weeks.",
      "url": "https://pau.fm/blog/2026/07/07/hack-weeks"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>The received wisdom among enlightened software engineers is that lines of code is a terrible measure\nof engineering productivity. Good engineers produce less code, not more: they delete things, find\nabstractions, avoid unnecessary systems, and spend large amounts of time on work that is not visible\nin diffs. LoC mostly rewards visible activity rather than engineering value.</p>\n<p>Coding agents change this in an uncomfortable way. The scarce resource is no longer typing code; it\nis specifying, directing, validating, and integrating work. If one engineer can use agents to ship\nfive integrations, migrate a subsystem, add tests, fix a dozen bugs, and clean up old infrastructure\nwhile another ships one feature, the first engineer probably did produce more. The fact that this\noutput materializes as a much larger quantity of code is no longer incidental. My view is that LoC\nis now a rough proxy for how much implementation surface an engineer can successfully orchestrate.</p>\n<p>It is probably still not a sufficient metric. Generated code can be redundant, brittle, or wrong,\nand the highest-leverage work still happens one level up: choosing the right architecture, defining\nboundaries, deciding what not to build, and determining whether any of this creates business value.\nBut I think that there is now substantial signal where previously there was not. In an agentic\nengineering environment, once quality and system-level outcomes are held constant, “how much code\ndid this person ship?” is suddenly a much more reasonable question.</p>\n",
      "date_published": "2026-07-06T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/07/06/lines-of-code-means-something-now",
      "summary": "When coding agents make typing cheap, code volume becomes a rough measure of how much implementation work an engineer can successfully orchestrate.",
      "tags": [
        "essay"
      ],
      "title": "Lines of code means something now",
      "url": "https://pau.fm/blog/2026/07/06/lines-of-code-means-something-now"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Claude gave me a four-week implementation plan. I knocked it out in two days.</p>\n",
      "date_published": "2026-06-30T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/06/30/four-week-implementation-plan",
      "summary": "Claude gave me a four-week implementation plan. I knocked it out in two days.",
      "tags": [
        "short"
      ],
      "title": "Claude gave me a four-week implementation plan.",
      "url": "https://pau.fm/blog/2026/06/30/four-week-implementation-plan"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I found a venture capitalist with his SAT score on his LinkedIn profile.</p>\n",
      "date_published": "2026-06-24T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/06/24/sat-score-on-linkedin",
      "summary": "I found a venture capitalist with his SAT score on his LinkedIn profile.",
      "tags": [
        "short"
      ],
      "title": "I found a venture capitalist with his SAT score on his LinkedIn profile.",
      "url": "https://pau.fm/blog/2026/06/24/sat-score-on-linkedin"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I asked Google to set a two-minute, thirty-second timer. “Okay, I’ve started a two-minute timer\ncalled ‘30 second.’”</p>\n",
      "date_published": "2026-06-17T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/06/17/google-timer",
      "summary": "I asked Google to set a two-minute, thirty-second timer. “Okay, I’ve started a two-minute timer called ‘30 second.’”",
      "tags": [
        "short"
      ],
      "title": "I asked Google to set a two-minute, thirty-second timer.",
      "url": "https://pau.fm/blog/2026/06/17/google-timer"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>The owner of my neighborhood teriyaki restaurant is wearing a GitLab T-shirt. Invest.</p>\n",
      "date_published": "2026-06-11T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/06/11/gitlab-teriyaki",
      "summary": "The owner of my neighborhood teriyaki restaurant is wearing a GitLab T-shirt. Invest.",
      "tags": [
        "short"
      ],
      "title": "The owner of my neighborhood teriyaki restaurant is wearing a GitLab T-shirt.",
      "url": "https://pau.fm/blog/2026/06/11/gitlab-teriyaki"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Most software has historically been a database with a user interface. The user does the actual work\n— researching, comparing, deciding, remembering — and the software records the output.</p>\n<p>The first generation of AI products mostly preserved this architecture and added a chat box. Instead\nof tracking wines, ask GPT which wine to buy. Instead of maintaining a travel spreadsheet, ask it\nwhere to stay. This is useful, but it is not much of a product idea.</p>\n<p>Clay is interesting because it suggests a different architecture. Airtable gives you rows and\ncolumns and lets you fill in the cells. Clay lets you describe what a column means and has an agent\nfill it in for every row. A task that previously required researching 500 companies, applying the\nsame fuzzy judgment 500 times, and manually recording 500 answers becomes a declarative operation:\ndefine the judgment once, then curate the results. The model is not bolted onto the database; it\nchanges the relationship between the user and the database. Rows are items, columns are jobs, and\ncells are work.</p>\n<p>This is roughly the architecture we are building at Sonora. Customer software traditionally stores\naccounts, tickets, calls, notes, product usage, and CRM fields, while humans are still responsible\nfor synthesizing all of it. Is this account actually at risk? Has this problem happened before? Did\nthe champion leave? Is this an escalation or just noise? Which customers are asking for the same\nthing?</p>\n<p>Those are repeated judgments over a set of customers. Sonora turns them into work the system can\nperform continuously across the customer base, using the underlying calls, tickets, email, CRM, and\nproduct data as its substrate. The user defines what matters, corrects the system when necessary,\nand increasingly curates the output instead of manually producing it.</p>\n<p>We celebrated Sonora reaching general availability tonight. A GA party is a chance to stop long\nenough to notice what the team has built, and what we celebrated was not a database with a chatbot\nattached. It was an engine that can do this work across every customer, continuously. There is much\nmore to build, but that shift now feels real.</p>\n<p>The interesting transition in AI software is not database → chatbot. It is database → engine.</p>\n<p>Software used to store the output of human judgment. Increasingly, it can store the method.</p>\n",
      "date_published": "2026-06-10T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/06/10/the-clay-theorem",
      "summary": "The interesting transition in AI software is not database to chatbot. It is database to engine: software that stores the method of human judgment and performs it continuously.",
      "tags": [
        "essay"
      ],
      "title": "The Clay Theorem",
      "url": "https://pau.fm/blog/2026/06/10/the-clay-theorem"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I would describe Rippling’s aesthetic as “boomer chain letter.”</p>\n<div class=\"short-media\" data-count=\"1\" data-orientation=\"portrait\" style=\"--short-media-aspect: 1450 / 1524; --short-media-intrinsic-width: 1450px\"><a class=\"short-media__item\" data-pswp-height=\"1524\" data-pswp-width=\"1450\" href=\"https://pau.fm/images/blog/rippling-boomer-chain-letter.webp\"><img src=\"https://pau.fm/images/blog/rippling-boomer-chain-letter.webp\" alt=\"A Rippling payday graphic with cartoon clouds, a rainbow, and the slogan “Rain, rain, go away. It’s payday!”\" decoding=\"async\" height=\"1524\" loading=\"lazy\" width=\"1450\"></a></div>\n",
      "date_published": "2026-06-04T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/06/04/boomer-chain-letter",
      "summary": "I would describe Rippling’s aesthetic as “boomer chain letter.”",
      "tags": [
        "short"
      ],
      "title": "I would describe Rippling’s aesthetic as “boomer chain letter.”",
      "url": "https://pau.fm/blog/2026/06/04/boomer-chain-letter"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Crypto is the Agile of real life. Fake work invented to enrich a sleazy grifter class.</p>\n",
      "date_published": "2026-05-28T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/05/28/crypto-is-the-agile-of-real-life",
      "summary": "Crypto is the Agile of real life. Fake work invented to enrich a sleazy grifter class.",
      "tags": [
        "short"
      ],
      "title": "Crypto is the Agile of real life.",
      "url": "https://pau.fm/blog/2026/05/28/crypto-is-the-agile-of-real-life"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I love the term “pre-owned.” Like somebody already did the work for you. What a relief! It’s\npre-owned, so I don’t have to worry about it.</p>\n",
      "date_published": "2026-05-22T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/05/22/pre-owned",
      "summary": "I love the term “pre-owned.” Like somebody already did the work for you. What a relief! It’s pre-owned, so I don’t have to worry about it.",
      "tags": [
        "short"
      ],
      "title": "I love the term “pre-owned.” Like somebody already did the work for you.",
      "url": "https://pau.fm/blog/2026/05/22/pre-owned"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>On a long enough timeline, all internet banter is meta-banter.</p>\n",
      "date_published": "2026-05-15T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/05/15/internet-meta-banter",
      "summary": "On a long enough timeline, all internet banter is meta-banter.",
      "tags": [
        "short"
      ],
      "title": "On a long enough timeline, all internet banter is meta-banter.",
      "url": "https://pau.fm/blog/2026/05/15/internet-meta-banter"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>MacRumors is stepping in to help people who don’t know when fall is.</p>\n<div class=\"short-media\" data-count=\"1\" data-orientation=\"landscape\" style=\"--short-media-aspect: 1424 / 166; --short-media-intrinsic-width: 1424px\"><a class=\"short-media__item\" data-pswp-height=\"166\" data-pswp-width=\"1424\" href=\"https://pau.fm/images/blog/macrumors-explains-fall.webp\"><img src=\"https://pau.fm/images/blog/macrumors-explains-fall.webp\" alt=\"MacRumors explaining that fall runs from September to December and usually coincides with Apple’s busiest release period\" decoding=\"async\" height=\"166\" loading=\"lazy\" width=\"1424\"></a></div>\n",
      "date_published": "2026-05-09T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/05/09/macrumors-explains-fall",
      "summary": "MacRumors is stepping in to help people who don’t know when fall is.",
      "tags": [
        "short"
      ],
      "title": "MacRumors is stepping in to help people who don’t know when fall is.",
      "url": "https://pau.fm/blog/2026/05/09/macrumors-explains-fall"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I install really basic shit through Homebrew. Then one day I discover I have 248 packages. Love to\nupgrade <code>aws-sdk-cpp</code>.</p>\n",
      "date_published": "2026-05-02T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/05/02/homebrew-dependencies",
      "summary": "I install really basic shit through Homebrew. Then one day I discover I have 248 packages. Love to upgrade aws-sdk-cpp.",
      "tags": [
        "short"
      ],
      "title": "I install really basic shit through Homebrew.",
      "url": "https://pau.fm/blog/2026/05/02/homebrew-dependencies"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Losing three percent every day has an upside. The absolute value keeps getting smaller.</p>\n",
      "date_published": "2026-04-26T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/04/26/the-good-news-about-a-three-percent-drop",
      "summary": "Losing three percent every day has an upside. The absolute value keeps getting smaller.",
      "tags": [
        "short"
      ],
      "title": "Losing three percent every day has an upside.",
      "url": "https://pau.fm/blog/2026/04/26/the-good-news-about-a-three-percent-drop"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Whenever literally anything happens in the world, I wait for Slack. I’m not okay until I see some VP\npost “it’s okay to take time for yourself.”</p>\n",
      "date_published": "2026-04-19T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/04/19/take-time-for-yourself",
      "summary": "Whenever literally anything happens in the world, I wait for Slack. I’m not okay until I see some VP post “it’s okay to take time for yourself.”",
      "tags": [
        "short"
      ],
      "title": "Whenever literally anything happens in the world, I wait for Slack.",
      "url": "https://pau.fm/blog/2026/04/19/take-time-for-yourself"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Hate your career long enough and AI displacement starts to sound pleasant.</p>\n",
      "date_published": "2026-04-13T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/04/13/quietly-hated-your-career",
      "summary": "Hate your career long enough and AI displacement starts to sound pleasant.",
      "tags": [
        "short"
      ],
      "title": "Hate your career long enough and AI displacement starts to sound pleasant.",
      "url": "https://pau.fm/blog/2026/04/13/quietly-hated-your-career"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Tradeoffs are real. This is an obvious statement, which makes it strange how much political rhetoric\nis organized around denying it.</p>\n<p>Political scientists usually define populism as a conflict between “the pure people” and “the\ncorrupt elite,” while technocracy claims authority through expertise and problem-solving. But the\nimportant policy consequence is simpler: once every problem has a villain, every constraint starts\nlooking like sabotage.\n(<a href=\"https://www.cambridge.org/core/journals/government-and-opposition/article/populist-zeitgeist/2CD34F8B25C4FFF4F322316833DB94B7\">Mudde</a>,\n<a href=\"https://www.cambridge.org/core/journals/american-political-science-review/article/abs/will-vs-reason-the-populist-and-technocratic-forms-of-political-representation-and-their-critique-to-party-government/58444AC41CD6B6A4116F12CFF2585E15\">Caramani</a>)</p>\n<p>On the left, corporate power and the rich are why we cannot have generous public services, high\nwages, cheap housing, and cheap energy all at once. On the right, elites, immigrants, bureaucrats,\nand the deep state are why we cannot have low taxes, secure borders, strong communities, cheap\nhousing, and a booming economy all at once. Different enemies, same promise: if the right people\nwere in charge, nothing would have to give.</p>\n<p>Sometimes the villains are real. They still do not repeal budget constraints, supply constraints, or\nsecond-order effects. Scarcity can be distributed unfairly without being invented by the people who\nbenefit from that distribution.</p>\n<p>Policy is where denying this gets expensive.</p>\n<ul>\n<li>Rent control protects current tenants. It can charge some of that protection to future tenants. A\n<a href=\"https://www.nber.org/papers/w24181\">San Francisco study</a> found that rent control increased\ncovered tenants’ likelihood of remaining at their address by nearly 20 percent, while landlords\nreduced rental housing supply by 15 percent.</li>\n<li>Greater trade integration supported growth and poverty reduction in many developing countries.\nIncreased import competition from China also produced persistent losses in employment and earnings\nin some American labor markets. The beneficiaries and the casualties are both real.\n(<a href=\"https://documents1.worldbank.org/curated/en/726971467989468997/pdf/97607-REPLACEMENT-The-Role-of-Trade-in-Ending-Poverty.pdf\">World Bank</a>,\n<a href=\"https://www.nber.org/papers/w21906\">Autor, Dorn, and Hanson</a>)</li>\n<li>Environmental review catches real harms. It can also delay clean-energy projects for years. A\n<a href=\"https://www.rff.org/publications/reports/how-long-does-it-take-national-environmental-policy-act-timelines-and-outcomes-for-clean-energy-projects/\">recent study</a>\nfound average environmental-impact-statement timelines of 27 months for solar projects and 45\nmonths for wind projects.</li>\n</ul>\n<p>In each case, both things are true. Which matters more is a values question. Pretending one of them\nis not happening is not conviction. It is refusing to do the arithmetic.</p>\n<p>A tradeoff is not an argument against a policy. It is the argument the policy has to answer.</p>\n<p>By a tradeoff sensibility, I do not mean centrism or splitting the difference. Sometimes the right\nanswer is to pay the cost. Sometimes it is to stop paying it. The discipline is to count the cost\nbefore announcing that it is free.</p>\n<p>The obvious objection is correct: “it’s complicated” is also the house style of everyone defending a\nstatus quo that suits them. But there is a tell. Deflection invokes complexity and stops. Analysis\nnames the assumptions, the distributional effects, and the evidence that would change the\nconclusion. Nobody hiding behind complexity volunteers to go through the elasticities.</p>\n<p>The fiscal debates of 2009 and 2021 show the risks in opposite directions. In early 2009,\n<a href=\"https://www.brookings.edu/articles/why-the-stimulus-package-may-be-too-weak-to-fix-the-economy/\">critics warned</a>\nthat the proposed stimulus might be too weak for the size of the recession. In early 2021,\n<a href=\"https://www.brookings.edu/articles/the-macroeconomic-implications-of-bidens-1-9-trillion-fiscal-package/\">economic analyses</a>\nprojected that the American Rescue Plan could push output above its pre-pandemic potential; San\nFrancisco Fed researchers later\n<a href=\"https://www.frbsf.org/research-and-insights/publications/economic-letter/2021/10/is-american-rescue-plan-taking-us-back-to-1960s/\">estimated</a>\nthat it could add about 0.3 percentage points to annual inflation through 2022. Subsequent\n<a href=\"https://www.brookings.edu/articles/what-caused-the-u-s-pandemic-era-inflation/\">research</a>\nattributed the inflation surge to both strong demand and supply disruptions, with their relative\nimportance changing over time.</p>\n<p>You can dispute the estimates. That is the point: the dispute is stated in terms that evidence can\nresolve. Output gaps. Multipliers. Supply constraints. Inflation. Put numbers on them and let\nreality grade the test.</p>\n<p>The real advantage of a tradeoff sensibility is not that it always gets the answer right. It is that\nwhen it gets the answer wrong, the assumptions are visible, the miss has a size, and the model can\nbe updated.</p>\n<p>The anti-tradeoff alternative is emotionally easier. When reality fails to deliver every promised\ngood at once, find another villain.</p>\n<p>Tradeoffs are real. Scarcity is not a conspiracy. A politics that will not say who pays is not\noffering abundance. It is hiding the bill.</p>\n",
      "date_published": "2026-04-11T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/04/11/tradeoffs-are-real",
      "summary": "Political rhetoric promises abundance by hiding the bill. A serious policy argument names the benefits, the costs, and who gets each.",
      "tags": [
        "essay"
      ],
      "title": "Tradeoffs are real",
      "url": "https://pau.fm/blog/2026/04/11/tradeoffs-are-real"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>Americans for Common Cents\n<a href=\"https://publicintegrity.org/politics/saving-the-penny-makes-cents-for-zinc-backed-front-group/\">was funded by the zinc lobby</a>.\nOf course it was.</p>\n",
      "date_published": "2026-04-06T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/04/06/common-cents",
      "summary": "Americans for Common Cents was funded by the zinc lobby. Of course it was.",
      "tags": [
        "short"
      ],
      "title": "Americans for Common Cents was funded by the zinc lobby.",
      "url": "https://pau.fm/blog/2026/04/06/common-cents"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>WeWork replaced its in-app snack-buying software with a generic Square terminal. It was a hundred\ntimes better. This is why you let technology companies make the software.</p>\n",
      "date_published": "2026-03-30T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/03/30/wework-snack-terminal",
      "summary": "WeWork replaced its in-app snack-buying software with a generic Square terminal. It was a hundred times better. This is why you let technology companies make the software.",
      "tags": [
        "short"
      ],
      "title": "WeWork replaced its in-app snack-buying software with a generic Square terminal.",
      "url": "https://pau.fm/blog/2026/03/30/wework-snack-terminal"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>It’s funny when DJs turn every knob while the music is clearly unaffected.</p>\n",
      "date_published": "2026-03-24T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/03/24/dj-knobs",
      "summary": "It’s funny when DJs turn every knob while the music is clearly unaffected.",
      "tags": [
        "short"
      ],
      "title": "It’s funny when DJs turn every knob while the music is clearly unaffected.",
      "url": "https://pau.fm/blog/2026/03/24/dj-knobs"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I’m so market-pilled I don’t think it’s possible to be overpaid. You both agreed to the salary.</p>\n",
      "date_published": "2026-03-17T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/03/17/marketpilled",
      "summary": "I’m so market-pilled I don’t think it’s possible to be overpaid. You both agreed to the salary.",
      "tags": [
        "short"
      ],
      "title": "I’m so market-pilled I don’t think it’s possible to be overpaid.",
      "url": "https://pau.fm/blog/2026/03/17/marketpilled"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>htmx has a useful idea called\n<a href=\"https://htmx.org/essays/locality-of-behaviour/\">“locality of behavior”</a>: the behavior of a piece of\ncode should be obvious from looking at that piece of code. A button that says <code>hx-get=\"/clicked\"</code>\ntells you what clicking it does. You do not need to find the event listener, trace a dispatch into a\nstore, inspect some middleware, and discover that a plugin registered three directories away is what\nactually makes the request.</p>\n<p>This is usually framed as a principle for humans. Local behavior is easier to understand because\nless of the program has to fit in your head at once. But coding agents make the idea more\ninteresting, not less. An agent does not begin a task with a mature mental model of the repository.\nIt constructs one by searching, opening files, and deciding which clues deserve another search.\nEvery jump is both a context cost and another chance to miss the mechanism entirely.</p>\n<p>Separation of concerns is often implemented as separation by technical category: components over\nhere, state over there, network requests in a service, validation in a schema folder, and side\neffects in middleware. Each directory looks tidy while a single product behavior is smeared across\nall of them. The architecture has separated the code without separating the concern. A concern is\nusually closer to “cancel a subscription” than to “all the functions which issue HTTP requests.”</p>\n<p>Locality does not require inlining every implementation. The htmx essay makes the useful distinction\nbetween inlining behavior and surfacing its invocation. A function can hide complicated machinery\nwhile its call site still makes the dependency obvious. The same is true of authorization, database\naccess, or a background job: centralize the policy, but make its participation visible where the\nbehavior is defined. Abstraction should hide machinery, not causality.</p>\n<p>Agents sharpen the economics of that distinction because context is finite. Anthropic describes\neffective context engineering as finding the\n<a href=\"https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents\">smallest set of high-signal tokens</a>\nthat produces the desired result. A codebase is therefore not just a program; it is a context\nretrieval system. Co-located tests, nearby comments, explicit dependencies, and feature-oriented\nfolders help an agent retrieve the right evidence cheaply. Registries, implicit conventions, and\nspooky action at a distance force it to reconstruct an invisible graph before it can safely change a\nline.</p>\n<p>There are still good reasons to put some things far away. A security invariant should not be copied\ninto every endpoint merely to improve locality; it should be encoded once in a boundary, type, or\ncheck that cannot be bypassed. Shared implementation belongs behind a stable interface. The test is\nnot whether all relevant code occupies one file, but whether the local code reveals which larger\nrules govern it and gives the reader an obvious path to them.</p>\n<p>Separation of concerns remains a means, not a goal. The more useful default for a world of coding\nagents is <strong>locality of context</strong>: code that changes together should be understandable together.\nEvery architectural hop consumes attention; good architecture compresses the explanation of a change\ninto the same neighborhood as the change itself.</p>\n",
      "date_published": "2026-03-16T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/03/16/locality-of-context-over-separation-of-concerns",
      "summary": "Coding agents strengthen the case for locality: organize code so the context needed to change it is nearby, rather than scattered across architectural layers.",
      "tags": [
        "essay"
      ],
      "title": "Locality of context over separation of concerns",
      "url": "https://pau.fm/blog/2026/03/16/locality-of-context-over-separation-of-concerns"
    },
    {
      "authors": [
        {
          "name": "Pau Friedman",
          "url": "https://pau.fm/"
        }
      ],
      "content_html": "<p>I came of age when blogging was cool. I always wanted to blog consistently, but I squarely fit into\nthe wide swathe of the curve of people that habitually start and abandon blogs every few years.</p>\n<p>But these days I’m finding myself with quite a lot to say: software development is evolving rapidly\nbecause of AI, and I want to catalog that shift as it happens. What gets easier and what gets\nharder? What are the limits of the tools, and which of those limits will actually stick around? What\ndoes the work even look like now, or in a decade?</p>\n<p>Expect essays, notes, links, and occasional updates drawn mostly from my work. There will be lots of\nopinions and maybe some dog photos.</p>\n<p>Thanks for reading, and enjoy your visit to my <a href=\"https://xkcd.com/148/\">wobsite</a>.</p>\n",
      "date_published": "2026-03-15T00:00:00.000Z",
      "id": "https://pau.fm/blog/2026/03/15/hello",
      "summary": "Notes on how AI is changing software development, plus the occasional dog photo.",
      "tags": [
        "essay"
      ],
      "title": "Hello.",
      "url": "https://pau.fm/blog/2026/03/15/hello"
    }
  ],
  "language": "en-US",
  "title": "Pau Friedman · pau.fm",
  "version": "https://jsonfeed.org/version/1.1"
}