<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Michele Durante</title>
    <link>https://blog.micheledurante.com/</link>
    <description>Notes on payments, fintech and software engineering.</description>
    <language>en-GB</language>
    <copyright>© 2026 Michele Durante. All rights reserved.</copyright>
<item>
  <title>Economies and diseconomies of scale in code: what economists know about DRY</title>
  <link>https://blog.micheledurante.com/economies-and-diseconomies-of-scale-in-code</link>
  <guid>https://blog.micheledurante.com/economies-and-diseconomies-of-scale-in-code</guid>
  <pubDate>Sun, 11 Oct 2026 00:00:00 GMT</pubDate>
  <description>What economies of scale, and where they end, say about DRY: sharing code pays up to a point, and economics tells you where that point is.</description>
  <content:encoded>&lt;aside class=&quot;takeaways&quot;>
  &lt;h2>TL;DR&lt;/h2>
&lt;ul>
&lt;li>Shared code is a fixed cost spread over its uses: it pays only once there are enough of them, and the rule of three is a rough guess at that break-even point.&lt;/li>
&lt;li>Past a certain size, every user of a shared abstraction pays for the needs of all the others, and that coupling cost can outgrow the savings.&lt;/li>
&lt;li>Keeping a duplicate on purpose is an option, not a failure: it leaves you free to abstract later, once you know what the copies really share.&lt;/li>
&lt;/ul>

&lt;/aside>
&lt;p>&amp;quot;Don't repeat yourself&amp;quot; is one of the first rules we learn as developers, and one of the most satisfying to apply: spot two similar blocks, pull them into a shared function, delete a dozen lines (very satisfying :D). Most of what I read about DRY celebrates those savings. Far less of it asks when sharing stops paying.&lt;/p>
&lt;p>I have a personal reason to ask. For my side projects I keep a small private TypeScript library, &lt;code>deno-tools&lt;/code>, with a rule that goes with it: when a project needs something the library could hold, extend the library, never copy its code. Building this blog meant four changes to that library in a single day before the blog would build, and it made me wonder whether my rule is a good deal. Economics turned out to have the words for it and, more usefully, a way to tell where the deal ends.&lt;/p>
&lt;p>The whole mapping at a glance, with the details below:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Economics&lt;/th>
&lt;th>Programming&lt;/th>
&lt;th>What it adds&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Economies of scale: a fixed cost spread over output&lt;/td>
&lt;td>Writing a shared function once; each new use is cheap&lt;/td>
&lt;td>The basic analogy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Minimum efficient scale&lt;/td>
&lt;td>The rule of three: abstract only at the third copy&lt;/td>
&lt;td>Abstraction has a break-even point, and you can estimate it&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Diseconomies of scale (coordination, bureaucracy)&lt;/td>
&lt;td>Shared code that has to serve every user, so each change ripples to all of them&lt;/td>
&lt;td>Past a certain size, sharing costs more than it saves&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Lock-in and switching costs&lt;/td>
&lt;td>Being tied to an abstraction that is hard to change&lt;/td>
&lt;td>The hidden long-term cost&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Economies of scope: shared inputs across &lt;em>different&lt;/em> products&lt;/td>
&lt;td>One library used by unrelated projects&lt;/td>
&lt;td>A closer fit for DRY than scale&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Coase: make or buy, and the cost of each transaction&lt;/td>
&lt;td>Write it, depend on a library, or copy it&lt;/td>
&lt;td>Explains when &lt;em>not&lt;/em> to share&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Real options&lt;/td>
&lt;td>Keeping a duplicate on purpose, to abstract later&lt;/td>
&lt;td>A reason to wait that is easy to defend&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id=&quot;economies-of-scale-a-fixed-cost-spread-over-its-uses&quot;>Economies of scale: a fixed cost spread over its uses&lt;/h2>
&lt;p>A shared function is a fixed cost: you pay once to write, test and document it, and each new use is nearly free. My library's &lt;code>fetch&lt;/code> is a good example: a drop-in replacement that retries failed requests, written once, and for any project that needs it the whole cost is one import.&lt;/p>
&lt;pre>&lt;code class=&quot;language-ts&quot;>import { fetch } from &amp;quot;./deno-tools/src/http/fetch.ts&amp;quot;

// Retries network errors and failed responses, five attempts by default
const res = await fetch(&amp;quot;https://api.example.com/rates&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>Spreading a fixed cost only works with enough output to spread it over. Written as a rule, sharing pays when the expected reuses × the saving per reuse is greater than the cost of building the abstraction plus the coupling cost it puts on each user. None of these can be measured precisely, but writing them down already changes the question from &amp;quot;is this duplicated?&amp;quot; to &amp;quot;will this be used enough to pay back?&amp;quot;.&lt;/p>
&lt;h2 id=&quot;minimum-efficient-scale-the-rule-of-three&quot;>Minimum efficient scale: the rule of three&lt;/h2>
&lt;p>Economists call the smallest output at which average cost stops falling the minimum efficient scale. Programming has a rough estimate of it: the rule of three, popularised by Martin Fowler's &lt;em>Refactoring&lt;/em>: the first time, write it; the second time, wince and copy it; the third time, abstract. Two copies don't tell you what is really shared and what only looks alike. Payments have a neat example:&lt;/p>
&lt;pre>&lt;code class=&quot;language-ts&quot;>// Two copies look alike: amounts in cents, two decimals
const euros = (cents: number) =&amp;gt; `${(cents / 100).toFixed(2)} EUR`
const francs = (cents: number) =&amp;gt; `${(cents / 100).toFixed(2)} CHF`

// The third shows what varies: the yen has no minor unit
const format = (amount: number, currency: string, decimals: number) =&amp;gt;
  `${(amount / 10 ** decimals).toFixed(decimals)} ${currency}`
&lt;/code>&lt;/pre>
&lt;p>Abstract at two copies and you hard-code the two decimals, then undo it the day the yen arrives.&lt;/p>
&lt;h2 id=&quot;diseconomies-of-scale-when-sharing-gets-too-big&quot;>Diseconomies of scale: when sharing gets too big&lt;/h2>
&lt;p>Firms run into diseconomies of scale: past some size, coordination and bureaucracy cost more than size saves. Shared code runs into the same thing: every user of a shared function pays for the needs of all the others, and changing it means checking every one of them. Sandi Metz put it in one line in &lt;a href=&quot;https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;>The Wrong Abstraction&lt;/a>: &amp;quot;duplication is far cheaper than the wrong abstraction&amp;quot;.&lt;/p>
&lt;p>My library had a small case of it. Its site builder worked out where each page goes by replacing &lt;code>src&lt;/code> in the page's path with the output folder. That held as long as every site kept its sources in a folder named &lt;code>src&lt;/code>, which they all did, until this blog needed a staging folder of its own:&lt;/p>
&lt;pre>&lt;code class=&quot;language-ts&quot;>// Before: correct only while the source folder is called &amp;quot;src&amp;quot;
const out = path.replace(/\bsrc\b/g, outputDir)

// After: the page's path under the source folder, whatever its name
const out = join(outputDir, relative(sourceDir, path))
&lt;/code>&lt;/pre>
&lt;p>The fix itself was one line. The cost was everything around it: tests, docs, a changelog entry, and, sooner or later, a library update in every project that builds a site, whether it needed the change or not. Lock-in is the long-term form of the same cost: the more projects lean on an abstraction, the harder it gets to change.&lt;/p>
&lt;h2 id=&quot;economies-of-scope-different-projects-shared-parts&quot;>Economies of scope: different projects, shared parts&lt;/h2>
&lt;p>Most of the value in my library isn't scale at all. Economies of scope are savings from sharing inputs across &lt;em>different&lt;/em> products, which is closer to what it does: Telegram bots, an API, a command-line assistant and a few websites have little in common, but they share logging, that retrying &lt;code>fetch&lt;/code> and a static site build. The Markdown renderer was written for this blog and now also builds my study notes. Ronald Coase asked why firms make some things and buy others; every dependency is the same choice between writing it, depending on it or copying it, and the answer depends on what integrating it costs.&lt;/p>
&lt;h2 id=&quot;real-options-keeping-the-copy-on-purpose&quot;>Real options: keeping the copy on purpose&lt;/h2>
&lt;p>A real option is the right, but not the duty, to act later. Keeping a duplicate on purpose buys one: you can still abstract once a third case shows what is shared, whereas an abstraction made too early has to be undone first, which costs more than the copy ever did.&lt;/p>
&lt;pre>&lt;code class=&quot;language-ts&quot;>// Copied from the bot on purpose: the two may still drift apart.
// Share it when a third project needs it.
const backoff = (attempt: number) =&amp;gt; Math.min(1000 * 2 ** attempt, 30_000)
&lt;/code>&lt;/pre>
&lt;h2 id=&quot;the-numbers-so-far&quot;>The numbers so far&lt;/h2>
&lt;p>Since March 2025, eight of my projects have come to use the library, which holds about a dozen modules, and those projects have updated their copy of it 52 times. The costs are easy to count: those updates, and that day of fixes for the blog. The savings are code I never had to write, which is harder to count. My honest answer is that the rule pays for the parts every project uses, logging and builds, and I would not extend it to code only one project needs.&lt;/p>


</content:encoded>
</item>
<item>
  <title>A valid payment can still go to the wrong person: an exercise in fencing in an AI agent</title>
  <link>https://blog.micheledurante.com/a-valid-payment-can-still-go-to-the-wrong-person</link>
  <guid>https://blog.micheledurante.com/a-valid-payment-can-still-go-to-the-wrong-person</guid>
  <pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate>
  <description>Sketching an AI agent that repairs rejected payments: it can be built, but most of the work sits around the agent, not in it.</description>
  <content:encoded>&lt;aside class=&quot;takeaways&quot;>
  &lt;h2>TL;DR&lt;/h2>
&lt;ul>
&lt;li>Fixed rules settle every reject they can, and the agent only gets what is left; repairs that people approve every time move back into rules.&lt;/li>
&lt;li>The line is drawn by field, not by trust: the agent may change how something is written, never who gets how much, and a check outside the agent enforces it.&lt;/li>
&lt;li>Quality is measured after the fact, failures feed tests and rules rather than the agent, and people stay able to check it, because the bank answers for every repair.&lt;/li>
&lt;/ul>

&lt;/aside>
&lt;p>I lead payment teams, and the most tempting pitch for AI agents in banking I've come across is also the simplest: payments get rejected all day, people fix them by hand, so let an agent fix them. It sounds like the perfect first job for an agent, as nobody loves the exception queue (I certainly don't :D).&lt;/p>
&lt;p>I came across a report from a CFTE roundtable on how banks move from copilots, which help a person, to agents, which act on their own. Rather than stay abstract, I tried to design one agent end to end: one that repairs rejected payments. My conclusion is that it can be built, but it does much less than &amp;quot;repairs rejected payments&amp;quot; suggests, and most of the work sits around it rather than in it.&lt;/p>
&lt;h2 id=&quot;why-rejects-are-a-good-place-to-start&quot;>Why rejects are a good place to start&lt;/h2>
&lt;p>A reject is the earliest kind of R-transaction: a bank refuses a payment instruction before settlement because it cannot process it, say a wrong format or an invalid account number. No money has moved yet. That makes it the safest point in a payment's life to let an agent act, because a mistake can still be caught before it costs anything.&lt;/p>
&lt;h2 id=&quot;rules-first-the-agent-for-the-rest&quot;>Rules first, the agent for the rest&lt;/h2>
&lt;p>Many rejects already have a fixed answer, and ISO 20022 gives each one a standard reason code. Account closed? Don't retry, send it back. Insufficient funds? Don't retry either. These need a rules engine, which is cheaper, testable and explainable, not an agent.&lt;/p>
&lt;p>The agent earns its place only with what a fixed rule cannot handle: free text in the wrong field, a date written in a local format, that kind of mess. And when people approve the same repair every time, it leaves the agent and becomes a rule, or the client sending those files is asked to fix them at the source. The agent's job should shrink over time, not grow.&lt;/p>
&lt;pre class=&quot;mermaid&quot;>flowchart LR
    R[Rejected payment] --&amp;gt; K{Known reason code?}
    K --&amp;gt;|yes| F[Fixed rule]
    K --&amp;gt;|no| A[Agent proposes a repair]
    A --&amp;gt; V{Checks outside the agent}
    V --&amp;gt;|passes| S[Resubmitted]
    V -.-&amp;gt;|fails| H[Operations team]
&lt;/pre>
&lt;p>Only the dotted line reaches a person: repairs that fail the checks.&lt;/p>
&lt;h2 id=&quot;a-valid-message-can-still-be-wrong&quot;>A valid message can still be wrong&lt;/h2>
&lt;p>This is the part that changed how I think about it. A validator checks the form of a message, not its meaning. An IBAN with a typo fails its check digits, and an agent could &amp;quot;fix&amp;quot; it by changing a digit until the check passes. The result is a perfectly valid IBAN that belongs to someone else, and the payment goes to the wrong person with no error anywhere.&lt;/p>
&lt;p>So the boundary is not &amp;quot;how much do we trust the agent&amp;quot;, it's which fields it may touch. It may change how something is written (character sets, date formats, code values) and never who pays whom or how much: payer, payee, account, amount and currency are off limits. A separate check compares the repaired message with the original and rejects any change to those fields, so the rule holds whatever the agent does.&lt;/p>
&lt;h2 id=&quot;measuring-the-right-thing&quot;>Measuring the right thing&lt;/h2>
&lt;p>The obvious metric is the share of repairs that pass validation. It's also the wrong one. A repair that passes and comes back days later as a return or a recall is a failure that no success rate shows. The real measures are what happens to repaired payments afterwards, and the near misses: every time the field check stops the agent from touching a protected field, nothing went wrong, so nothing would be counted, yet it's the agent trying exactly the shortcut of the valid but wrong IBAN.&lt;/p>
&lt;p>Those failures are the most valuable data the bank has, as I wrote in &lt;a href=&quot;https://blog.micheledurante.com/the-junior-work-we-think-were-saving.html&quot;>The junior work we think we're saving&lt;/a>. Here they feed a fixed set of test cases and new rules, never the agent itself: no failures as examples in its prompt and no training on them, otherwise the tests only measure what it memorised.&lt;/p>
&lt;h2 id=&quot;the-people-behind-it&quot;>The people behind it&lt;/h2>
&lt;p>If only the hard cases reach people, the operations team loses the routine work it used to learn on, and a few years later fewer people can check the agent. Two practices help: a small blind sample, where someone repairs a reject before seeing the agent's proposal, and a few planted wrong proposals in the queue. If people overrule the agent less often, either it got better or they stopped looking, and the planted ones tell the two apart.&lt;/p>
&lt;h2 id=&quot;who-stands-behind-a-repair&quot;>Who stands behind a repair&lt;/h2>
&lt;p>Whatever the agent does, the bank answers for it: to its customer when a payment goes wrong, and to the regulator when a repair hides a sanctions match. Buying the model from a vendor doesn't move that responsibility. So the agent belongs to the head of the operations team whose queue it takes over, and its business case has to count the cost of checking it (the exception queue, the samples, the returns) next to the hours it saves. Count only the hours, and you get the savings now and the costs later, somewhere else.&lt;/p>
&lt;h2 id=&quot;what-i-take-away&quot;>What I take away&lt;/h2>
&lt;p>The interesting design work isn't in the agent. It's in the rules that keep its job small, the check that fences it in, the metrics that look past validation, and the people who stay able to say no. An agent that does less, inside walls it doesn't control, is one a bank can actually stand behind.&lt;/p>


</content:encoded>
</item>
<item>
  <title>The junior work we think we're saving</title>
  <link>https://blog.micheledurante.com/the-junior-work-we-think-were-saving</link>
  <guid>https://blog.micheledurante.com/the-junior-work-we-think-were-saving</guid>
  <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
  <description>Handing the junior work to AI saves less than it seems. What I took from Christian Catalini's essay on the economics of AI: producing work is getting cheap, checking it is not, and that moves where the value is.</description>
  <content:encoded>&lt;aside class=&quot;takeaways&quot;>
  &lt;h2>TL;DR&lt;/h2>
&lt;ul>
&lt;li>AI makes producing work cheap, but checking it still moves at human speed, and the gap between the two is where both the risk and the value now sit.&lt;/li>
&lt;li>The data worth keeping is no longer the finished work but the record of what failed, was rejected or nearly went wrong.&lt;/li>
&lt;li>As output gets cheap, value moves to whoever can stand behind a result and pay when it is wrong.&lt;/li>
&lt;/ul>

&lt;/aside>
&lt;p>I lead payment teams and, among other things, act as hiring manager. Since many people in companies started using AI, I've heard several iterations of the same idea: AI takes over the junior work and we save money. We all instinctively have an intuition of its fallacy (or at least I hope we do :D), but it's tempting to ignore it given the quick returns.&lt;/p>
&lt;p>Christian Catalini's essay &lt;a href=&quot;https://catalini.com/ideas/economics-of-ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;>The Economics of AI&lt;/a> gave me the words for that intuition. It sums up &amp;quot;Some Simple Economics of AGI&amp;quot;, a working paper he wrote with Xiang Hui and Jane Wu, and despite the title it's about how AI is already moving the value in everyday work, not about machines becoming generally intelligent one day.&lt;/p>
&lt;h2 id=&quot;execution-at-machine-speed-verification-at-human-speed&quot;>Execution at machine speed, verification at human speed&lt;/h2>
&lt;p>The core idea is simple: producing work is becoming almost free, checking it isn't. An agent can write a specification, the code and a launch campaign overnight. Lovely, until someone has to find out whether it invented a fact, opened a security hole or took a clever shortcut around its goal. Production runs at machine speed. Checking runs at human speed, or at the speed of the real world when the answer only arrives years later. Catalini calls the widening distance between the two the Measurability Gap.&lt;/p>
&lt;h2 id=&quot;why-we-take-the-risk-anyway&quot;>Why we take the risk anyway&lt;/h2>
&lt;p>So why not slow down wherever checking is hard? Because for the company that doesn't, the maths works. The savings arrive now and land on the company, while the cost of a hidden failure arrives later and often lands on someone else: customers, or the wider market. Skipping the checks is a rational choice for one company, even when it's a bad deal for everyone.&lt;/p>
&lt;p>The cost that worries me most is the slowest one, and it brings me back to that idea about juniors. Experts learn their judgement through years of routine work, so if agents take over the junior work, in ten years there are fewer people able to supervise the agents. As someone who hires engineers, I keep asking myself: if juniors no longer do the work, how do they learn to check it? The essay's answer is to rebuild the apprenticeship on purpose, with simulations in which people decide before they see the answer.&lt;/p>
&lt;h2 id=&quot;the-data-worth-keeping&quot;>The data worth keeping&lt;/h2>
&lt;p>Most companies treat their archive of finished work as the treasure, but finished work only teaches a model what to produce, and models see more of it every year. The data worth keeping is the record of what to reject and why: blocked deployments, near misses, postmortems, expert overrides. This one felt very familiar from payments, where moving money is the easy bit and the real knowledge sits in the exceptions, the disputes and the chargebacks. A competitor can copy a workflow, but not the years of cases that taught you when to trust it.&lt;/p>
&lt;h2 id=&quot;a-second-model-is-not-a-second-opinion&quot;>A second model is not a second opinion&lt;/h2>
&lt;p>AI will have to help check AI, as there is simply too much output for people to read. The catch is that a checker built on similar data and design shares the blind spots of the model it checks. People make independent mistakes, which is useful; agents built on the same foundations can make the same mistake a million times and politely approve each other's work. Real verification needs different methods, real-world outcomes and a person who stays responsible for the result.&lt;/p>
&lt;h2 id=&quot;who-stands-behind-it&quot;>Who stands behind it&lt;/h2>
&lt;p>I'll admit liability was the thread I was slowest to see (it turns out it runs through the whole essay). If anyone can produce a polished result, the customer stops caring which model made it and starts caring who guarantees it. In high-stakes markets the essay expects AI firms to be judged more like insurers than like software vendors, which favours banks and insurers, with their licences and balance sheets. The policy argument follows: as long as failures cost someone else, companies buy too little verification. Make the deployer pay, and verification tools get built without anyone prescribing how AI must be designed.&lt;/p>
&lt;h2 id=&quot;when-more-users-make-a-network-worse&quot;>When more users make a network worse&lt;/h2>
&lt;p>The idea I liked most connects to what I'm &lt;a href=&quot;https://blog.micheledurante.com/now-learning.html&quot;>studying&lt;/a>. Card schemes are the textbook two-sided market: more cardholders attract more merchants, and the other way round. That network effect quietly assumes every participant is real. Agents can fill a marketplace with listings and reviews, and because they make the same mistakes, more activity can make a network less trustworthy rather than more useful. The network effect runs in reverse.&lt;/p>
&lt;pre class=&quot;mermaid&quot;>flowchart LR
    A[More agent activity] ==&amp;gt;|the same plausible mistakes| T[Less trust]
    T ==&amp;gt;|the best contributors leave| V[A less valuable network]
&lt;/pre>
&lt;p>What counts then is how much participation people can trust, and a platform's history of resolved fraud cases, disputes and chargebacks becomes its real advantage.&lt;/p>
&lt;h2 id=&quot;what-i-take-away&quot;>What I take away&lt;/h2>
&lt;p>None of this is an argument against using AI: the essay's goal is &amp;quot;the maximum automation you can verify, insure, reverse, and still understand&amp;quot;. So, back to the junior work and the money we save. For engineering teams, the saving is real only if we count the work we can stand behind rather than the work produced, keep the record of what we rejected and why, and give juniors a way to learn to check work they no longer write.&lt;/p>


</content:encoded>
</item>

  </channel>
</rss>
