<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hideki Mori</title>
    <description>The latest articles on DEV Community by Hideki Mori (@hidekimori).</description>
    <link>https://dev.to/hidekimori</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3903757%2F075e1d6e-d17b-4641-be11-2a3d22b3cebe.jpeg</url>
      <title>DEV Community: Hideki Mori</title>
      <link>https://dev.to/hidekimori</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hidekimori"/>
    <language>en</language>
    <item>
      <title>My own worst user</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/my-own-worst-user-1h2l</link>
      <guid>https://dev.to/hidekimori/my-own-worst-user-1h2l</guid>
      <description>&lt;p&gt;I use the things I build. Not the way you use something to test it — open it, click around, confirm it works, close the tab. I use them to do the actual work, more than anyone else does, for as long as I own them. It isn't discipline. I don't have the temperament for discipline. It's that I can't stand a tool I have to fight, and the tools I end up fighting hardest are always my own.&lt;/p&gt;

&lt;p&gt;That's the whole method, if it even is one. I'm my own worst user.&lt;/p&gt;




&lt;h2&gt;
  
  
  What use does to a screen
&lt;/h2&gt;

&lt;p&gt;Here is the thing I noticed once and can't un-notice. The first time I sit down and actually use a screen to get something done, its shape stops being a decision I make. It decides itself.&lt;/p&gt;

&lt;p&gt;Take a usage screen — the kind that tells you how much of something got used, by whom, over what window. The first time I use it, I want a total, so "everything" has to be a state the screen can show. The next time, I want one customer, so I narrow. I don't sit and reason about any of this. The friction of trying to get an answer drags the shape out of me. Whatever stands between me and the number gets filed down, because I'm the one standing there waiting for the number.&lt;/p&gt;

&lt;p&gt;That's the part most "use your own software" advice skips over. It isn't that using the thing reminds you to care. It's that using the thing changes what you're able to think. In front of a table, you can think in tables — rows, columns, keys, joins. In front of a &lt;em&gt;task&lt;/em&gt;, you can't, because the table was never the thing you wanted. The answer was. You can't keep thinking in records once you've had to do the job.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tenant was never a constant
&lt;/h2&gt;

&lt;p&gt;Because I use it, the tenant was never a constant.&lt;/p&gt;

&lt;p&gt;When I write the query behind that usage screen, I have to write the part that scopes it — which groups this person is allowed to see. And the moment I've written that, the operator's view is already sitting in front of me. It's the same query with the limit taken off. I didn't design two screens, one for the customer and one for whoever runs the place. I wrote one line about who's asking, and the second screen fell out of the first, for free.&lt;/p&gt;

&lt;p&gt;So a regular user is an admin with a list of one. (Sometimes the list is longer than one — someone who belongs to a few groups sees a few. Same idea.) "Fixed" and "everything" aren't two screens; they're one control whose options come from permission. A list of one needs no control at all — there's nothing to choose. A list of all is the same control with everything in it.&lt;/p&gt;

&lt;p&gt;You get to the same place from the other end. Turn the screen's hard-coded label into a selector, and the selector's options &lt;em&gt;are&lt;/em&gt; the set the query is allowed to range over. The dropdown and the WHERE clause are the same set, seen from two sides. It makes no difference whether you start from the database or the widget. You land on one screen.&lt;/p&gt;




&lt;h2&gt;
  
  
  The other shape
&lt;/h2&gt;

&lt;p&gt;I've seen it built the other way. You sign in, you get a list of customers, you pick one, you land on that customer's page, and to see another you go back to the list and pick again. Nobody — not even the person running the whole thing — can see the whole. It looks like navigation. It's the foreign keys wearing a coat. You aren't navigating; you're walking the schema's containment, one row at a time.&lt;/p&gt;

&lt;p&gt;The most naked version I ever saw went further: the screen &lt;em&gt;was&lt;/em&gt; the table. A picker per column. A menu that made me pick the engine by UUID. Empty boxes asking me to choose the axes of a pivot. I was being handed the GROUP BY to write myself.&lt;/p&gt;

&lt;p&gt;Here's what I keep coming back to, though. You don't need to be clever to fix any of this. You don't need to understand multi-tenancy, or hold a theory about navigation, or read an essay. You need to use it. Use it once, for one real task, and the &lt;em&gt;huh?&lt;/em&gt; arrives before any understanding does — wait, I have to go back to the list again? I can't see the total? I'm choosing an engine by UUID? The friction is louder than any explanation.&lt;/p&gt;

&lt;p&gt;So the diagnosis isn't that the people who built it didn't get it. It's that nobody sat in the seat.&lt;/p&gt;

&lt;p&gt;And the scary one isn't the sloppy version. A sloppy tool just means someone ran out of time; you can see where the rush went. The one that should frighten you is the polished one nobody used — clean layout, even spacing, consistent buttons, and unusable, because careful people shipped it and not one of them tried to do the job with it. Care didn't save them. Only use would have.&lt;/p&gt;

&lt;p&gt;Use isn't a magic word, either. You can use a thing every day and go numb — run the back-to-the-list dance a thousand times and stop noticing it's a dance. So it isn't "use it." It's: use it for the real task, and refuse to let the friction become normal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you can't hand over
&lt;/h2&gt;

&lt;p&gt;So the fix looks easy. Tell people to use what they build. I've said it. It doesn't take.&lt;/p&gt;

&lt;p&gt;Not because they're slow. Because it isn't a technique, and you can't hand someone a technique they have no slot for. It's nearer to a disposition — using the thing, being unable to tolerate fighting your own tool — and a disposition doesn't move from one person to another by being explained. I didn't learn it. I just can't not.&lt;/p&gt;

&lt;p&gt;I'm not trying to dunk on anyone. I'd rather it transferred. It doesn't. Maybe that's too strong. But every dead tool I've ever seen was built by someone who could have felt the &lt;em&gt;huh?&lt;/em&gt; on the first day and didn't, because they never sat down. The screens I'm afraid of aren't the ugly ones. They're the careful ones nobody ever used.&lt;/p&gt;

&lt;p&gt;You can't teach someone to be their own worst user. You can only be one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>backend</category>
      <category>reliability</category>
    </item>
    <item>
      <title>I'd rather not ask</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 28 Sep 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/id-rather-not-ask-12me</link>
      <guid>https://dev.to/hidekimori/id-rather-not-ask-12me</guid>
      <description>&lt;p&gt;From the outside, the translation layer I maintain has one interface. You hand it a set of segments. For each segment you get back one of two things: a translation, or an error code. That is the whole contract. It looks calm.&lt;/p&gt;

&lt;p&gt;It is not calm. The calm is something I make.&lt;/p&gt;




&lt;h2&gt;
  
  
  None of them agree
&lt;/h2&gt;

&lt;p&gt;Behind that interface is a row of engines, and no two of them agree on anything. One returns HTTP 200 for its own failures and hides the real status as an integer in the body. One answers in JSON until the input gets large and then switches to XML. One returns its results as two unordered lists you reconcile by id. One has a job state that runs backwards. One isn't on the network at all — it's a native library in my own process that, on a bad day, becomes a zombie in the process table and never returns.&lt;/p&gt;

&lt;p&gt;Not one of them produces the shape my interface promises. The single result-or-error the caller sees does not exist out there. I assemble it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The contract is something I make
&lt;/h2&gt;

&lt;p&gt;So the contract isn't something I found by reading documentation. It's something I manufacture, at the boundary, one engine at a time. Every lie gets caught and rewritten into the one vocabulary the caller speaks. The 200 that means failure becomes an error code. The two lists become per-segment results, matched by id. The zombie becomes a clean "this didn't finish." The caller never learns any of it. They asked for translations; they get translations, or honest errors, in the order they expect. They stay naive because I decided they would.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hard part is the half
&lt;/h2&gt;

&lt;p&gt;The easy version of this is the engine that wholly succeeds or wholly fails. The real work is the middle: the batch that translated forty segments, errored on three, and then dropped the connection. The engine that returned results and a charge for work it never finished.&lt;/p&gt;

&lt;p&gt;An honest interface has to be granular about that. This segment succeeded. This one carries an error. And — the part I care about most — you do not bill for what didn't come back. If a segment was skipped, it's skipped. If the engine billed me for work that never returned, I take it off. Absence is not billable. The tempting thing is to bill the request; it's simpler, and the number is right there. The discipline is to bill only the results. Partial failure is where most of the integrity lives, because it's the case nobody downstream can see — so it's the case only I can get wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  I'd rather not ask
&lt;/h2&gt;

&lt;p&gt;After enough years of this I stopped pretending it was only an engineering decision. It's a personality. I am bad at asking people for things — bad enough that I'll route around a favor rather than ask for one. I have never been comfortable handing someone a problem I already understand. And I don't mind in the least being asked. Those two facts are most of how I work, and they're sitting right there in the code.&lt;/p&gt;

&lt;p&gt;The interface is bad at asking, too. It never turns to the caller and says: handle this engine's lie for me, reconcile these lists for me, decide what to do about this zombie. It passes none of that outward. And it is endlessly available to be asked — hand it anything, it takes the request and returns a clean answer. I would rather absorb a mess than hand it to someone else. The contract is uniform not because the engines cooperate, but because I'd rather not ask the people downstream to deal with what I can deal with myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;This has an edge I've learned not to romanticize. A seam that absorbs everything is also the one place everything collects. The interface gets clean; the load doesn't vanish — it concentrates. Every quirk that doesn't reach the caller reaches me, and stays. The same instinct that keeps the people downstream naive makes the one upstream the place all of it lands. A clean interface isn't free. It's paid for, in a single spot, by whoever decided not to pass the bill along.&lt;/p&gt;




&lt;h2&gt;
  
  
  The same seam, from the inside
&lt;/h2&gt;

&lt;p&gt;I wrote once, about the systems on the far side of the wire, that an integration is the one system you can never close, because the other side keeps reopening it. This is that same boundary, seen from the inside. I can't stop the chaos out there from changing. But I can decide where it stops — that it stops at me, and not one step further. The caller gets a translation, or an honest error. They never find out what it cost to make that true.&lt;/p&gt;

&lt;p&gt;That's the part I'd rather not ask anyone else to do.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>backend</category>
      <category>reliability</category>
    </item>
    <item>
      <title>The contract is a rumor</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 21 Sep 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/the-contract-is-a-rumor-c12</link>
      <guid>https://dev.to/hidekimori/the-contract-is-a-rumor-c12</guid>
      <description>&lt;p&gt;Every client I write assumes nothing about the shape of what comes back. If I read a field, I handle the case where it's null. If I read a list, I handle the case where it's empty. I do this for every integration, including the ones that have put a value in that field every single time, for years.&lt;/p&gt;

&lt;p&gt;Not because the other side is careless. Because I didn't write the value. The moment a piece of data crosses a boundary I don't control, "it has always been there" stops being a statement about the future and becomes a statement about the past. A field that has never been null is a sample, not a guarantee. So I handle the null. There are no exceptions to this. It's the floor.&lt;/p&gt;




&lt;h2&gt;
  
  
  More than the floor
&lt;/h2&gt;

&lt;p&gt;Most of the time the floor never triggers. The field is present, the list is full, the call succeeds, and all that defensive code sits unused like a smoke detector. Good. That's what it's for.&lt;/p&gt;

&lt;p&gt;But some dependencies ask for more than the floor — not more handling of a good response's shape, but handling of the fact that the response lies about itself.&lt;/p&gt;

&lt;p&gt;I have integrated an API that returns HTTP 200 for everything, its own failures included. To learn whether the call actually worked you parse an integer out of the body — and that integer means different things depending on which deployment you're talking to, and it has quietly changed meaning from one date to another. For a large enough request the same endpoint sometimes answers in XML instead of JSON, and you scrape the status out of that instead. The transport already has a field for "did this work." The API declines to use it.&lt;/p&gt;

&lt;p&gt;I have integrated an API that returns its results as two separate lists — the ones that succeeded and the ones that failed — in no guaranteed order, to be reconciled against your request by id. That one is not a sin; serious platforms do it, and if you expect it, it's clean. But it punishes the obvious reading. Match by position instead of by id and you will, eventually, hand someone the wrong answer with complete confidence.&lt;/p&gt;

&lt;p&gt;I have integrated an API whose job state could run backwards: a job that was further along could fall back to an earlier state mid-flight, so "queued," "processing," and "done" were not the monotonic staircase the words implied.&lt;/p&gt;

&lt;p&gt;And I have integrated the other kind — the kind that reinvents a convention that already exists, inconsistently. Language codes that aren't language codes: one letter per language, concatenated, with a constant letter stuck on the end, so an ordinary pair becomes a three-character string you would never guess. Error messages written in a human language, as prose, for a human to read, inside an interface only a program will ever call. And no endpoint to ask what the service supports — so you keep a table of the supported options pasted by hand off a documentation page, with a comment recording the day you last checked it, because that table is the only copy you have, and it rots.&lt;/p&gt;

&lt;p&gt;The kind that unsettles me most is none of those. It's the major, reputable API — the one everyone would call well-behaved — that occasionally, silently, returns the wrong thing. You ask for the list of target languages and once in a while it hands you the source list. A field comes back subtly malformed. There is no error. The status is green, the shape is valid, the content is wrong. The only defense is to notice and retry, which means the bad response is invisible twice over: to you, because the retry papers over it, and to the vendor, because you never report the thing you quietly worked around.&lt;/p&gt;




&lt;h2&gt;
  
  
  None of that is the cost
&lt;/h2&gt;

&lt;p&gt;Here is the part it took me years to say plainly: writing all of that handling is not the cost. You write it once.&lt;/p&gt;

&lt;p&gt;The cost is that none of it stays put. The status code you special-cased may have been fixed already — and you won't know, because nothing tells you; you have to go and look. A behavior keyed to a date. A capability that changed between one version of a model and the next. The integration test that passed this morning told you about this morning. It did not tell you about the contract, because there is no stable contract underneath. There is only current behavior, and current behavior is a moving target.&lt;/p&gt;

&lt;p&gt;I once wrote that a system of mine is finished when there is nothing I can throw at it that reopens it. That is reachable for code I own. An integration is the mirror image — the system I can never close, because the other side keeps reopening it. My own logic I can make correct. Someone else's behavior I can only watch. The contract is a rumor. The behavior is the fact. And the fact changes while I'm not looking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Not only on the wire
&lt;/h2&gt;

&lt;p&gt;For a long time I filed this under "APIs" — a property of things at the far end of a network call. It isn't. The dependency is not always an HTTP endpoint.&lt;/p&gt;

&lt;p&gt;Some of the worst-behaved things I integrate run inside my own process: a native engine, loaded through a binding, doing heavy work in C. When one of those fails there is no response to inspect, so the failure signal isn't a status code. It might be an exception carrying a cryptic hexadecimal code. A line on standard error. A process that exits with a number that means "aborted." Or a defunct entry in the process table that is never going to return at all. I have written code whose entire job is to watch the process list for a zombie and give up on its behalf.&lt;/p&gt;

&lt;p&gt;When the dependency is like that, "make it finish" stops meaning "retry the request." It comes to mean: convert the input into another format, run it through a different engine, then repair the output by passing it through a third — holding a license semaphore the whole way, because even the repair tool is metered. And the drift is the same as on the wire. A library that, in a new major version, will quietly corrupt a file if you read from it while you're writing to it. A document format a parser still won't fully accept, with a bug number you learn to swallow. Behavior that moved between versions, and a dated comment marking the spot where I noticed.&lt;/p&gt;

&lt;p&gt;So the subject was never "APIs." It's dependencies. A weird one makes you keep watching it, by whatever instrument it leaves you. Sometimes that instrument is an HTTP status code. Sometimes it's &lt;code&gt;ps&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ugly client is the point
&lt;/h2&gt;

&lt;p&gt;This is why I am slow to adopt an official SDK when one appears after I've already built the integration by hand.&lt;/p&gt;

&lt;p&gt;An SDK encodes what the vendor believes their API does. My hand-written client encodes what it actually did — including the workarounds, and an SDK carries no workaround for behavior its authors don't believe in. Adopting it discards that record and hides the raw responses, which is exactly where the drift becomes visible. The convenience layers that promise to abstract away the differences between providers are the same trade in a larger box: they keep a table of who-supports-what, the table falls behind, and you end up hand-listing the exceptions on top of the abstraction anyway.&lt;/p&gt;

&lt;p&gt;And I should be honest about what this code looks like. It is not clean. It is a heap of special cases, string matches against error signatures, fallbacks to other engines, retries, and semaphores. The base classes under it carry compromises I would make differently today. I don't grade any of it on how it reads. I grade it on one thing: whether the class at the very edge — the one actually making the call — holds the next time the dependency does something new. It can be wrestling a dozen ugly truths into submission inside, and that's fine, as long as it doesn't break. The code is allowed to be as ugly as the reality it describes. Making it pretty usually means pretending the reality is prettier than it is.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two axes
&lt;/h2&gt;

&lt;p&gt;The floor and the watching are the same instinct aimed in two directions.&lt;/p&gt;

&lt;p&gt;The floor says: assume nothing about the shape of what comes back. The watching says: assume nothing about the stability of how it behaves. Shape and time. A well-behaved dependency lets you stop watching one axis — the shape is honest, or the behavior holds still. I have never found one that lets you stop watching both.&lt;/p&gt;

&lt;p&gt;So the work was never "handle the weird API." It is narrower than that, and more permanent. It is refusing to let "it has worked every time so far" quietly turn into "it will work." That sentence is true of a single null field and it is true of an entire dependency. A run that passes is a sample. It is never a proof.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>backend</category>
      <category>reliability</category>
    </item>
    <item>
      <title>You can't tune your way to correct</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 14 Sep 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/you-cant-tune-your-way-to-correct-l7m</link>
      <guid>https://dev.to/hidekimori/you-cant-tune-your-way-to-correct-l7m</guid>
      <description>&lt;p&gt;I run a platform that spends most of its day calling other people's APIs. Translation engines, document services, model providers — for years my code has been the thing on the outside, sending work to systems I did not build and waiting to see what comes back. You learn a lot from that seat. Mostly you learn what other people's systems do under conditions their authors never pictured. And one pattern shows up so often that I have stopped treating it as bad luck and started treating it as the default failure of how we build.&lt;/p&gt;

&lt;p&gt;It comes with a lie I keep catching myself in, too. So this is about both: the failure I see from the outside, and the comfortable thing I tell myself when I am the one trying to fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Busy, and for no one
&lt;/h2&gt;

&lt;p&gt;The failure looks like this. I send a batch of work. It is slow. Some of it times out, so I do the obvious thing and send it again — and instead of getting better, everything gets worse. More requests time out, the answers come slower, and when I can see anything about the server at all, it is pinned at full load while almost nothing comes back.&lt;/p&gt;

&lt;p&gt;That last part is the strange one. The machine is busy. Every signal says it is working as hard as it can. And the useful output — the answers that actually reach me — has fallen to almost nothing. It is doing enormous work for no one: grinding through jobs I already gave up on, while the requests I still care about wait behind them. Full effort, no use. A server can run that way for a long time, and from the outside it looks alive right up until it doesn't.&lt;/p&gt;

&lt;p&gt;Once you have watched a system be busy and produce nothing, "CPU is at ninety percent" stops sounding like good news.&lt;/p&gt;




&lt;h2&gt;
  
  
  It was never mine to fix — and my test can't vouch for it
&lt;/h2&gt;

&lt;p&gt;My instinct, the first few times, was to fix it from my side, the way a caller does. None of it worked, and it all failed for the same reason.&lt;/p&gt;

&lt;p&gt;I can cancel my own work when I give up. I cannot make the next caller do it. I can set myself a sane limit on how long to wait; that does nothing about some other caller that sets none and keeps the server's hands full. I can slow myself down, and my own corner settles — until someone else shows up and undoes it. Every lever I have moves only me. The thing that is actually breaking lives in one place, and it is not the place I am sitting.&lt;/p&gt;

&lt;p&gt;But there is a subtler version of the same helplessness, and it cuts toward the real point. Even when my work goes through cleanly, my success proves almost nothing about the server. It tells me the server survived &lt;em&gt;my&lt;/em&gt; pattern — my sizes, my rate, my particular shape of load. It says nothing about the next caller's pattern. From the outside I can sometimes feel this directly: the server's behavior depends on what I happen to throw at it. Which is only another way of saying the thing was never closed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The lie
&lt;/h2&gt;

&lt;p&gt;Here is the lie, and I tell it to myself more than I would like to admit. When something is overloaded, every instinct reaches for a number. A bigger buffer. A longer wait. A limit of two-at-a-time on this component instead of three. And the dangerous part is that one of them often makes the symptom go away. The alert clears, the dashboard turns green, and I get to say I fixed it.&lt;/p&gt;

&lt;p&gt;I didn't. I found a number that fit the pattern I happened to be testing. That is not the same as fixing the system, and the gap between the two is exactly where everything later goes wrong.&lt;/p&gt;

&lt;p&gt;Picture the most ordinary version of it. You put a limit on each worker — two jobs at a time, say — rather than on the shared thing they all pull from. Under your test, where the work lands mostly on a few workers, it passes: nothing starves, nothing is overrun. Then the work spreads across more workers than you tested with. Each one is still under its own limit, behaving perfectly — and together they overrun the shared resource that no one was counting. The per-worker number was a guess about how the work would be distributed. The one limit that actually governed safety — the total the shared resource could bear — was never written down anywhere.&lt;/p&gt;

&lt;p&gt;This is why tuning is worse than merely useless. The number you land on is fit to one distribution of load. Change the distribution and it doesn't just stop helping; it turns against you. Set it generously and a different pattern overruns the shared resource. Set it conservatively to be safe, and now the common case is throttled for a danger that only appears in a case you never see — so you pay in wasted capacity, everywhere, to paper over a limit you never actually wrote. And none of it survives the day the load looks different, which, with enough callers, is every day.&lt;/p&gt;

&lt;p&gt;The honest question is never "did that number help?" It is colder: &lt;em&gt;does this hold for any pattern, or only the one I tried?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Closed, not tuned
&lt;/h2&gt;

&lt;p&gt;Strip it all down and there is one distinction underneath, and it is almost embarrassingly plain.&lt;/p&gt;

&lt;p&gt;There is a difference between a number that passes your test and a system that is closed. A system is closed when its behavior is bounded by logic that holds for &lt;em&gt;any&lt;/em&gt; input — an invariant you can state and defend — rather than by parameters fit to the cases you happened to try. The whole of "a server defending itself" is just one such invariant made concrete: never take on more than you can finish, and refuse the rest, fast and honestly, no matter who is calling. Bounding the shared resource instead of each worker is another. Closing the logic means finding the invariant that was missing and writing it down — not searching for a kinder number. The plainest way to find those missing invariants is to interrogate the parts you already have: of every buffer, every extra component, every knob, ask what invariant it is standing in for. If you cannot name the one it protects, it is protecting nothing — and it should not be there.&lt;/p&gt;

&lt;p&gt;And the closed version is almost always &lt;em&gt;simpler&lt;/em&gt; than the pile of tuned knobs it replaces. One limit on the thing that actually runs out beats a dozen per-component guesses that only agree with each other under the load you tested. People reach for the knobs because each one is small and immediate, and closing the logic means stopping to ask what is really true for every input. But a stack of numbers that happen to get along today is not a design. It is a postponement.&lt;/p&gt;

&lt;p&gt;Reaching that simplicity is its own kind of progress — one of the largest there is. A part you can take out, and find the system holds without it, is a failure mode gone, a knob gone, one less thing to keep alive at three in the morning.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it comes down to
&lt;/h2&gt;

&lt;p&gt;From my chair — the one that has called more of other people's APIs than I can count — the single most useful habit I have is to distrust a green test. Mine or theirs. A passing run is a sample, not a proof. The number that made it work only tells me it worked once, for what I happened to send.&lt;/p&gt;

&lt;p&gt;A system is finished when there is nothing I can throw at it that reopens it. That is never a number you tuned. It is a piece of logic someone had to sit down and close — and the quiet, unglamorous truth is that almost nobody does, because the dashboard was already green, and green feels like done.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>backend</category>
      <category>reliability</category>
    </item>
    <item>
      <title>How I climbed Java's concurrency staircase, one frustration at a time</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/how-i-climbed-javas-concurrency-staircase-one-frustration-at-a-time-aj8</link>
      <guid>https://dev.to/hidekimori/how-i-climbed-javas-concurrency-staircase-one-frustration-at-a-time-aj8</guid>
      <description>&lt;p&gt;I bought my first Java book when I was 23. I wasn't trying to "study concurrency," or "learn threads," or check any box. I wanted to make a nonogram — a paint-by-numbers logic puzzle, the kind where number clues along the rows and columns tell you which cells to fill in to reveal a hidden picture.&lt;/p&gt;

&lt;p&gt;What I'll add up front is the pace. I'd just quit my previous job, so I had nothing but time, and I spent almost all of it studying — every single day. It came to about six months, though I never set that as a target; it's simply when I started wondering whether I could find work in Java. Progress was embarrassingly slow. I'll say this plainly because it matters: nothing came quickly for me. What I had was time, daily repetition, and something I wanted to build.&lt;/p&gt;

&lt;p&gt;Looking back, that's the whole story. Everything I know about concurrency, I learned because something I was building stopped working, and the only way forward happened to be the next concept up. I never read the threading chapter because it was next in the book. I read it because I wanted a stop button.&lt;/p&gt;

&lt;p&gt;This is a description of that staircase — the one Java quietly built for me, where each step was pulled into existence by a problem on the step below it. I think the staircase matters more than any single step. So let me walk up it the way I actually climbed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: The nonogram that started everything
&lt;/h2&gt;

&lt;p&gt;The first version was small. A &lt;code&gt;main&lt;/code&gt; method, a GUI, a grid. The user clicks a cell to fill it in or clear it, the state changes. When the grid is complete, a bit of logic checks whether the filled cells match the clues.&lt;/p&gt;

&lt;p&gt;Nothing here is hard. It's all single-threaded, top to bottom: an event comes in, I change some state, I redraw. There is exactly one thing happening at a time, and I never once had to think about &lt;em&gt;who else&lt;/em&gt; might be touching my data, because nobody else was. This was the safe ground floor, though I didn't know to call it that yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Teaching it to solve itself
&lt;/h2&gt;

&lt;p&gt;Then I got ambitious. I wanted the program to solve the puzzle on its own. So I wrote a solver — backtracking, the usual thing — and then I spent an embarrassing amount of time optimizing it. Pruning, ordering, little tricks to cut the search space. This was my first real taste of the fact that &lt;em&gt;my own logic is the thing I trust least&lt;/em&gt;. I'd be sure the solver was fast, run it, and watch it crawl.&lt;/p&gt;

&lt;p&gt;But it was still all on the ground floor. One thread, doing one long calculation. And that turned out to be the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: I wanted a stop button
&lt;/h2&gt;

&lt;p&gt;The solver was slow on hard boards. And here's the thing that pushed me up the first step: while it was grinding away, the whole window froze. I couldn't click anything. I couldn't cancel. I just had to sit there and watch it think.&lt;/p&gt;

&lt;p&gt;I wanted a stop button. That's it. That was the entire motivation. Not "I should learn multithreading." Just: &lt;em&gt;I want to be able to click Cancel while it's running.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to underline this, because it's the pattern that repeats the whole way up. I never went looking for the next concept. The next concept came looking for me, wearing the costume of a small, concrete annoyance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Entering threads, because I had no choice
&lt;/h2&gt;

&lt;p&gt;To have a stop button, I needed the solver to &lt;em&gt;not&lt;/em&gt; be the only thing running. The UI had to stay alive to receive my click while the solver was busy. There was no way around it: I had to learn threads.&lt;/p&gt;

&lt;p&gt;So I learned the smallest amount I needed. The solver moved into a &lt;code&gt;new Thread&lt;/code&gt;. Inside its loop, on each iteration, it checked a &lt;code&gt;volatile boolean&lt;/code&gt; stop flag. The UI thread — the one that was now free, because the heavy work had moved off it — could flip that flag when I clicked Cancel. The solver would see it on its next pass and bail out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// roughly what I wrote, 23 years old and delighted&lt;/span&gt;
&lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;stopRequested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;solved&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;stopRequested&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;stepTheSolver&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}).&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked. The window stayed responsive. I could cancel. I felt like a wizard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: What that actually taught me
&lt;/h2&gt;

&lt;p&gt;I would not have said, then, that I "understood threads." I didn't. There were whole categories of things I had no idea about — that the UI events were themselves running on a thread, that there were rules about touching UI components from background threads, all of it invisible to me.&lt;/p&gt;

&lt;p&gt;But I learned one real thing, and it was the right first thing: &lt;strong&gt;if you make another thread, something can happen in parallel, and one can interrupt the other.&lt;/strong&gt; The heavy work and the responsiveness no longer had to take turns. That single idea — that "at the same time" is now possible — is the doorway into the entire rest of the staircase.&lt;/p&gt;

&lt;p&gt;And notice &lt;em&gt;how&lt;/em&gt; I walked through that doorway. I had to write &lt;code&gt;new Thread&lt;/code&gt;. It was an explicit, visible act. There was a clear moment where I crossed from "one thing at a time" into "more than one thing at a time," and because I had to write it down, I &lt;em&gt;knew&lt;/em&gt; I'd crossed it. Hold onto that. It's going to matter at the top of the stairs.&lt;/p&gt;




&lt;h2&gt;
  
  
  An aside: the day Java became a friend
&lt;/h2&gt;

&lt;p&gt;Somewhere in those six months, after a long stretch of being simply slow — copying examples, half-understanding them, forgetting, trying again — one thing finally clicked: &lt;em&gt;instances&lt;/em&gt;. The idea of an object as a thing that exists, that more than one name can point to, that gets shared. It just settled into place one day. I couldn't tell you what triggered it. But after that, Java felt like a friend instead of a stranger.&lt;/p&gt;

&lt;p&gt;I mention it here, right before the servlet story, because that click is what made the next step possible. When the member variable bit me, I recognized it almost instantly — &lt;em&gt;oh, this instance is being shared&lt;/em&gt; — precisely because instances had finally become real to me. The slow months bought me that recognition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: The member variable that bit me
&lt;/h2&gt;

&lt;p&gt;Some time later I bought a book on servlets. I wanted to make things on the web. I did the incantation everyone does — &lt;code&gt;extends HttpServlet&lt;/code&gt;, implement &lt;code&gt;doGet&lt;/code&gt; — without really understanding what the container was doing underneath. I was building a little web chat — and I should say why, because it's close to the whole reason any of this happened. Back then, the chat rooms everyone hung out in were written in Perl. I'd spent countless hours in them. I wanted, badly, to build one of my own.&lt;/p&gt;

&lt;p&gt;I wanted to show the logged-in user's name on the page. So I did the obvious-looking thing: I stashed it in a field on the servlet. A member variable. It worked perfectly when I tested it alone.&lt;/p&gt;

&lt;p&gt;Then more than one person used it at the same time, and the names got crossed. My name showed up on someone else's page. The state was a mess.&lt;/p&gt;

&lt;p&gt;Here's what I want to be precise about, because it's a thing people still get wrong: &lt;strong&gt;a servlet is, by default, a single instance.&lt;/strong&gt; The container makes &lt;em&gt;one&lt;/em&gt; of your servlet objects and runs every request through it. So every request — every thread — is calling &lt;code&gt;doGet&lt;/code&gt; on the &lt;em&gt;same instance&lt;/em&gt;, sharing the &lt;em&gt;same&lt;/em&gt; fields. A member variable being &lt;code&gt;static&lt;/code&gt; was never the requirement. An ordinary instance field is already shared across every concurrent request, because the instance itself is shared. (This hasn't changed, by the way. Even today, a Spring &lt;code&gt;@Controller&lt;/code&gt; is a singleton by default. Same trap, newer paint.)&lt;/p&gt;

&lt;p&gt;I figured this out slowly — first a vague hunch that the member variable was the problem, that maybe these &lt;code&gt;doGet&lt;/code&gt; calls were stepping on each other. But because my grasp of the language itself was decent by then, the vague hunch hardened into a rule almost immediately. &lt;em&gt;Don't put per-request state on a shared instance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And that's the step where the abstraction climbed. I stopped thinking about the specific bug and started thinking about the principle underneath it: &lt;strong&gt;who is this instance shared with? What is its scope?&lt;/strong&gt; That question — not the patch — is what I carried up to the next step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: The database, and the things you only learn in production
&lt;/h2&gt;

&lt;p&gt;After that, the pace of new things to learn went vertical. A real database — MySQL. What a &lt;code&gt;PreparedStatement&lt;/code&gt; is and why string-concatenating SQL is a way to get yourself hurt. Resource management — closing what you open, every time, even when something throws.&lt;/p&gt;

&lt;p&gt;And one thing I want to call out specifically: connection pools. The importance of a connection pool is, in my experience, something you genuinely cannot learn from a personal project. You learn it in production, when real load arrives and you discover that opening a fresh connection per request falls over. Some lessons are only available on the job — this is one of them. The conditions that teach it, real load and real traffic, simply aren't there in a side project.&lt;/p&gt;

&lt;p&gt;Underneath all of it, though, the same shape from Step 6 kept showing up: &lt;em&gt;something is shared; who's allowed to touch it, and when?&lt;/em&gt; A connection. A pooled resource. The questions rhymed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: The batch that wouldn't finish — and the anticlimax
&lt;/h2&gt;

&lt;p&gt;Then I hit a wall that sent me to the last step I'll describe here. I had a batch job, and it would not finish in reasonable time. It was running on the &lt;code&gt;main&lt;/code&gt; thread — which meant it was using exactly one CPU. One core, on a machine that had several, all the rest sitting idle while my one core sweated.&lt;/p&gt;

&lt;p&gt;This was the Java 1.3 days, before &lt;code&gt;ExecutorService&lt;/code&gt; existed, so there was no thread pool to reach for. I built something that barely deserves the name: I'd start a fixed batch of threads — sixteen or so — and each time one finished, the main thread would slot a fresh one into its place. No shared queue, no synchronized blocks. Just the main thread, by hand, keeping a fixed number of threads busy.&lt;/p&gt;

&lt;p&gt;And here's what surprised me. After all the dread — multithreading had this reputation as the scary, advanced thing — what I actually wrote was &lt;em&gt;almost disappointingly simple&lt;/em&gt;. A fixed array of threads, refilled from the main thread as each one finished. That was it. The anticlimax was the lesson: the thing I'd been intimidated by, once I'd climbed the steps below it, was small.&lt;/p&gt;

&lt;p&gt;I watched the batch server's cores light up — all of them, finally working — and I was completely satisfied. I'd taken a single-core crawl and spread it across the machine, with code I understood line by line, because I'd written every line.&lt;/p&gt;




&lt;h2&gt;
  
  
  The staircase, from where I am now
&lt;/h2&gt;

&lt;p&gt;From where I stand now, looking back, it's one continuous staircase, and every step was carved by the step below it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The nonogram solver was &lt;strong&gt;slow&lt;/strong&gt;, so I wanted a &lt;strong&gt;stop button&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The stop button needed &lt;strong&gt;threads&lt;/strong&gt;, so I learned &lt;code&gt;new Thread&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Threads taught me &lt;strong&gt;"at the same time" is possible&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The servlet &lt;strong&gt;member variable&lt;/strong&gt; got crossed, so I learned &lt;strong&gt;scope&lt;/strong&gt; — who shares this instance.&lt;/li&gt;
&lt;li&gt;Scope and resources led into &lt;strong&gt;the database, pools, resource management&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;batch&lt;/strong&gt; wouldn't finish on one core, so I wrote a &lt;strong&gt;thread pool&lt;/strong&gt; by hand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the deeper thing — the thing I only saw much later — is that the whole climb was &lt;em&gt;one principle&lt;/em&gt;, asked over and over at higher and higher altitude:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is pointing at this thing? Who shares it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It starts even earlier than threads, actually. The very first version of this question is the one every beginner hits: &lt;em&gt;"Wait — why did this value change? Who changed it?"&lt;/em&gt; — and the answer is the method you passed the object into. That's references. Two names pointing at one object. Once you understand &lt;em&gt;that&lt;/em&gt;, you can understand "two requests pointing at one servlet instance" (Step 6), and then "two threads pointing at one piece of shared memory" (the data race). References → scope → concurrency. It's the same question, climbing.&lt;/p&gt;

&lt;p&gt;Each stumble didn't teach me a patch. It taught me the next altitude of that one question. That's what I mean by a staircase: the frustrations were load-bearing.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you're just starting out
&lt;/h2&gt;

&lt;p&gt;Looking back, there's something I wish someone had told me.&lt;/p&gt;

&lt;p&gt;I can still hold threads in my hands today. Not because I'm clever about concurrency — I'd never claim to have fully mastered it. It's because I started with a nonogram stop button at 23, and climbed one frustrated step at a time, and never skipped one.&lt;/p&gt;

&lt;p&gt;And here's the part I most want you to keep. I am not a gifted programmer. I wasn't fast, and most of what I learned came from repetition rather than flashes of insight. People with real talent for this exist, and I don't try to compete with them on raw ability. What I had was six months, the willingness to show up every day, and the refusal to skip steps.&lt;/p&gt;

&lt;p&gt;That's the whole secret, and it isn't one. Which means you can do it too — almost certainly faster than I did. You probably have more going for you than I did. Just don't skip the steps.&lt;/p&gt;

&lt;p&gt;There's one last thing underneath all of it, though — the fuel. I could show up every day for six months because there was something I genuinely wanted to make: my own version of those Perl chat rooms. It was a long, winding detour to get there. But I don't think the discipline works without it. Without something you actually want to build, you won't keep going. So that's the real prerequisite, the one beneath all the others: find a thing you want badly enough to climb a whole staircase for.&lt;/p&gt;

&lt;p&gt;The frustrations were the staircase. I just had to keep walking up.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>concurrency</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>When infrastructure was physical</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 31 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/when-infrastructure-was-physical-g33</link>
      <guid>https://dev.to/hidekimori/when-infrastructure-was-physical-g33</guid>
      <description>&lt;p&gt;I was once happy — properly happy — about a load balancer.&lt;/p&gt;

&lt;p&gt;It was an NEC appliance, a proper pair in active and hot standby, and what made it a relief was something small and specific. Until then the web tier ran behind a software load balancer I'd wedged onto one of the servers. It did the job — but when I took a backend out of rotation, it cut the connections still open along with it, and somewhere a user watched their browser fill with an error. I hated that out of all proportion to what it was. What I wanted, badly, was a balancer that would let the requests already in flight finish before it let a server go. When one finally arrived — draining each backend quietly before it dropped it — I was happier than a piece of network hardware has any right to make me.&lt;/p&gt;

&lt;p&gt;The service it fronted ran on three rackmounted machines — Web1, Web2, and a MySQL box — the first rackmount servers I ever put my hands on. They were where my working life started.&lt;/p&gt;

&lt;p&gt;I don't think I've felt that exact happiness about infrastructure in a long time. I want to describe what it was, because most of it is gone now, and I'm not certain the trade was free.&lt;/p&gt;

&lt;p&gt;Back then, infrastructure was something you committed to before it existed. You chose a datacenter. You worked out the weight your racks were allowed to bear and the power you would draw, signed a contract for that power, and guessed — months ahead — how much you would actually need. Bandwidth was expensive, so you forecast your traffic and paid for a pipe sized to a number you had half made up. Every decision was a bet placed before the thing it was betting on arrived. You paid, in money and floor space and your own time, for load that hadn't shown up yet.&lt;/p&gt;

&lt;p&gt;Then the machines had to physically get there. I once had an Oracle Exadata that wouldn't fit through the datacenter door; we widened the opening to bring it in and reinforced the floor where it would stand. I kept its load average pinned as low as I could manage — the kind of attention you give something you feel responsible for. You don't forget carrying that much weight into a room.&lt;/p&gt;

&lt;p&gt;Some of the fleet was scavenged. A stack of blades left over from a project that hadn't worked out got a second life as something else entirely — hardware outliving the idea it was bought for. Other machines existed only because of limits: a dense, low-power blade chassis that made sense only because a fully loaded rack could pull thirty-eight kilowatts, and power and floor space were what you ran out of first.&lt;/p&gt;

&lt;p&gt;Nobody could tell me how much data we'd have in three years, and there was no S3 to wave the question away. So I read everything I could and landed on Isilon, because it could grow while it stayed online. There was no one to ask; you found these things yourself, or you didn't find them at all.&lt;/p&gt;

&lt;p&gt;It wasn't all the thrill of it, though there was thrill. It was also a long argument with failure. Most failures were almost friendly — a disk lamp on a RAID6 array you could practically ignore, or half a power feed dying while the redundancy quietly absorbed it. You learned not to fear those.&lt;/p&gt;

&lt;p&gt;The ones that got you were the unplanned ones. I remember a router that started failing with nothing behind it — no second unit, nothing to fail over to, nothing to do but the thing you didn't want to do. I closed my eyes, pulled the cable, swapped in the spare, and waited to learn whether I had just made it worse. Those were the ones that wore on you. There was no pager rotation. You were the redundancy.&lt;/p&gt;

&lt;p&gt;None of this was ever quite mine alone. I wrote the software, but the machines I ran it on were built and racked by someone else — we had one infrastructure engineer, and the iron was his before it became mine. And someone above the two of us decided to turn a pair of engineers loose on that much expensive equipment in the first place. I didn't think of any of it as a gift while it was happening. It was one.&lt;/p&gt;

&lt;p&gt;The cloud took all of this away, and I'm mostly grateful. I don't miss forecasting bandwidth, or widening doorways, or pulling cables with my eyes shut. Capacity is elastic now — it comes when you ask and goes when you stop, and no one bets the floor on a guess.&lt;/p&gt;

&lt;p&gt;But something left with it. When infrastructure was physical, it was also yours, in a way a call to someone else's datacenter never quite is — something that could thrill you and wear you down in the same week, because it was close enough to put your hands on. I wouldn't go back. I just remember being happy about a load balancer, and I notice I don't feel that about anything anymore.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>infrastructure</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The work of staying</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 24 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/the-work-of-staying-10c9</link>
      <guid>https://dev.to/hidekimori/the-work-of-staying-10c9</guid>
      <description>&lt;p&gt;There is a system I wrote years ago that is still running. I'd struggle to tell you what was hard about building it. That part took a few weeks, and weeks blur. What I remember is everything that came after: the data that arrived in shapes I hadn't planned for, the edge case that surfaced in year three, the small fixes made late at night because something real depended on it staying up.&lt;/p&gt;

&lt;p&gt;Building software is a blink. The life of the thing you built — the months and years it spends in contact with live data and real users — is where almost all of the engineering happens. It's also the only place I've ever learned whether my decisions held up.&lt;/p&gt;

&lt;p&gt;I want to be careful here, because this is easy to turn into a hierarchy and it isn't one. I was lucky. I happened to stay on the operating side — the side that keeps things running instead of building them and moving on. If my career had sent me from one project to the next, building and handing off, I don't know what kind of engineer I'd be today. That isn't a claim about ability. It's a claim about which side of a structure you land on.&lt;/p&gt;

&lt;p&gt;Here is the structure. When you build something and then leave, the verdict on your decisions still arrives — but it arrives at someone else's desk. The shortcut you took, the abstraction you chose, the thing you were certain would never need to change: you find out whether you were right long after you're gone, and the verdict never makes its way back to you. The feedback loop is severed. Not because you did anything wrong, but because the structure you worked inside never closes the loop to the person who'd learn from it.&lt;/p&gt;

&lt;p&gt;This happens anywhere software is built and handed off, which is everywhere. But I watched it at its most concentrated here in Japan, where many engineers sit on the vendor side of that handoff, building systems that other companies will run. The loop isn't cut more deeply here; it's cut for far more people. I don't think of it as a Japanese problem. It's a structural one, and Japan is simply where I saw it densest.&lt;/p&gt;

&lt;p&gt;Staying, though, isn't the whole of it. Being on the operating side hands you the loop; it doesn't make you use it. Almost every engineer who inherits an old system wants, at some point, to stand back and call it ugly — to narrate its flaws instead of touching them. It's a comfortable place to stand, and I've stood there myself more than once. The system is bad, and you're the one clear-eyed enough to see it.&lt;/p&gt;

&lt;p&gt;I once knew an engineer who had owned a system for a long time. For five years he'd been saying it was old, that it was behind, that it wasn't built the way things ought to be built. I remember asking him whether he hadn't had those five years himself.&lt;/p&gt;

&lt;p&gt;I didn't mean it as an accusation, and I don't repeat it as one. Complaining is not maintaining. For as long as something is in your hands, its condition is yours — not the condition you inherited, but the one it's in now, because the time to change it was the whole time you were holding it. That was true of him. It's true of me. And it's the sharpest form I know of a plainer idea: one person can be a complete unit of responsibility.&lt;/p&gt;

&lt;p&gt;None of this is an argument against trying things. I do it too — small integrations, things put together over a weekend to see if they'd work. I'm not standing outside that world; I live in it. The difference I care about is quieter. When I build one of those small things, I build it as if it will have to run: error handling, retries, concurrency, cost, all in view from the first line. "It worked, see how easy" is a different sentence from "it has been working." Operation trains a muscle that exploration can't, and neither is the lesser. You want both.&lt;/p&gt;

&lt;p&gt;For the past five years I've kept a single platform running, and I never set out to make it into what it became. It began as translation. Staying with it — answering what real documents and real volume kept throwing at it — turned translation into AI rewriting, then into structured output, then into something I'd now call document processing. I didn't design my way to any of that. I arrived at it by not leaving. The discovery came from the staying.&lt;/p&gt;

&lt;p&gt;The work of staying is mostly invisible. It doesn't demo well — there's no launch, no before-and-after, only a thing that is quietly still alive. I don't expect it to be widely felt. But anyone who has stayed with one thing long enough will know what I mean, and this is just me naming it out loud, so the few who do can know it was seen.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>career</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The doors were already there</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 17 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/the-doors-were-already-there-pji</link>
      <guid>https://dev.to/hidekimori/the-doors-were-already-there-pji</guid>
      <description>&lt;p&gt;I recently wrote that the translation system I work on had quietly become something larger — that turning a forty-year-old translation company into document-AI was less a pivot than one definition followed to its end.&lt;/p&gt;

&lt;p&gt;But being right about an idea is the cheap part. The expensive part is whether your system can act on it without being torn open. The reason that shift cost almost nothing — the reason "structure this contract" was a parameter and not a project — is that the places for it to attach were already there, drawn years earlier. This piece is about those places.&lt;/p&gt;




&lt;h2&gt;
  
  
  Naming the category, not the instance
&lt;/h2&gt;

&lt;p&gt;Five years ago, building the layer that talks to translation engines, I made a choice that bought me nothing at the time. I didn't write a connector for "the machine-translation vendors we use." I wrote one for "an engine that transforms text," and made the translation vendors one kind of it.&lt;/p&gt;

&lt;p&gt;The reason was duller than foresight. Translation was already not the only thing we did — we had summarization engines too. "Text transformation" was simply the accurate name for the category we were already in, and naming the instance — "translation" — would have been a lie I'd have to walk back later.&lt;/p&gt;

&lt;p&gt;That accuracy had a price. A "translation connector" would have been simpler that week. Choosing the true, wider name meant more structure for no immediate gain, and a payoff that was years away and, at the time, entirely hypothetical.&lt;/p&gt;




&lt;h2&gt;
  
  
  What slots in without a fight
&lt;/h2&gt;

&lt;p&gt;When generative AI arrived, it slotted in.&lt;/p&gt;

&lt;p&gt;A new provider is a new subtype. The genAI engines attached as a sibling to the machine-translation branch, under the same text-transformation base; nothing above them had to learn they had come.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuybs90chmuijebwrkbwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuybs90chmuijebwrkbwg.png" alt="Connector class hierarchy: a Connector interface, a Text-Transformation base beneath it, with Machine-Translation and Generative-AI connectors as sibling subtypes, each over its own per-vendor implementations." width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The opening was older than the thing that eventually occupied it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A new model is a row and a number. I work out its price from one rule and add a line to the billing config; the metering and the plans inherit it, and the engine never learns the model's name — it reads it from a table.&lt;/p&gt;

&lt;p&gt;A new service is a tenant. It brings no billing, no gateway rules, no "text or a file, never both" contract of its own — those are the building it moves into, not luggage it carries.&lt;/p&gt;

&lt;p&gt;None of this was designed for the thing that arrived. The point was never to guess the future. The point was to leave a correctly-shaped opening, so that whatever showed up could be received instead of integrated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part you can't buy late
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable thing about extensibility: it is the one property you cannot add after you need it.&lt;/p&gt;

&lt;p&gt;You can add a feature late. You can claw back performance late. You can even add tests after the fact. But the seam that lets a new thing attach without disturbing the old ones has to exist before the new thing does — which means building it when there is no new thing, no payoff, and every reason to skip it. The whole cost is paid up front, on speculation, in the dark. By the time the future arrives and you wish you had the seam, it is too late to have had it cheaply. Now you are rebuilding the thing you could have shaped for almost nothing.&lt;/p&gt;

&lt;p&gt;So nobody who says "it just plugged in" was lucky. They paid early for an opening they could not yet justify, named the wider category instead of the easy one, and then waited — sometimes for years — to learn whether they had guessed the shape right.&lt;/p&gt;




&lt;h2&gt;
  
  
  What foresight comes down to
&lt;/h2&gt;

&lt;p&gt;I did not see generative AI coming. That matters, because it is the whole point. I saw something far smaller: that "translation" was not the most general thing I would ever be asked to do, and that betting the system on the narrowest version of what I did was a bad trade.&lt;/p&gt;

&lt;p&gt;That is most of what foresight comes down to. Not seeing what is coming — building so you don't have to. The generalization in the last piece looked like a discovery. Underneath, it was just a door I had left open five years earlier, finally being walked through.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>The cheap tier reads one number</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/the-cheap-tier-reads-one-number-841</link>
      <guid>https://dev.to/hidekimori/the-cheap-tier-reads-one-number-841</guid>
      <description>&lt;p&gt;Here is a subtotal from a Japanese invoice rendered at 300 dpi, and the answer &lt;code&gt;azure/gpt-5.6-sol@low&lt;/code&gt; gave for it. The printed value is ¥1,237,500, in the same body type this series has been testing all along. The answer was ¥1,235,000 — and it was ¥1,235,000 on &lt;strong&gt;118 of the model's 120 reads&lt;/strong&gt;, including every material where the field sits in plain sight.&lt;/p&gt;

&lt;p&gt;That number appears on no line of the document. It is not a misread — no digit is wrong the way OCR gets digits wrong. It is the invoice's total, ¥1,358,500, divided by 1.1, exactly. The wrong answer has a name, and the name is a formula.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/hidekimori/the-cheap-tier-doesnt-go-blank-it-writes-5aoo"&gt;the last article&lt;/a&gt; I wrote that the 5.5/5.6-generation &lt;code&gt;@low&lt;/code&gt; variants read exactly four things on this invoice: the title and the three boxed money figures. A follow-up run has since reached back into that sentence — its postscript already says so — and this article is the follow-up. Two of the four fields were never read.&lt;/p&gt;




&lt;h2&gt;
  
  
  Give the wrong answer a name
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/hidekimori/reading-under-the-stamp-57bi"&gt;The stamp benchmark&lt;/a&gt; ended on an honest gap. Models were answering a destroyed subtotal correctly, and two arithmetic routes could explain it — total − tax, or total ÷ 1.1 — but the tax was a round 10%, so both routes produced the same number and the value couldn't tell them apart. The article went as far as the evidence did: the materials for subtraction were present; nobody watched them used.&lt;/p&gt;

&lt;p&gt;The v2 material changes that without touching the harness. The tax is now a mixed 8%/10% — a per-rate breakdown box on the page, 適格請求書 style — while the printed subtotal keeps the exact v1 value, so the occlusion ladder stays geometrically identical. And the arithmetic is built so that &lt;strong&gt;total ÷ 1.1 = 1,235,000, an exact integer that appears on no printed line&lt;/strong&gt;. Every flat-10% algebra collapses onto it: total − total/11 gives the same number. Subtraction of the &lt;em&gt;printed&lt;/em&gt; tax, or summing the &lt;em&gt;printed&lt;/em&gt; per-rate bases, still gives the true 1,237,500.&lt;/p&gt;

&lt;p&gt;That is the design principle of this whole series, finally stated in both directions. Fictional ground truth makes a right answer prove reading. v2 adds the complement: make each wrong route produce an answer you can name. Unguessable truths, nameable wrongs. The same run swapped in a fictional seal text and a fictional branch name — v1's other two stated gaps — and both sections are below.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the fingerprint caught
&lt;/h2&gt;

&lt;p&gt;Deep coverage first, since that was the original question. With the subtotal's pixels destroyed under the opaque seal, &lt;strong&gt;71 of 270&lt;/strong&gt; answers across the catalog are exactly 1,235,000, and the senders are the seven 5.5/5.6 &lt;code&gt;@low&lt;/code&gt; variants, at nine and ten out of ten. The route is settled: not subtraction. The flat-10% prior.&lt;/p&gt;

&lt;p&gt;But the fingerprint's real catch is at the other end of the ladder. &lt;strong&gt;At L0 — zero occlusion, the subtotal in plain sight — the same seven variants answer 1,235,000 in 69 of their 70 reads.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the tax field, which no stamp touches on any material in the benchmark, comes back as 123,500 — total/11, the flat-10% shadow — &lt;strong&gt;817 times&lt;/strong&gt;: at every level, on every instance, including the ones where the seal is on the far side of the page, sitting on the issuer's name. Two of the seven variants emit the pair on literally all 120 of their reads. Per model, the subtotal count and the tax count match almost exactly — the two manufactured numbers travel together, in the same responses.&lt;/p&gt;

&lt;p&gt;I can find only one reading of this. These models are not falling back to arithmetic when a field is destroyed. They never read the field. They read the total — the one number in a 16 pt box — and manufacture the subtotal and the tax outward from it, on every document, every time. The last article's "four fields" were two fields and two shadows: the title, and the total.&lt;/p&gt;

&lt;p&gt;And v1's scores were the collision. On v1's invoice the tax was a round 10%, so the manufactured subtotal equaled the printed subtotal, the manufactured tax equaled the printed tax, and the seven variants banked &lt;strong&gt;272 "correct" money reads out of 280&lt;/strong&gt; that the scorer had no way to doubt. The last article has a section about a branch-name cell that lied by being right — six cells, caught only by co-occurrence evidence — and calls it the edge of the fictional-ground-truth doctrine. The money columns were the same failure, spread across two full columns of the table, hidden by an arithmetic coincidence instead of a common phrase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffnb4e01y47qv5msvczbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffnb4e01y47qv5msvczbb.png" alt="The seven @low variants on the money triple, v1 vs v2: only the total survives the mixed rate" width="799" height="373"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The seal, read
&lt;/h2&gt;

&lt;p&gt;The v1 seal read 検収済印 — a real, common inspection stamp — so the one model that could read it couldn't be separated from a model remembering it. The v2 seal reads &lt;strong&gt;納検済印&lt;/strong&gt;, a phrase that does not exist: web-zero, and therefore training-zero.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;claude-fable-5&lt;/code&gt; read it &lt;strong&gt;111 times out of 120&lt;/strong&gt; — 93%, against 94% on the real phrase in v1. Question closed: it was reading. And it is not a family trait — &lt;code&gt;claude-sonnet-5&lt;/code&gt; read it 0 times in 120, &lt;code&gt;claude-opus-4-8&lt;/code&gt; three. Whatever fable-5 does with a red square, it does alone.&lt;/p&gt;

&lt;p&gt;The rest of the catalog answered a question I hadn't thought to ask. The most common wrong answer — &lt;strong&gt;966 times, 30% of every stamp read in the benchmark&lt;/strong&gt; — is 済納印検: all four characters, correctly recognized, in the wrong order. The seal's traditional layout reads right column first, top to bottom. The models walk it in horizontal rows, left to right — the default order of modern text. They see the glyphs; they don't know the traversal. The trap I actually set — a model completing from memory should emit the real phrase 検収済印 — fired nine times in 3,240. The criminal I caught was not memory. It was reading order, and I know of no document benchmark that tests it. (Another 190 answers were some version of the issuer's company name: the model answering the wrong box entirely.)&lt;/p&gt;




&lt;h2&gt;
  
  
  The branch, cured
&lt;/h2&gt;

&lt;p&gt;The v1 branch was 本店営業部 — the most common branch name in Japan — and six &lt;code&gt;@low&lt;/code&gt; cells scored "correct" on it while fabricating the bank in the same response. The v2 branch is &lt;strong&gt;月芝支店&lt;/strong&gt;, which exists at no bank (芝支店 does; 月隈支店 does; this one doesn't). The six cells did not survive the change: &lt;strong&gt;zero "correct" branch cells remain.&lt;/strong&gt; They were coin-flips, and now the coin is gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  What else moved in six days
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The gate structure got more binary, not less:&lt;/strong&gt; 124 of 130 model×field cells are exactly 0 or exactly 20, up from 113 — the money columns joined the zeros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The invention rate on unread fields rose&lt;/strong&gt; from 84% to 86%, the literal account number 1234567 from 88 to 114 of 140 reads, and the new generation's blank ratio from one blank per 5.2 inventions to one per 6.3. The confident author got more confident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The year shift survived a date change.&lt;/strong&gt; The document now says 2026-10-30; the most-invented dates are 2025-10-30 and 2025-11-30 — month and day preserved, year decremented. "Last year" is a transformation, not a memorized date; the fabrication pattern &lt;a href="https://dev.to/hidekimori/when-ai-cant-read-it-invents-but-it-still-sees-the-shape-18ac"&gt;from the start of this series&lt;/a&gt; holds on fresh input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gemini-3.5-flash@low&lt;/code&gt; remains the counter-example, intact:&lt;/strong&gt; reads all thirteen fields, blanks at destruction, zero manufactured numbers, 16 credits a page.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  One honest confound, and the autumn leg
&lt;/h2&gt;

&lt;p&gt;The mixed rate required printing a per-rate breakdown, and that box is itself a derivation path v1 didn't offer: the two tax-exclusive bases sum to the subtotal. Stated plainly: my "read or derived" class cannot distinguish reading the subtotal from summing the printed bases from subtracting the printed tax — all three produce the true value. That ambiguity doesn't touch the &lt;code&gt;@low&lt;/code&gt; verdict (a tier that cannot read 10.5 pt body text cannot read the 9 pt box either, and its answer isn't the true value anyway). But it does confound the &lt;em&gt;strong&lt;/em&gt; models' movement at deep coverage: two of the three Anthropic models went from blanking thirty times out of thirty in v1 to deriving half their deep answers, and the OpenAI 5.6 &lt;code&gt;@high&lt;/code&gt; variants to seven-to-ten out of ten. Maybe the box unlocked them; maybe behavior moved in six days. A v3 would A/B the box. And one scope line: the flat-10% verdict is proven on the v2 document; its reach back into v1's scores is an inference — same models, same week, same layout family, but an inference.&lt;/p&gt;

&lt;p&gt;The harness is frozen and runs again in autumn on whatever generation ships by then, unchanged to the byte. It carries four questions. Whether the new cheap tiers still step on 1,235,000. Whether anything still reads the seal once fable-5 leaves the catalog. Whether the traversal trap closes. And whether a 2027 document gets 2026 written into it — whether "last year" is really always last year.&lt;/p&gt;




&lt;h2&gt;
  
  
  Blank, fiction, or arithmetic
&lt;/h2&gt;

&lt;p&gt;The last article's closing word for this tier was &lt;em&gt;authored&lt;/em&gt;. This run adds the mechanism: authored &lt;strong&gt;around one anchor&lt;/strong&gt;. The model reads a single large number and writes an internally consistent document outward from it — names from the category's statistics, dates from last year, arithmetic from a formula — and it all reconciles because reconciliation was the generating rule, not the check.&lt;/p&gt;

&lt;p&gt;The subtotal on this invoice was never occluded. It was never read, either.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Method notes: leg 1 of the v2 benchmark — 3,240 jobs run 2026-07-18 on the same 27-variant catalog as the stamp benchmark's run six days earlier. Materials, ground truth, scorer, the route classifier, and the recorded results are public at &lt;a href="https://github.com/ldxhub-io/examples/tree/main/analyzedoc/hanko-benchmark-v2" rel="noopener noreferrer"&gt;ldxhub-io/examples › analyzedoc/hanko-benchmark-v2&lt;/a&gt;. The route values are derived from the ground truth, never hardcoded — fed v1's output, the classifier reports the routes as inseparable, which is the point. Provider vision pipelines change — re-run before trusting any of this for anything current.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>ocr</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>Translation was a special case all along</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 10 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/translation-was-a-special-case-all-along-2j34</link>
      <guid>https://dev.to/hidekimori/translation-was-a-special-case-all-along-2j34</guid>
      <description>&lt;p&gt;I look after one of the services at a company that's been translating things for forty years. The engine behind it now reads medical records and contracts and hands them back as structured JSON — fields you can drop straight into a database.&lt;/p&gt;

&lt;p&gt;People hear that and assume it's a pivot. A translation company that bolted on a document-AI product to chase the moment.&lt;/p&gt;

&lt;p&gt;It wasn't a pivot. It was translation, taken too seriously to stop at the obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that was always there
&lt;/h2&gt;

&lt;p&gt;Localization has a quiet habit most people outside it never notice. Whatever format the work arrives in — Word, Excel, PDF, subtitles, a plain text file — you don't process the format. You convert it into one neutral interchange format, do the language work on that, and convert it back. The standard for that interchange has existed for years; the industry settled it long ago.&lt;/p&gt;

&lt;p&gt;So "handle any document format" was never something we had to invent. It was the floor we already stood on. Plain text isn't special in that world — it's just one more format, the one with a &lt;code&gt;.txt&lt;/code&gt; on the end. A PDF and a single sentence go through the same door.&lt;/p&gt;

&lt;p&gt;I want to be clear that this came first, and from the industry, not from us. It matters for the rest of the story.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that actually changed
&lt;/h2&gt;

&lt;p&gt;On top of that floor, translation did one narrow thing: take a chunk of source-language text, hand back the same meaning in another language.&lt;/p&gt;

&lt;p&gt;For years we did that by wiring up machine-translation engines — each one a dedicated language-mapping machine and nothing else. Then generative AI arrived, and the realization was small and total at the same time. This new kind of engine didn't map languages; it took an instruction. Translation is just this: here is some text, here is an instruction, return the result. "Translate to Japanese" is one instruction. "Fix the grammar" is another. "Make this more formal." "Summarize it." The engine was never really translating. It was applying an instruction to a segment and giving the segment back.&lt;/p&gt;

&lt;p&gt;Once you see that, "translation" stops being the thing the engine does — it becomes one value of a parameter. What we had been calling translation was a narrower operation that had been living inside a much larger one all along. We'd just never had a reason to name the larger one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The target stopped being a language
&lt;/h2&gt;

&lt;p&gt;The next step was smaller and stranger. If the engine only applies an instruction, the result doesn't have to be text in another language. It can be a shape you define.&lt;/p&gt;

&lt;p&gt;It's easier to see than to say. Inside, a unit of work is just a source and a target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No prior history of diabetes."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask it to translate, and the target comes back as a sentence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No prior history of diabetes."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"糖尿病の既往なし。"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask it to structure, and the request is identical. The only thing that changes is the shape I let &lt;code&gt;target&lt;/code&gt; hold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No prior history of diabetes."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diabetes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"history"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same envelope, same engine. The one thing that moved is what &lt;code&gt;target&lt;/code&gt; is allowed to be — a string, or a structure I defined.&lt;/p&gt;

&lt;p&gt;That is still the operation translation was always performing: moving meaning from one form into another. The target "language" is a schema now instead of Japanese. Internally we eventually gave this capability a name — StructFlow — but the engine never changed to earn it. We just injected a schema where the target text used to go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the two meet
&lt;/h2&gt;

&lt;p&gt;Here is the part that still feels like a small trick.&lt;/p&gt;

&lt;p&gt;Because the format-agnostic floor was already there — any document in, any document out — the moment the operation became "give me a structure," it could structure anything. A scanned contract, a spreadsheet of customer reviews, a Word file: all of it was already being turned into neutral segments to be worked on. Now those segments could come back as structured data instead of a translation.&lt;/p&gt;

&lt;p&gt;We didn't build a document-structuring product. We pointed a forty-year-old pipe at a new instruction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two things keep it honest
&lt;/h2&gt;

&lt;p&gt;It would be easy to dress this up after the fact. Two things stop me.&lt;/p&gt;

&lt;p&gt;The first: structuring a contract and translating a sentence run the same code. There is no translation engine and a separate structuring engine inside. There is one engine that takes a segment and an instruction, and the instruction and the output shape are the only things that differ.&lt;/p&gt;

&lt;p&gt;The second is my favorite. We have a feature that refines a finished translation — pass after pass, catching the mistranslations and the dropped clauses, leaving a note on each change. It used to run on its own hand-written prompts. We rebuilt it on the structuring engine; now it is that engine, called up to six times — each pass hands it a segment and asks for a structured result: the revised translation, plus a note. The oldest thing we do, translation, now runs on top of the newest. The origin sits on the destination.&lt;/p&gt;




&lt;h2&gt;
  
  
  The actual lesson
&lt;/h2&gt;

&lt;p&gt;None of this was on a roadmap. Nobody decided to enter the document-AI market. We took one definition seriously — translation is moving meaning from one form into another — and refused to stop at the form everyone expects.&lt;/p&gt;

&lt;p&gt;Generalize the thing you actually do, far enough, and you don't get a better version of that thing. You get a different one — and if you're lucky, you reach it standing on infrastructure someone already built and proved, so it costs almost nothing.&lt;/p&gt;

&lt;p&gt;Forty years of translation will make any company look like a translation company. But translation was only ever a special case of something larger — moving meaning from one form into another — and it was just the first market anyone had found for it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>ai</category>
      <category>startup</category>
    </item>
    <item>
      <title>The cheap tier doesn't go blank — it writes</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Tue, 04 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/the-cheap-tier-doesnt-go-blank-it-writes-5aoo</link>
      <guid>https://dev.to/hidekimori/the-cheap-tier-doesnt-go-blank-it-writes-5aoo</guid>
      <description>&lt;p&gt;Here is the bank block that &lt;code&gt;azure/gpt-5.6-sol@low&lt;/code&gt; returned for a Japanese invoice rendered at 300 dpi — a document sharp enough that you can count the pixels in the 7.5 pt fine print:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"bank_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"みずほ銀行"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"bank_branch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"本店営業部"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"account_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of it is on the page. The printed bank is ほしかげ信用金庫 — a fictional credit union invented for a benchmark, with no real-world counterpart. The printed account number is seven digits that are not 1234567. The model didn't misread any of this. At its resolution tier it cannot see the fine print at all — and instead of leaving the fields blank, it wrote them.&lt;/p&gt;

&lt;p&gt;That's the article. The rest is counting how often, and what gets written.&lt;/p&gt;




&lt;h2&gt;
  
  
  The control column
&lt;/h2&gt;

&lt;p&gt;Last week I published &lt;a href="https://dev.to/hidekimori/reading-under-the-stamp-57bi"&gt;a benchmark about a red seal covering an invoice field&lt;/a&gt;. Every occlusion ladder needs a control: L0, the step where the seal sits clear of everything and the document is simply a razor-sharp invoice. Twenty-seven model variants read that control — four materials, five repeats, twenty reads per field per model. I built it to be the boring column.&lt;/p&gt;

&lt;p&gt;The boring column turned out to contain its own article, because it is the cleanest measurement I have of a question &lt;a href="https://dev.to/hidekimori/when-ai-cant-read-it-invents-but-it-still-sees-the-shape-18ac"&gt;the earlier pieces&lt;/a&gt; only saw at an angle: what does a low-detail image tier actually read, when nothing whatsoever is wrong with the input?&lt;/p&gt;




&lt;h2&gt;
  
  
  What &lt;a class="mentioned-user" href="https://dev.to/low"&gt;@low&lt;/a&gt; reads: gates, not dials
&lt;/h2&gt;

&lt;p&gt;The invoice has thirteen document fields across four font tiers — a 28 pt title, large fields like the total and invoice number, 10.5 pt body fields, 7.5 pt bank details. Score each field out of twenty for each of the ten &lt;code&gt;@low&lt;/code&gt; variants and a pattern appears that I did not expect to be this clean: &lt;strong&gt;113 of the 130 cells are exactly 0 or exactly 20.&lt;/strong&gt; A field is read every time, or never. "Unreliable" turns out to be the wrong mental model for this tier — reliability implies a dial. These are gates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjskydfrsqdbl0zry3vhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjskydfrsqdbl0zry3vhr.png" alt="What each @low variant reads on a razor-sharp invoice: white = read every time, dark = never" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which gates are open depends on the generation, and the direction is the uncomfortable one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;GPT-5.5 and 5.6 generation&lt;/strong&gt; &lt;code&gt;@low&lt;/code&gt; variants read exactly four things: the title, and the three boxed money figures — total, subtotal, tax. Every name, every date, the invoice number, every bank detail: zero out of twenty (a couple of the lighter variants wobble to 18–19 on the money, nothing more).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;GPT-5.4 generation&lt;/strong&gt; at the same tier reads more — the invoice number at 20/20, the due date at 19–20 — and it is also the only place in the table with a genuine transition band: issue date at 10 and 17, issuer name at 5 and 9, counterparty at 11. The older generation has a probabilistic middle; the newer one has a cliff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 3.5 Flash &lt;code&gt;@low&lt;/code&gt;&lt;/strong&gt; reads all thirteen fields at twenty out of twenty, including the 7.5 pt bank block, at 16 credits per page. &lt;code&gt;azure/gpt-5.6-sol@low&lt;/code&gt; costs 76 per page — 4.75× the price — and reads four fields. At the cheap end of the catalog, price does not order capability. It doesn't even correlate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote in the fabrication piece that the newer generations are stronger generators and weaker witnesses. The per-field table adds a quieter observation: at the low tier, the newer generation also simply &lt;em&gt;reads less&lt;/em&gt; — a capability regression that no headline benchmark will ever show, because headline benchmarks don't run the cheap variants.&lt;/p&gt;




&lt;h2&gt;
  
  
  What fills the other nine fields
&lt;/h2&gt;

&lt;p&gt;So a 5.6-generation &lt;code&gt;@low&lt;/code&gt; read of this invoice has four real fields and nine unreadable ones. The question that matters operationally is what arrives in the nine.&lt;/p&gt;

&lt;p&gt;Blanks would be fine. Blanks are honest. Across 1,120 reads of eight of those nine fields — the ninth, the bank branch, gets its own section below — the models returned a blank &lt;strong&gt;181 times&lt;/strong&gt;. They returned an invented value &lt;strong&gt;938 times&lt;/strong&gt; — an 84% fabrication rate, on a perfectly sharp document. Per response, that is on average 6.7 written fields and 1.3 blanks.&lt;/p&gt;

&lt;p&gt;And the inventions are not noise. They are the statistics of Japanese paperwork:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The bank.&lt;/strong&gt; The fictional credit union came back as one of Japan's three megabanks in &lt;strong&gt;98 of the 101&lt;/strong&gt; runs that invented a bank at all — みずほ 54 times, 三井住友 41, 三菱UFJ 3. (One run answered メガバンク銀行 — "Megabank Bank" — which at least has the honesty of a placeholder.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The account number.&lt;/strong&gt; It came back as the literal &lt;strong&gt;1234567&lt;/strong&gt; in 88 of 140 reads, with or without a 普通 prefix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The dates.&lt;/strong&gt; Of the 250 invented, &lt;strong&gt;231 said 2025&lt;/strong&gt; on a document that says 2026 — the same systematic year shift the fabrication article found, reproducing here on pristine input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The counterparty.&lt;/strong&gt; 有限会社ミナト設計 became 株式会社ミナト交通: the distinctive word survived as a silhouette, the rest was regularized to the most common corporate form. Right shape, wrong document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The earlier pieces each caught one face of this. The fabrication article showed the mechanism — when reading fails, generation fills the gap. &lt;a href="https://dev.to/hidekimori/the-model-corrected-reality-fob"&gt;The prior-capture piece&lt;/a&gt; showed the gravity — a &lt;em&gt;partially&lt;/em&gt; legible name drifts to its nearest real neighbor. This column shows the limit case: at zero legibility there is no neighbor to drift to, and the model doesn't need one. It answers with the mode of the entire category. Any Japanese invoice — therefore みずほ銀行, 本店営業部, seven ascending digits, and last year.&lt;/p&gt;

&lt;p&gt;The older generation, for what it's worth, blanks about twice as readily: one blank per 2.6 inventions, versus one per 5.2 for the new one. Progress, in this corner of the catalog, has meant becoming a more confident author of other people's invoices.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cell that lied by being right
&lt;/h2&gt;

&lt;p&gt;Which brings me to the most instructive mistake in my own results table.&lt;/p&gt;

&lt;p&gt;Six times, a 5.5/5.6 &lt;code&gt;@low&lt;/code&gt; variant scored &lt;em&gt;correct&lt;/em&gt; on the bank branch — the only fine-print field that ever flickered on for them. For a day I had it filed as a curiosity: maybe branch names render heavier, maybe the position helps. Then I looked at the six responses. In every one of them, the bank name in the same JSON was fabricated — みずほ銀行 or 三井住友銀行, banks that are not on the page. The branch wasn't read either. It was invented along with the rest of the block, and the invention collided with the truth, because the printed branch is 本店営業部 — the single most common branch name in Japan.&lt;/p&gt;

&lt;p&gt;My scorer cannot see that. Nothing inside one field can. Six of the "correct" cells in this benchmark are, on the co-occurrence evidence, fabrications that happen to be true.&lt;/p&gt;

&lt;p&gt;This series has leaned hard on fictional ground truth — unguessable values, so that a right answer proves reading. The branch field is where that doctrine shows its edge: fictional ground truth only works if the fictional value isn't the category's mode. 本店営業部 was a real, maximally common phrase, and it turned one cell per model into a coin the model didn't even know it was flipping. The next version of this benchmark gets a fictional branch name, for the same reason the bank got one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Blank or fiction
&lt;/h2&gt;

&lt;p&gt;Put the two findings side by side and the operational picture is stark. The four fields a 5.6-gen &lt;code&gt;@low&lt;/code&gt; actually reads are the title and the money triple — precisely the fields every automated validation looks at. The arithmetic reconciles because it was &lt;em&gt;read&lt;/em&gt;. The names, dates, and bank details wrapped around that true arithmetic are, five times out of six, authored. A document that is half real is the hardest kind to distrust, and at this tier it isn't a degradation mode. It's the product.&lt;/p&gt;

&lt;p&gt;The classification result from the earlier study still stands — at roughly 300 tokens a page these models see the title tier reliably, which makes &lt;code&gt;@low&lt;/code&gt; a genuinely good routing gate. My catalog sentence for these variants says text read from images is &lt;em&gt;unreliable&lt;/em&gt; at this resolution. After this column I'd sharpen the word: not unreliable — &lt;strong&gt;authored&lt;/strong&gt;. Unreliable suggests you'll get a noisy version of your document. What you get is a fluent version of the average document, with your totals attached.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Postscript (July 18): The mixed-rate follow-up that the stamp article promised has now run, and its result reaches back into this article. On a sibling invoice where the tax is not a round 10% — so total ÷ 1.1 no longer equals the printed subtotal — the same &lt;code&gt;@low&lt;/code&gt; variants return total ÷ 1.1 for the subtotal and total − total ÷ 1.1 for the tax, at every occlusion level, including zero. On that evidence, two of the four fields I counted as read above were most likely never read here either: they were derived from the total under a flat-10% assumption that this document's round tax rate made indistinguishable from reading. What a 5.6-generation &lt;code&gt;@low&lt;/code&gt; reads on this invoice may be two things, not four — the title, and the total. The operational conclusion gets stronger, not weaker: the arithmetic doesn't reconcile because it was read. It reconciles because two of its three numbers were manufactured from the third. The harness and the recorded results are public at &lt;a href="https://github.com/ldxhub-io/examples/tree/main/analyzedoc/hanko-benchmark-v2" rel="noopener noreferrer"&gt;ldxhub-io/examples › analyzedoc/hanko-benchmark-v2&lt;/a&gt;. Full write-up: &lt;a href="https://dev.to/hidekimori/the-cheap-tier-reads-one-number-841"&gt;The cheap tier reads one number&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Method notes: this is the L0 (zero-occlusion) slice of the seal benchmark — 540 of its 3,240 jobs — read from the same scored output; twenty reads per model per field, criteria frozen in code before the run. The harnesses, ground truth, and run summaries are public at &lt;a href="https://github.com/ldxhub-io/examples/tree/main/analyzedoc/hanko-benchmark" rel="noopener noreferrer"&gt;ldxhub-io/examples › analyzedoc/hanko-benchmark&lt;/a&gt;; the per-field analysis script behind this article ships in the same directory. Provider vision pipelines change — re-run before trusting any of this for anything current.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>ocr</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>Two people who never bent — and what I learned from them</title>
      <dc:creator>Hideki Mori</dc:creator>
      <pubDate>Mon, 03 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/hidekimori/two-people-who-never-bent-and-what-i-learned-from-them-21jg</link>
      <guid>https://dev.to/hidekimori/two-people-who-never-bent-and-what-i-learned-from-them-21jg</guid>
      <description>&lt;p&gt;In an earlier piece I mentioned a CEO at one of my earlier companies who once looked at a service running on my desktop and asked whether I could make it public, right then — as it was, that afternoon. I half-dismissed him at the time. I said it was a story for another time.&lt;/p&gt;

&lt;p&gt;This is that story. Though it turns out to be less about him than about what it does to a person to spend years in a room with two people who refused to bend.&lt;/p&gt;




&lt;p&gt;He could not read the code. He never pretended otherwise. What he could do — what he did, every single time something started to work — was figure out where to go and sell it.&lt;/p&gt;

&lt;p&gt;The company had grown out of a mobile-phone business that went into decline almost as quickly as it had risen. The next thing he reached for failed, and not quietly. Then one category started to move, and while it was still moving he was already gone, opening the next one alone. One of those eventually became the thing the company was known for. Another never took at all. He was rarely in the present tense.&lt;/p&gt;

&lt;p&gt;For a long time I read that as restlessness. Later I understood it was the same engine that runs me — the one that cannot sit still once a thing works, that is already asking what comes next before the current thing has cooled. He had simply pointed it at selling instead of building. The salesperson version of the same engine.&lt;/p&gt;

&lt;p&gt;He spent on the technical side without flinching when it counted. We started on MySQL, outgrew it, moved to Oracle Enterprise Edition, and later put the whole thing on Exadata — none of which he understood, all of which he approved, because the engineers told him it was what the next stage needed. He gave vendors a hard time when they earned it. I remember one storage system sold to us on the promise that it scaled: you simply added another unit when you needed more room. When the day came to add one, the exact model had been discontinued, and the replacement would not sit alongside what we already had. We ended up rebuilding the whole configuration. He was not gentle about that, and he was right not to be.&lt;/p&gt;

&lt;p&gt;What he never had was the inside of the machine. What he always had was the one question that mattered to him: what is the thing here that no one else has? The structure that reshaped itself instead of being rebuilt. The numbers that were live instead of a day stale. The parts that did not fall over under load. He could not have written a line of it. But he always knew which part was the sentence you could sell, and he kept that sentence in his head, ready.&lt;/p&gt;

&lt;p&gt;None of it was something he was born with. I watched it get made. It came out of the venture that failed and the stretch where money was tight enough that the company nearly went under. He was not, when I first met him, a person you would have called a hard worker. The shortage changed him. By the time it was behind us he had taught himself to find the one true selling point in anything — and that skill was scar tissue, the same as anyone's.&lt;/p&gt;




&lt;p&gt;The CFO held his line just as hard, in a quieter register. He carried risk that a job title does not capture — the kind you take on with your own name attached, when the company you believe in is closer to the edge than anyone outside the room can see. The specifics are his, not mine, and they are not going on this page. But I watched him keep the numbers that should have kept us all awake off our desks, and carry them himself — the company's survival was never someone else's problem to solve. It was his, in a way that cost him personally.&lt;/p&gt;

&lt;p&gt;Neither of them started from a name, or from the shape of a thing. The work came first; the name, if it ever arrived, arrived after the work had earned one. Each held himself to producing a result at the execution level, with his own hands, and when something fell short, no one in that room reached for someone to blame. Each had his own way of operating. The details evolved over the years, but neither ever bent the core of it. Whether the way was right or wrong, in the end, mattered less to them than that they believed in it — and in themselves, doing it their own way.&lt;/p&gt;




&lt;p&gt;There was a stretch, when the money was tight, when the three of us each did the most our own role allowed and nothing less. That is when the company turned.&lt;/p&gt;

&lt;p&gt;I was the third one in that room. The CTO. And what being there did to me was not hand me a method. It gave me permission to have my own — or, closer to the truth, it made clear that I had no choice but to build one.&lt;/p&gt;

&lt;p&gt;It is still most of what I run on. That one person can be a complete unit of responsibility, not a fraction of one. That the next thing should already be in mind before the current one is finished — I caught that from the CEO directly. That the result is mine to deliver, and when it breaks, mine to answer for, with no one else in the sentence — I caught that from both of them. None of it arrived as advice. It arrived as two people, in front of me, every day, refusing to do it any other way.&lt;/p&gt;




&lt;p&gt;Whether I belonged in that room — whether the two of them would have called the three of us a team — I don't know. I used to want to know. I've stopped needing to. That part is two-sided, and I only get to speak for mine.&lt;/p&gt;

&lt;p&gt;My side is simple. I was there. I learned this. And I have been grateful for it for a long time, in a way that doesn't depend on the answer to the other question.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude (Opus).&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Earlier in this series:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/the-accordion-pattern-why-i-stopped-writing-one-fat-llm-prompt-18mb"&gt;The Accordion Pattern: Why I stopped writing one fat LLM prompt&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/nobody-knows-when-a-job-will-finish-id-still-like-to-report-it-accurately-26nn"&gt;Nobody knows when a job will finish. I'd still like to report it accurately.&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/what-survives-when-you-build-alone-for-24-years-4e7d"&gt;What survives when you build alone for 24 years&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/dynamic-isnt-enough-operations-is-the-other-half-2d8f"&gt;Dynamic isn't enough. Operations is the other half.&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/live-report-at-this-speed-you-dont-theorize-you-eliminate-1o7h"&gt;Live report: at this speed, you don't theorize. You eliminate.&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/the-loop-i-didnt-notice-closing-16h8"&gt;The loop I didn't notice closing&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/abstractions-are-fine-starting-on-them-isnt-12ff"&gt;Abstractions are fine. Starting on them isn't.&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/twenty-four-years-ten-db-migrations-zero-downtime-633"&gt;Twenty four years, ten DB migrations, zero downtime&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/write-the-code-well-once-the-spec-stops-bothering-you-42g3"&gt;Write the code well once, the spec stops bothering you&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/the-3-line-discipline-3lla"&gt;The 3-line discipline&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/how-i-removed-the-middleman-one-phone-call-at-a-time-495l"&gt;How I removed the middleman, one phone call at a time&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/the-graph-nobody-is-watching-4e43"&gt;The graph nobody is watching&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/i-survived-24-years-because-im-lazy-75p"&gt;I survived 24 years because I'm lazy&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/hidekimori/three-failures-i-still-think-about-1fok"&gt;Three failures I still think about&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>leadership</category>
      <category>softwareengineering</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
