<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://bhavesh-g.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://bhavesh-g.github.io/" rel="alternate" type="text/html" /><updated>2026-09-21T17:25:50+00:00</updated><id>https://bhavesh-g.github.io/feed.xml</id><title type="html">bhavesh-gpt</title><subtitle>developer blog &amp; rants</subtitle><author><name>Bhavesh</name></author><entry><title type="html">time is also currency</title><link href="https://bhavesh-g.github.io/meta/2026/09/20/time-is-also-currency.html" rel="alternate" type="text/html" title="time is also currency" /><published>2026-09-20T00:00:00+00:00</published><updated>2026-09-20T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/09/20/time-is-also-currency</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/09/20/time-is-also-currency.html"><![CDATA[<blockquote>
  <p>we have become very good at measuring financial success.</p>
</blockquote>

<p>salary.</p>

<p>net worth.</p>

<p>annual income.</p>

<p>investment returns.</p>

<p>property.</p>

<p>we put numbers against all of these and then use those numbers to decide whether someone is doing well.</p>

<p>but there is another number that rarely appears alongside them:</p>

<p><strong>how much of their time did it take to produce all of this?</strong></p>

<p>that omission matters.</p>

<p>because money is not the only thing we spend.</p>

<p>we spend time to make money.</p>

<p>and unlike money, time cannot be accumulated for later.</p>

<hr />

<h2 id="the-metric-problem">the metric problem</h2>

<p>we tend to optimize whatever we measure.</p>

<p>if a company is measured primarily by revenue, people inside it will find ways to increase revenue.</p>

<p>if an employee is measured by salary and promotions, they will naturally optimize for career progression.</p>

<p>and if we measure personal success primarily by income and net worth, we should not be surprised when people organize their lives around maximizing them.</p>

<p>the problem isn’t that these are bad metrics.</p>

<p>they are useful.</p>

<p>the problem is that they are incomplete.</p>

<p>imagine evaluating a business purely by its revenue while ignoring how much it costs to generate that revenue.</p>

<p>we would immediately recognize that as an incomplete analysis.</p>

<p>yet we routinely do something similar with people’s incomes.</p>

<p>someone earns ₹50 lakh a year.</p>

<p>great.</p>

<p>but how much time did it take?</p>

<p>how much commuting?</p>

<p>how many weekends?</p>

<p>how much availability outside working hours?</p>

<p>how much mental bandwidth?</p>

<p>how much of the person’s day is actually theirs?</p>

<p>we call the first number income.</p>

<p>perhaps we should also measure the second one.</p>

<hr />

<h2 id="the-price-of-money">the price of money</h2>

<p>every rupee has a cost of production.</p>

<p>for a business, that might mean labour, capital, materials, rent and energy.</p>

<p>for an individual, one of the largest inputs is time.</p>

<p>this doesn’t mean that every hour should be converted into rupees.</p>

<p>life would become absurd if we tried to put a price on every dinner with friends or every evening spent reading.</p>

<p>the point is simpler:</p>

<p><strong>earning money requires spending something that cannot be replenished.</strong></p>

<p>and the headline salary doesn’t show the full cost.</p>

<p>consider two people earning the same amount.</p>

<p>one works from home, has predictable hours, rarely thinks about work outside those hours, and can take a month off without much disruption.</p>

<p>the other commutes two hours every day, works late most evenings, answers messages on weekends and cannot really disconnect.</p>

<p>financially, they may look identical.</p>

<p>in terms of life, they are not.</p>

<p>the difference is being paid in a currency that doesn’t appear on the payslip.</p>

<hr />

<h2 id="the-declining-value-of-more">the declining value of more</h2>

<p>there is another reason this matters.</p>

<p>the value of money isn’t constant.</p>

<p>the first ₹10 lakh can be transformative.</p>

<p>it can create an emergency fund.</p>

<p>remove debt.</p>

<p>pay for education.</p>

<p>provide a financial buffer.</p>

<p>change the kind of job you can accept.</p>

<p>the next ₹10 lakh can still be valuable.</p>

<p>but eventually the relationship changes.</p>

<p>going from ₹1 crore to ₹2 crore can be meaningful.</p>

<p>going from ₹20 crore to ₹21 crore may barely change the way someone lives.</p>

<p>the exact numbers are different for everyone.</p>

<p>the principle isn’t.</p>

<p><strong>the marginal value of money generally falls as you already have more of it.</strong></p>

<p>time has a different problem.</p>

<p>you don’t become less in need of time because you already had a lot of it.</p>

<p>an additional free afternoon can still be an additional free afternoon.</p>

<p>an additional year with your children can never be made irrelevant by having enough money.</p>

<p>this creates an important question:</p>

<blockquote>
  <p>at what point is another unit of money worth less to you than another unit of discretionary time?</p>
</blockquote>

<p>there isn’t a universal answer.</p>

<p>but there should be a point where you start asking the question.</p>

<hr />

<h2 id="money-is-liquid-across-time">money is liquid across time</h2>

<p>money has an extraordinary property.</p>

<p>you can move its usefulness across time.</p>

<p>you can earn today and spend next year.</p>

<p>you can save today and use it decades later.</p>

<p>you can invest today and let it compound while you do something else.</p>

<p>money is remarkably portable through time.</p>

<p>time isn’t.</p>

<p>you cannot borrow an hour from tomorrow.</p>

<p>you cannot transfer an unused afternoon from your 20s into your 40s.</p>

<p>you cannot put this year’s summer into an account and withdraw it when you retire.</p>

<p><strong>money can sit in a bank. time can’t.</strong></p>

<p>this is more than a nice metaphor.</p>

<p>it is a fundamental difference between the two resources.</p>

<p>when you give up money today, you can potentially earn it back tomorrow.</p>

<p>when you give up an hour today, the transaction is final.</p>

<hr />

<h2 id="the-same-hour-does-not-always-buy-the-same-life">the same hour does not always buy the same life</h2>

<p>this is where the usual idea of “time is money” becomes incomplete.</p>

<p>an hour is not simply a unit of labour.</p>

<p>what that hour can mean depends on when you have it.</p>

<p>at 25, you may have the freedom to move to another country for six months.</p>

<p>at 35, you may have children in school.</p>

<p>at 45, your parents may need more of your attention.</p>

<p>at 55, your physical capabilities and interests may have changed.</p>

<p>none of these stages is inherently better.</p>

<p>but they are different.</p>

<p>the same sixty minutes can exist in all of them while offering completely different possibilities.</p>

<p>this is why I don’t think we should pretend that all future time is perfectly interchangeable.</p>

<p>you can always earn more money later.</p>

<p>you cannot necessarily recreate the circumstances in which you wanted to use your time earlier.</p>

<hr />

<h2 id="some-time-has-an-expiry-date">some time has an expiry date</h2>

<p>there is a difference between postponing a purchase and postponing an experience.</p>

<p>if you don’t buy a car this year, you can probably buy it next year.</p>

<p>if you don’t invest ₹1 lakh this month, you can invest it next month.</p>

<p>but if you miss your child’s school performance because you were working, there isn’t necessarily a future version of that exact event waiting for you.</p>

<p>if you don’t spend time with your parents while they are healthy, you cannot assume the same opportunity will exist ten years later.</p>

<p>if you spend your twenties working every weekend, you can take weekends off in your forties.</p>

<p>but you cannot recreate your twenties.</p>

<p>some decisions are reversible.</p>

<p>some are not.</p>

<p><strong>time has more irreversible transactions than money does.</strong></p>

<p>that should affect how we think about its value.</p>

<hr />

<h2 id="this-is-not-an-argument-against-ambition">this is not an argument against ambition</h2>

<p>this idea can very easily turn into generic advice about quitting your job, travelling the world and “living your life.”</p>

<p>that’s not the argument.</p>

<p>sometimes working extremely hard is exactly the right decision.</p>

<p>early in a career, a difficult job can buy you skills that dramatically increase your future options.</p>

<p>building a company can require years of extraordinary effort.</p>

<p>learning something difficult can demand long periods of concentration.</p>

<p>taking a financial risk can require temporarily sacrificing leisure.</p>

<p>there is nothing irrational about any of this.</p>

<p>the important question is whether the trade is conscious.</p>

<p><strong>what are you getting in exchange for your time?</strong></p>

<p>money?</p>

<p>skills?</p>

<p>ownership?</p>

<p>experience?</p>

<p>freedom later?</p>

<p>status?</p>

<p>something you genuinely care about?</p>

<p>or are you simply continuing because everyone around you is continuing?</p>

<hr />

<h2 id="enough-is-not-a-number">enough is not a number</h2>

<p>this is where the idea of “enough” becomes important.</p>

<p>people often imagine that financial independence means reaching some universal number.</p>

<p>₹5 crore.</p>

<p>₹10 crore.</p>

<p>₹20 crore.</p>

<p>whatever the number happens to be.</p>

<p>but there cannot really be one number.</p>

<p>someone with a simple lifestyle and modest obligations may need much less than someone with expensive tastes and large responsibilities.</p>

<p>“enough” is not an amount of money sitting in an account.</p>

<p>it is the point at which additional money stops changing your life as much as additional freedom would.</p>

<p>that point is personal.</p>

<p>and it can change over time.</p>

<p>but without some concept of enough, the optimization has no natural stopping condition.</p>

<p>there is always another promotion.</p>

<p>another property.</p>

<p>another target.</p>

<p>another level of consumption.</p>

<p>another person to compare yourself with.</p>

<p>the finish line keeps moving because the system was never designed to tell you that you have arrived.</p>

<hr />

<h2 id="the-transition-nobody-tells-you-about">the transition nobody tells you about</h2>

<p>there is a natural progression in how money can work.</p>

<p>early on, you exchange:</p>

<blockquote>
  <p><strong>time → skills → money</strong></p>
</blockquote>

<p>then:</p>

<blockquote>
  <p><strong>skills → higher income → capital</strong></p>
</blockquote>

<p>and eventually, if you’ve built enough capital:</p>

<blockquote>
  <p><strong>capital → autonomy → time</strong></p>
</blockquote>

<p>this is what financial independence should mean beyond the usual spreadsheet definition.</p>

<p>not simply having a large portfolio.</p>

<p>but reaching a point where money gives you more control over your life.</p>

<p>the mistake isn’t exchanging time for money.</p>

<p><strong>the mistake is continuing to make the same exchange forever without checking whether the exchange rate still makes sense.</strong></p>

<p>at 25, another ₹10 lakh might be worth an enormous amount of effort.</p>

<p>at 45, after you have substantial financial security, the same ₹10 lakh might be worth far less than the time required to earn it.</p>

<p>the numbers changed.</p>

<p>but more importantly, <strong>your life changed.</strong></p>

<hr />

<h2 id="why-people-dont-notice">why people don’t notice</h2>

<p>there is no notification that arrives one morning saying:</p>

<blockquote>
  <p>congratulations. you now have enough money. you may begin optimizing for time.</p>
</blockquote>

<p>career systems don’t work that way.</p>

<p>companies reward more responsibility.</p>

<p>society rewards visible achievement.</p>

<p>income is easy to compare.</p>

<p>net worth is easy to compare.</p>

<p>time is not.</p>

<p>you can see someone’s car.</p>

<p>you can see their house.</p>

<p>you can ask what they earn.</p>

<p>you cannot easily see whether they control their Tuesday afternoon.</p>

<p>so we keep optimizing the things that are visible.</p>

<p>and because more money is measurable, “more” feels objective.</p>

<p>“enough” feels vague.</p>

<p>this is one reason people can become financially successful without ever becoming time-rich.</p>

<p>they solved the money problem without noticing that money was supposed to help solve the time problem.</p>

<hr />

<h2 id="wealth-is-partly-control">wealth is partly control</h2>

<p>perhaps wealth should not be defined only by what you own.</p>

<p>perhaps it should also include what you control.</p>

<p>how much of your week can you decide yourself?</p>

<p>can you take a random Tuesday afternoon off?</p>

<p>can you spend a month somewhere without your income collapsing?</p>

<p>can you say no to a job that pays more but consumes your life?</p>

<p>can you spend an ordinary evening with your family without feeling that you’re falling behind?</p>

<p>these aren’t luxuries in the traditional sense.</p>

<p>they are forms of autonomy.</p>

<p>and autonomy is one of the things people are usually trying to purchase when they accumulate wealth in the first place.</p>

<hr />

<h2 id="dont-turn-time-into-another-productivity-metric">don’t turn time into another productivity metric</h2>

<p>there is a final trap here.</p>

<p>once you start thinking about time as currency, it is tempting to optimize every minute.</p>

<p>that would miss the point.</p>

<p>not every hour needs to be productive.</p>

<p>some hours should be wasted.</p>

<p>some should be spent walking without a destination.</p>

<p>some should be spent talking nonsense with friends.</p>

<p>some should be spent doing absolutely nothing.</p>

<p>the purpose of having more control over your time isn’t to squeeze more output out of every minute.</p>

<p>it’s to have the freedom to decide what deserves your attention.</p>

<p><strong>a free hour does not need to produce anything to be valuable.</strong></p>

<p>sometimes its value is precisely that nobody owns it.</p>

<hr />

<h2 id="the-direction-of-the-exchange">the direction of the exchange</h2>

<p>perhaps the most useful way to think about this is not “work-life balance.”</p>

<p>that phrase makes it sound like work and life are two competing boxes that need to be kept at some ideal ratio.</p>

<p>life is not a spreadsheet.</p>

<p>the better question is:</p>

<p><strong>what am I exchanging my time for?</strong></p>

<p>early in life, you may deliberately exchange a lot of time for skills and money.</p>

<p>later, you may deliberately exchange some of that accumulated money for freedom.</p>

<p>the direction of the exchange changes.</p>

<p>that’s not laziness.</p>

<p>that’s not giving up ambition.</p>

<p>it’s simply recognizing that money was never the final objective.</p>

<p>money was a means.</p>

<p>and one of the things it can buy is control over your time.</p>

<hr />

<h2 id="the-real-definition-of-enough">the real definition of enough</h2>

<p>so perhaps financial success shouldn’t end with the question:</p>

<blockquote>
  <p>how much money have I accumulated?</p>
</blockquote>

<p>there should be another question beside it:</p>

<blockquote>
  <p>how much of my life do I control?</p>
</blockquote>

<p>and eventually:</p>

<blockquote>
  <p>how much additional money am I willing to exchange my time for?</p>
</blockquote>

<p>because the answer shouldn’t remain constant forever.</p>

<p>there may be a period when the trade is absolutely worth it.</p>

<p>there may come a point when it isn’t.</p>

<p>the important thing is noticing when that point arrives.</p>

<p>we spend years learning how to make money.</p>

<p>we learn how to invest it.</p>

<p>we learn how to protect it.</p>

<p>perhaps we should also learn how to spend it correctly.</p>

<p>not just on things.</p>

<p><strong>on time.</strong></p>

<p>because money can be earned again.</p>

<p>money can be invested.</p>

<p>money can wait.</p>

<p><strong>time cannot.</strong></p>

<p>and the strange thing about time is that you’re spending it whether you have decided what it’s worth or not.</p>

<p>so maybe the real question isn’t how rich you are.</p>

<p>maybe it is:</p>

<blockquote>
  <p><strong>how much of your life are you getting to keep?</strong></p>
</blockquote>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[we have become very good at measuring financial success.]]></summary></entry><entry><title type="html">you can work hard for 10 years and still get nowhere</title><link href="https://bhavesh-g.github.io/meta/2026/09/08/you-can-work-hard-for-10-years-and-still-get-nowhere.html" rel="alternate" type="text/html" title="you can work hard for 10 years and still get nowhere" /><published>2026-09-08T00:00:00+00:00</published><updated>2026-09-08T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/09/08/you-can-work-hard-for-10-years-and-still-get-nowhere</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/09/08/you-can-work-hard-for-10-years-and-still-get-nowhere.html"><![CDATA[<blockquote>
  <p>the uncomfortable reason some careers compound while others quietly stay linear.</p>
</blockquote>

<p>two people can start their careers in almost identical positions.</p>

<p>same age.</p>

<p>similar education.</p>

<p>similar intelligence.</p>

<p>roughly the same number of hours available every day.</p>

<p>five years later, they can be living completely different professional lives.</p>

<p>one has become unusually valuable in a particular area.</p>

<p>they have built things, met interesting people, developed a reputation, and accumulated opportunities.</p>

<p>the other may have worked just as hard.</p>

<p>they may even have more experience.</p>

<p>but their career has mostly remained a longer version of where it started.</p>

<p>it is tempting to explain this with talent, luck, or discipline.</p>

<p>sometimes those things matter.</p>

<p>but there is another variable that is easier to miss.</p>

<p>the two people may be getting very different returns from the same amount of effort.</p>

<p>one hour of work does not have a fixed value.</p>

<p>what matters is what that hour creates.</p>

<p>if the result disappears when the work ends, tomorrow starts almost from zero.</p>

<p>if the result becomes a skill, a system, a relationship, a product, an audience, or a reputation, tomorrow starts with something extra.</p>

<p>this difference looks small in the beginning.</p>

<p>over several years, it can become enormous.</p>

<hr />

<h1 id="most-work-has-a-short-memory">most work has a short memory</h1>

<p>imagine two engineers working on the same team.</p>

<p>both spend their days solving production problems.</p>

<p>one becomes extremely efficient at debugging.</p>

<p>the other notices that the same classes of problems keep appearing.</p>

<p>instead of fixing each problem separately, they start removing the cause.</p>

<p>they write tooling.</p>

<p>they automate repetitive checks.</p>

<p>they document failure modes.</p>

<p>they improve the deployment process.</p>

<p>after a few months, something interesting happens.</p>

<p>the first engineer has become better at solving problems.</p>

<p>the second has made some problems less likely to exist.</p>

<p>both have improved.</p>

<p>but their improvements have different lifetimes.</p>

<p>the first improvement lives mainly inside the engineer.</p>

<p>the second has been transferred into the system.</p>

<p>this is one of the simplest forms of leverage.</p>

<p>and it appears everywhere.</p>

<p>a consultant can repeatedly answer the same question, or turn the answer into a framework.</p>

<p>a teacher can repeatedly explain the same concept, or create material that explains it without requiring their presence.</p>

<p>a developer can repeatedly perform a manual operation, or automate it.</p>

<p>a manager can repeatedly resolve the same class of conflict, or change the process that creates the conflict.</p>

<p>the important distinction is not effort.</p>

<p>it is persistence.</p>

<p>some work produces output.</p>

<p>some work changes the machinery that produces output.</p>

<p>the second kind is unusually powerful because its effects survive the original action.</p>

<hr />

<h1 id="an-hour-can-have-very-different-economics">an hour can have very different economics</h1>

<p>we tend to treat time as the scarce resource.</p>

<p>it is.</p>

<p>but time is not the only constraint.</p>

<p>the more interesting constraint is often the number of things that can benefit from that time.</p>

<p>consider two five-hour projects.</p>

<p>the first involves helping five people solve five individual problems.</p>

<p>the second involves building a small tool that eventually helps five thousand people solve the same problem.</p>

<p>the second project may fail completely.</p>

<p>perhaps nobody uses it.</p>

<p>perhaps the problem wasn’t important.</p>

<p>perhaps someone else already built something better.</p>

<p>but the possible outcomes are radically different.</p>

<p>this is where asymmetric opportunities become interesting.</p>

<p>some experiments have limited downside and unusually large upside.</p>

<p>you spend a weekend learning a new technology.</p>

<p>you publish something you know about.</p>

<p>you build a small prototype.</p>

<p>you talk to people in an industry you don’t understand.</p>

<p>you contribute to a project outside your normal work.</p>

<p>most of these things will produce little.</p>

<p>that is not necessarily a problem.</p>

<p>if the cost of finding out is small, the experiment does not need a high probability of success to be worthwhile.</p>

<p>the useful question becomes:</p>

<blockquote>
  <p>what happens if this works, and what happens if it doesn’t?</p>
</blockquote>

<p>a failed weekend project costs a weekend.</p>

<p>a successful one might change what you work on for the next five years.</p>

<p>that is a very different equation from spending the same weekend optimizing something whose maximum possible outcome is already known.</p>

<hr />

<h1 id="leverage-changes-the-relationship-between-time-and-output">leverage changes the relationship between time and output</h1>

<p>there is a reason software has changed so many businesses.</p>

<p>a programmer can spend months building something and then allow a machine to execute the result millions of times.</p>

<p>the work happens once.</p>

<p>the output does not.</p>

<p>this is leverage.</p>

<p>the same principle exists outside software.</p>

<p>a book can be read without the author being present.</p>

<p>an automated process can execute without its creator.</p>

<p>a video can reach people while its creator is asleep.</p>

<p>a product can serve another customer without requiring another complete production cycle.</p>

<p>a distribution channel can send future work to people who already know where to find you.</p>

<p>these are different mechanisms, but they share the same property.</p>

<p>they separate output from the creator’s immediate time.</p>

<p>without leverage, your capacity remains close to:</p>

<p>output ≈ hours worked</p>

<p>with leverage, the relationship becomes closer to:</p>

<p>output ≈ hours worked × reach of what those hours created</p>

<p>the second variable is where large differences begin to appear.</p>

<p>two people can work for eight hours.</p>

<p>one produces eight hours of output.</p>

<p>the other produces something that eight hundred people can use.</p>

<p>the difference is not necessarily effort.</p>

<p>it is the number of places the effort can go.</p>

<hr />

<h1 id="opportunity-has-a-location">opportunity has a location</h1>

<p>there is another problem with career planning.</p>

<p>you can become extremely good at solving problems that nobody around you considers important.</p>

<p>this happens because opportunity is not distributed evenly.</p>

<p>some industries are growing quickly.</p>

<p>some companies are full of ambitious people.</p>

<p>some communities sit close to emerging technologies.</p>

<p>some cities concentrate capital, talent, customers, and founders.</p>

<p>some online communities perform the same function without requiring everyone to live in the same place.</p>

<p>what changes in these environments is not just the number of opportunities.</p>

<p>it is the number of opportunities you can see.</p>

<p>imagine two engineers with identical technical ability.</p>

<p>one spends most of their time inside a stable organization where the same problems have been solved for years.</p>

<p>the other spends time talking to researchers, founders, independent developers, and people experimenting with new technologies.</p>

<p>the second engineer will encounter ideas that the first engineer may never hear about.</p>

<p>not because they are smarter.</p>

<p>because their information environment is different.</p>

<p>this is easy to underestimate.</p>

<p>before you can act on an opportunity, you need to know that it exists.</p>

<p>you need to recognize it.</p>

<p>you need to know what to do about it.</p>

<p>the first step is simply exposure.</p>

<p>sometimes the highest-leverage career move is therefore not learning another thing.</p>

<p>it is moving closer to places where interesting things are already happening.</p>

<hr />

<h1 id="networks-are-information-infrastructure">networks are information infrastructure</h1>

<p>this is also why professional relationships are more valuable than a contact list suggests.</p>

<p>a network allows information to move.</p>

<p>someone hears about a problem.</p>

<p>you know someone who can solve it.</p>

<p>another person is looking for an engineer.</p>

<p>someone has just discovered a market that is growing faster than expected.</p>

<p>another person has already made the mistake you were about to make.</p>

<p>none of this requires a formal networking strategy.</p>

<p>it happens naturally when people repeatedly interact and have enough context to trust each other.</p>

<p>there is also an interesting property of being around people from different domains.</p>

<p>if everyone around you works on the same problems, your information becomes highly redundant.</p>

<p>if your network crosses industries and disciplines, unexpected combinations become possible.</p>

<p>a technical problem can meet a business problem.</p>

<p>a researcher can meet a product person.</p>

<p>a developer can meet a customer who has a problem nobody has built for yet.</p>

<p>these collisions are difficult to manufacture deliberately.</p>

<p>you can, however, increase their probability.</p>

<p>spend more time in environments where different kinds of useful information intersect.</p>

<hr />

<h1 id="reputation-is-accumulated-evidence">reputation is accumulated evidence</h1>

<p>eventually, opportunity depends on trust.</p>

<p>someone has to decide that you are worth betting on.</p>

<p>a company has to hire you.</p>

<p>a customer has to trust you.</p>

<p>a founder has to work with you.</p>

<p>someone has to recommend you to another person.</p>

<p>the problem is that nobody knows exactly how you will behave in the future.</p>

<p>so they look at the past.</p>

<p>what have you built?</p>

<p>what have you shipped?</p>

<p>who has worked with you?</p>

<p>do you finish things?</p>

<p>do you communicate well?</p>

<p>do you keep commitments?</p>

<p>do other people trust you?</p>

<p>this makes visible work unusually valuable.</p>

<p>suppose two engineers have roughly equal technical ability.</p>

<p>one says they are good at building distributed systems.</p>

<p>the other has written about distributed systems, contributed to production systems, built tools, and can show previous work.</p>

<p>the second engineer has created evidence.</p>

<p>that evidence reduces uncertainty.</p>

<p>this is what reputation really does.</p>

<p>it compresses a large amount of previous information into a signal that another person can use quickly.</p>

<p>over time, the signal becomes an asset.</p>

<p>a successful project can help with the next project.</p>

<p>a useful article can lead to a conversation months later.</p>

<p>a recommendation can lead to an opportunity years after the original work.</p>

<p>the strange part is that reputation can keep working even when you are not actively trying to create it.</p>

<hr />

<h1 id="skills-become-more-valuable-when-they-connect">skills become more valuable when they connect</h1>

<p>there is another way people accidentally make their careers harder.</p>

<p>they optimize for isolated expertise.</p>

<p>become a better programmer.</p>

<p>become a better designer.</p>

<p>become a better writer.</p>

<p>become a better marketer.</p>

<p>all useful.</p>

<p>but the interesting value often appears between them.</p>

<p>a programmer who understands customers can make better product decisions.</p>

<p>a designer who understands engineering can design within real technical constraints.</p>

<p>a technical person who can communicate clearly can move ideas across organizational boundaries.</p>

<p>a founder who understands product, engineering, distribution, and sales can operate across several parts of the company without needing every problem translated for them.</p>

<p>none of these people necessarily needs to be the best programmer, designer, writer, or salesperson.</p>

<p>their advantage comes from the combination.</p>

<p>this changes the question you should ask about learning.</p>

<p>instead of:</p>

<blockquote>
  <p>what skill should I become exceptional at?</p>
</blockquote>

<p>sometimes ask:</p>

<blockquote>
  <p>what skill would make the things I already know significantly more useful?</p>
</blockquote>

<p>that produces a different kind of specialization.</p>

<p>you go deep somewhere.</p>

<p>then add adjacent capabilities that multiply the usefulness of that depth.</p>

<p>eventually, the combination itself becomes unusual.</p>

<hr />

<h1 id="compounding-starts-before-it-becomes-visible">compounding starts before it becomes visible</h1>

<p>the most confusing part of all this is that none of it looks particularly impressive at the beginning.</p>

<p>learning a skill doesn’t immediately change your life.</p>

<p>writing ten articles doesn’t guarantee an audience.</p>

<p>meeting twenty interesting people doesn’t guarantee an opportunity.</p>

<p>building a small tool doesn’t guarantee users.</p>

<p>reading deeply about a subject doesn’t guarantee a better career.</p>

<p>the returns are often delayed.</p>

<p>but delay is not the same thing as absence.</p>

<p>knowledge accumulated today can make tomorrow’s learning faster.</p>

<p>a reputation developed today can make next year’s introduction easier.</p>

<p>an audience built today can reduce the cost of distributing future work.</p>

<p>a system created today can remove repetitive work for years.</p>

<p>relationships developed today can surface opportunities long after the original conversation.</p>

<p>this is what compounding looks like when the asset isn’t money.</p>

<p>the previous result changes the conditions under which the next result is produced.</p>

<p>this is compounding.</p>

<p>it does not have to involve money.</p>

<p>a useful way to think about it is:</p>

<p>today’s work
      ↓
something remains
      ↓
that something improves tomorrow’s work
      ↓
tomorrow creates another asset
      ↓
the assets interact
      ↓
future work becomes more valuable</p>

<p>the first few iterations are often boring.</p>

<p>then the accumulated effects begin interacting.</p>

<p>that is when the curve starts to look different.</p>

<hr />

<h1 id="luck-needs-somewhere-to-land">luck needs somewhere to land</h1>

<p>some career-changing events are genuinely random.</p>

<p>you meet the right person.</p>

<p>someone discovers your work.</p>

<p>a technology suddenly becomes important.</p>

<p>a market appears.</p>

<p>a company needs exactly the skills you happen to have.</p>

<p>there is no reliable way to schedule these events.</p>

<p>but you can influence how often you are exposed to them.</p>

<p>publish more things.</p>

<p>build more things.</p>

<p>talk to more interesting people.</p>

<p>try unfamiliar projects.</p>

<p>enter new communities.</p>

<p>learn technologies before you absolutely need them.</p>

<p>each action creates another possible path.</p>

<p>most paths lead nowhere.</p>

<p>a few lead somewhere useful.</p>

<p>occasionally, one changes everything.</p>

<p>from the outside, the final event looks like luck.</p>

<p>what is invisible is the number of doors the person had been opening for years.</p>

<p>luck is random.</p>

<p>exposure to luck is not.</p>

<hr />

<h1 id="the-real-advantage-is-trajectory">the real advantage is trajectory</h1>

<p>this is why two people can work equally hard and still drift apart.</p>

<p>one person’s work is mostly consumed by the present.</p>

<p>the other person’s work is gradually building a structure around the future.</p>

<p>they are accumulating things that remain useful:</p>

<p>skills</p>

<p>systems</p>

<p>knowledge</p>

<p>relationships</p>

<p>reputation</p>

<p>distribution</p>

<p>products</p>

<p>capital</p>

<p>none of these guarantees success.</p>

<p>but each one changes what becomes possible next.</p>

<p>and that is the part that matters.</p>

<p>a career is not just a sequence of jobs.</p>

<p>it is a sequence of increasingly constrained or increasingly expanded possibilities.</p>

<p>if every year requires starting from the same position, progress remains difficult.</p>

<p>if every year begins with more knowledge, more credibility, better relationships, better tools, and more options, the next year becomes structurally different from the previous one.</p>

<p>the goal is therefore not to make every hour maximally productive.</p>

<p>that is too local a metric.</p>

<p>the better question is:</p>

<blockquote>
  <p>what will this work make easier, cheaper, faster, or possible later?</p>
</blockquote>

<p>sometimes the answer will be nothing.</p>

<p>that’s unavoidable.</p>

<p>sometimes the answer will be something small.</p>

<p>and occasionally, the answer will be a completely different career.</p>

<p>those occasional outcomes are enough.</p>

<p>because you don’t need every decision to work.</p>

<p>you need enough of them to change the trajectory.</p>

<p>and once the trajectory changes, the work starts looking different too.</p>

<p>not because you suddenly became more disciplined.</p>

<p>not because you found a secret productivity system.</p>

<p>but because the things you built yesterday are finally participating in what happens tomorrow.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[the uncomfortable reason some careers compound while others quietly stay linear.]]></summary></entry><entry><title type="html">rethinking rag for agentic search</title><link href="https://bhavesh-g.github.io/meta/2026/06/27/rethinking-rag-for-agentic-search.html" rel="alternate" type="text/html" title="rethinking rag for agentic search" /><published>2026-06-27T00:00:00+00:00</published><updated>2026-06-27T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/06/27/rethinking-rag-for-agentic-search</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/06/27/rethinking-rag-for-agentic-search.html"><![CDATA[<blockquote>
  <p>why top-k retrieval is the wrong abstraction for agents</p>
</blockquote>

<h2 id="modern-rag-has-an-architectural-assumption">modern rag has an architectural assumption</h2>

<p>for years, rag has been built around a simple assumption.</p>

<p>modern llms can debug code, orchestrate tools, write production
software, reason over long execution traces, and iteratively refine
plans. yet whenever they need external knowledge, we still route them
through a single retrieval api.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">results</span> <span class="o">=</span> <span class="n">retriever</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">top_k</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</code></pre></div></div>

<p>this looks harmless.</p>

<p>it is also the most opinionated interface in the entire stack.</p>

<p>the retriever decides what the llm is allowed to observe before
reasoning even begins.</p>

<p>that assumption made sense when retrieval was the strongest component in
the system.</p>

<p>today, reasoning is rapidly becoming the stronger component.</p>

<p>the architecture, however, has barely changed.</p>

<h2 id="why-rag-became-the-default">why rag became the default</h2>

<p>rag was never the goal.</p>

<p>it was an optimization.</p>

<p>engineering workspaces are huge. source code, design docs, runbooks,
wikis, slack discussions, postmortems and tickets cannot fit into an llm
context window.</p>

<p>retrieval solved this by aggressively reducing the search space.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>workspace

|

embedding

|

vector search

|

top k chunks

|

llm
</code></pre></div></div>

<p>for years this was exactly the right tradeoff.</p>

<p>reasoning was expensive.</p>

<p>context windows were small.</p>

<p>vector search was fast.</p>

<p>there was little reason to question the abstraction.</p>

<h2 id="retrieval-is-an-api-not-just-an-algorithm">retrieval is an api, not just an algorithm</h2>

<p>thinking about retrieval as a model hides the more interesting
observation.</p>

<p>retrieval is an interface.</p>

<p>every interface determines what information is available to downstream
consumers.</p>

<p>the retrieval interface exposes exactly one capability.</p>

<p>given a query, return the best ranked chunks.</p>

<p>it does not expose uncertainty.</p>

<p>it does not expose alternative search paths.</p>

<p>it does not expose near misses.</p>

<p>it does not allow iterative narrowing before information is discarded.</p>

<p>that means the llm never reasons over the corpus.</p>

<p>it reasons over whatever survives retrieval.</p>

<p>this is an architectural constraint rather than a reasoning limitation.</p>

<h2 id="retrieval-compresses-information">retrieval compresses information</h2>

<p>modern rag pipelines repeatedly compress information.</p>

<p>workspace</p>

<p>-&gt; chunking</p>

<p>-&gt; embeddings</p>

<p>-&gt; nearest neighbour search</p>

<p>-&gt; top k</p>

<p>-&gt; llm</p>

<p>every stage removes information.</p>

<p>chunking removes surrounding context.</p>

<p>embeddings remove lexical precision.</p>

<p>ranking discards candidates.</p>

<p>top k discards everything else.</p>

<p>none of these choices are incorrect.</p>

<p>they are practical engineering optimizations.</p>

<p>but every optimization introduces an abstraction boundary.</p>

<h2 id="what-changes-for-agentic-systems">what changes for agentic systems</h2>

<p>traditional rag assumes a linear execution model.</p>

<p>question</p>

<p>-&gt; retrieve</p>

<p>-&gt; reason</p>

<p>-&gt; answer</p>

<p>agents rarely operate this way.</p>

<p>they search.</p>

<p>inspect.</p>

<p>form hypotheses.</p>

<p>revise.</p>

<p>verify.</p>

<p>search again.</p>

<p>their execution resembles software debugging more than document
retrieval.</p>

<p>this is already visible in coding agents like claude code, codex, aider
and openhands.</p>

<p>they spend much of their execution inside terminal tools.</p>

<p>they search.</p>

<p>inspect.</p>

<p>trace.</p>

<p>repeat.</p>

<p>the search process itself becomes part of reasoning.</p>

<h2 id="who-owns-the-search-policy">who owns the search policy</h2>

<p>this is the inversion proposed by the paper.</p>

<p>historically, the retriever owned the search policy.</p>

<p>the llm simply consumed whatever was returned.</p>

<p>direct corpus interaction flips that ownership.</p>

<p>the llm now owns the search policy.</p>

<p>terminal commands become primitive operations rather than complete
retrieval solutions.</p>

<p>rg.</p>

<p>grep.</p>

<p>find.</p>

<p>sed.</p>

<p>cat.</p>

<p>head.</p>

<p>tail.</p>

<p>individually they are extremely simple.</p>

<p>collectively they become building blocks for adaptive search.</p>

<h2 id="composability-beats-fixed-retrieval">composability beats fixed retrieval</h2>

<p>consider two interfaces.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>retrieve(query)
</code></pre></div></div>

<p>versus</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg
find
grep
sed
cat
python
</code></pre></div></div>

<p>the first exposes one operation.</p>

<p>the second exposes primitives.</p>

<p>this is reminiscent of the unix philosophy.</p>

<p>small deterministic tools.</p>

<p>simple interfaces.</p>

<p>arbitrary composition.</p>

<p>the intelligence does not live inside grep.</p>

<p>it emerges from how the llm composes these primitives.</p>

<p>an investigation might look like</p>

<p>rg “kv cache”</p>

<p>-&gt; discover paged attention</p>

<p>-&gt; inspect implementation</p>

<p>-&gt; discover continuous batching</p>

<p>-&gt; search again</p>

<p>notice something subtle.</p>

<p>none of these searches attempted to answer the original question.</p>

<p>each search attempted to discover the next search.</p>

<p>the search strategy is synthesized online.</p>

<h2 id="retrieval-interface-resolution">retrieval interface resolution</h2>

<p>the deepest idea is retrieval interface resolution.</p>

<p>traditional retrieval exposes documents or chunks.</p>

<p>direct interaction exposes symbols, functions, lines, neighbouring
context and exact lexical matches.</p>

<p>resolution is about control rather than volume.</p>

<p>higher resolution allows an agent to progressively narrow its
investigation instead of repeatedly asking for another ranked list.</p>

<p>the abstraction becomes interactive rather than transactional.</p>

<h2 id="why-this-matters">why this matters</h2>

<p>the surprising result is not simply higher benchmark scores.</p>

<p>the interesting observation is that better performance often does not
come from surfacing more relevant documents.</p>

<p>it comes from extracting more value after reaching a relevant document.</p>

<p>once the agent reaches useful evidence it performs local exploration.</p>

<p>inspect.</p>

<p>extract entity.</p>

<p>search again.</p>

<p>verify.</p>

<p>continue.</p>

<p>that behaviour is difficult to express through a fixed retrieve api.</p>

<h2 id="where-this-breaks-down">where this breaks down</h2>

<p>none of this means retrieval disappears.</p>

<p>search primitives optimise search depth.</p>

<p>retrievers optimise search breadth.</p>

<p>as corpora become extremely large, exhaustive terminal exploration
becomes expensive.</p>

<p>tool calls increase.</p>

<p>latency grows.</p>

<p>cost rises.</p>

<p>there is still a regime where retrieval remains the better engineering
tradeoff.</p>

<p>the point is not replacing retrieval.</p>

<p>the point is questioning whether top k retrieval should remain the only
interface between reasoning and information.</p>

<h2 id="takeaway">takeaway</h2>

<p>the contribution is bigger than replacing embeddings with grep.</p>

<p>it reframes retrieval as an interface design problem.</p>

<p>for years we pushed intelligence into retrievers.</p>

<p>agentic systems suggest pushing intelligence into orchestration instead.</p>

<p>once the llm becomes the strongest reasoning component in the stack,
perhaps it should own search as well.</p>

<p>Original Paper: <a href="https://arxiv.org/abs/2605.05242">Beyond Semantic Similarity: Rethinking Retrieval for Agentic Search via Direct Corpus Interaction</a></p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[why top-k retrieval is the wrong abstraction for agents]]></summary></entry><entry><title type="html">most ai systems are just memory problems disguised as intelligence</title><link href="https://bhavesh-g.github.io/meta/2026/05/15/most-ai-systems-are-just-memory-problems-disguised-as-intelligence.html" rel="alternate" type="text/html" title="most ai systems are just memory problems disguised as intelligence" /><published>2026-05-15T00:00:00+00:00</published><updated>2026-05-15T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/05/15/most-ai-systems-are-just-memory-problems-disguised-as-intelligence</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/05/15/most-ai-systems-are-just-memory-problems-disguised-as-intelligence.html"><![CDATA[<blockquote>
  <p>modern ai abstractions are extremely convincing.</p>
</blockquote>

<p>from tokenization to datacenter cooling, the entire inference stack is basically an optimization war against physics.</p>

<p>you send text into an api.<br />
a few milliseconds later, coherent language comes back out.</p>

<p>the interface is polished enough that people start imagining the model as some continuously reasoning entity sitting behind the endpoint.</p>

<p>in reality, most production inference systems are closer to numerical factories than digital minds.</p>

<p>underneath every chatbot sits a pipeline that:</p>
<ul>
  <li>converts text into integers</li>
  <li>converts integers into vectors</li>
  <li>repeatedly transforms those vectors through matrix operations</li>
  <li>predicts the statistically most likely next token</li>
</ul>

<p>the interesting part is not just the math.</p>

<p>it is the chain of engineering constraints that shaped the entire stack.</p>

<p>because every layer in modern llm infrastructure exists to solve a limitation introduced by the previous one:</p>
<ul>
  <li>tokenizers exist because models cannot process text</li>
  <li>embeddings exist because integers carry no semantic structure</li>
  <li>attention exists because older architectures struggled with long-range dependencies</li>
  <li>kv cache exists because autoregressive generation wastes computation</li>
  <li>quantization exists because memory movement becomes the bottleneck</li>
  <li>liquid cooling exists because large-scale computation eventually becomes a thermodynamics problem</li>
</ul>

<p>once you follow the execution flow carefully, most of the “magic” starts collapsing into systems engineering.</p>

<hr />

<h1 id="the-model-starts-as-a-file-full-of-learned-numbers">the model starts as a file full of learned numbers</h1>

<p>when you download a modern language model, most of the size comes from files like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>model.safetensors
</code></pre></div></div>

<p>despite the branding around ai, this file is fundamentally just structured numerical storage.</p>

<p>inside it are tensors.</p>

<p>a tensor is simply a container for numbers:</p>
<ul>
  <li>a list</li>
  <li>a table</li>
  <li>or a higher-dimensional numerical grid</li>
</ul>

<p>machine learning vocabulary makes tensors sound more exotic than they are.</p>

<p>operationally, the file mostly contains weights:</p>
<ul>
  <li>billions of floating point values</li>
  <li>adjusted gradually during training</li>
  <li>used later to influence future calculations</li>
</ul>

<p>older pytorch checkpoints relied on pickle serialization through <code class="language-plaintext highlighter-rouge">.bin</code> files.<br />
pickle could reconstruct arbitrary python objects during loading, which also meant loading a model could potentially execute arbitrary code.</p>

<p><code class="language-plaintext highlighter-rouge">.safetensors</code> exists because model loading eventually needed to become:</p>
<ul>
  <li>predictable</li>
  <li>memory-efficient</li>
  <li>non-executable</li>
</ul>

<p>so the format became intentionally restrictive:</p>
<ul>
  <li>tensor metadata</li>
  <li>raw numerical bytes</li>
  <li>no embedded execution logic</li>
</ul>

<p>the important part is this:</p>

<p>nothing inside the file resembles:</p>
<ul>
  <li>grammar rules</li>
  <li>symbolic reasoning</li>
  <li>explicit knowledge structures</li>
</ul>

<p>the model stores numerical relationships, not human-readable understanding.</p>

<hr />

<h1 id="neural-networks-cannot-process-text-directly">neural networks cannot process text directly</h1>

<p>models do not understand words.</p>

<p>they only process numbers.</p>

<p>so before inference begins, text must first become numerical identifiers.</p>

<p>this is the tokenizer’s job.</p>

<hr />

<h1 id="tokenization-is-a-compression-system-disguised-as-language-processing">tokenization is a compression system disguised as language processing</h1>

<p>a tokenizer creates a mapping between text fragments and integer ids.</p>

<p>for example:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="s">"system"</span><span class="p">:</span> <span class="mi">345</span><span class="p">,</span>
  <span class="s">"observ"</span><span class="p">:</span> <span class="mi">8763</span><span class="p">,</span>
  <span class="s">"ability"</span><span class="p">:</span> <span class="mi">992</span><span class="p">,</span>
  <span class="s">"critical"</span><span class="p">:</span> <span class="mi">2042</span>
<span class="p">}</span>
</code></pre></div></div>

<p>tokens are not always complete words.</p>

<p>modern tokenizers usually split text into reusable fragments because storing every possible word would create an impractically large vocabulary.</p>

<p>so:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>observability
</code></pre></div></div>

<p>may become:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="s">"observ"</span><span class="p">,</span> <span class="s">"ability"</span><span class="p">]</span>
</code></pre></div></div>

<p>this allows related words to reuse components:</p>
<ul>
  <li>observable</li>
  <li>observability</li>
  <li>observer</li>
</ul>

<p>all partially overlap.</p>

<p>which means tokenization is solving two problems simultaneously:</p>
<ul>
  <li>representing language</li>
  <li>compressing vocabulary efficiently</li>
</ul>

<p>the tokenizer therefore shapes:</p>
<ul>
  <li>context efficiency</li>
  <li>multilingual behavior</li>
  <li>inference cost</li>
  <li>code generation quality</li>
</ul>

<p>bad tokenization leaks inefficiency into the entire model pipeline.</p>

<hr />

<h1 id="the-tokenizer-and-the-model-are-tightly-coupled">the tokenizer and the model are tightly coupled</h1>

<p>this relationship is critical.</p>

<p>the tokenizer determines:</p>
<ul>
  <li>which token maps to which integer id</li>
</ul>

<p>the model then trains against those exact ids.</p>

<p>if:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"system"</span> <span class="err">→</span> <span class="mi">345</span>
</code></pre></div></div>

<p>then row <code class="language-plaintext highlighter-rouge">345</code> inside the model’s embedding table gradually learns the statistical representation associated with <code class="language-plaintext highlighter-rouge">"system"</code>.</p>

<p>changing the tokenizer changes the mapping.</p>

<p>which means the model would retrieve the wrong internal representation entirely.</p>

<p>this is why checkpoints always ship with:</p>
<ul>
  <li>tokenizer configuration</li>
  <li>model weights</li>
</ul>

<p>together.</p>

<p>they are components of the same learned system.</p>

<hr />

<h1 id="tokenizer-training-happens-before-model-training">tokenizer training happens before model training</h1>

<p>before the model itself learns anything, engineers first train the tokenizer.</p>

<p>they feed enormous text corpora into tokenization algorithms:</p>
<ul>
  <li>books</li>
  <li>forums</li>
  <li>source code</li>
  <li>websites</li>
  <li>documentation</li>
</ul>

<p>the tokenizer searches for reusable fragments that maximize efficiency.</p>

<p>suppose the dataset repeatedly contains:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>monitor
monitoring
monitored
monitors
</code></pre></div></div>

<p>the tokenizer may conclude that <code class="language-plaintext highlighter-rouge">"monitor"</code> should exist as a reusable token because it appears frequently across multiple contexts.</p>

<p>so it creates:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"monitor"</span> <span class="err">→</span> <span class="mi">5421</span>
</code></pre></div></div>

<p>later during model training, token <code class="language-plaintext highlighter-rouge">5421</code> repeatedly appears in relevant contexts, allowing the model to gradually learn useful statistical patterns around it.</p>

<p>this entire process exists because vocabulary size is a hard constraint.</p>

<p>larger vocabularies:</p>
<ul>
  <li>increase memory usage</li>
  <li>increase embedding size</li>
  <li>reduce efficiency</li>
</ul>

<p>so tokenization becomes an optimization problem before the model even exists.</p>

<hr />

<h1 id="token-ids-still-carry-no-meaning">token ids still carry no meaning</h1>

<p>after tokenization, text becomes integers:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="mi">345</span><span class="p">,</span> <span class="mi">8763</span><span class="p">,</span> <span class="mi">992</span><span class="p">,</span> <span class="mi">2042</span><span class="p">]</span>
</code></pre></div></div>

<p>but integers themselves are semantically empty.</p>

<p><code class="language-plaintext highlighter-rouge">345</code> does not inherently mean <code class="language-plaintext highlighter-rouge">"system"</code>.</p>

<p>it is simply an index.</p>

<p>the next problem becomes:</p>

<blockquote>
  <p>how do you convert token ids into representations the model can actually reason over numerically?</p>
</blockquote>

<p>this is where embeddings enter the pipeline.</p>

<hr />

<h1 id="embeddings-transform-tokens-into-numerical-representations">embeddings transform tokens into numerical representations</h1>

<p>inside the model exists a large embedding table.</p>

<p>conceptually, it behaves like a spreadsheet:</p>
<ul>
  <li>each row belongs to one token</li>
  <li>each column stores one learned numerical feature</li>
</ul>

<p>if:</p>
<ul>
  <li>vocabulary size = 50,000</li>
  <li>embedding dimension = 768</li>
</ul>

<p>then the embedding table contains:</p>
<ul>
  <li>50,000 rows</li>
  <li>768 numerical values per row</li>
</ul>

<p>during inference, if:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"system"</span> <span class="err">→</span> <span class="mi">345</span>
</code></pre></div></div>

<p>the framework retrieves:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">embedding_table</span><span class="p">[</span><span class="mi">345</span><span class="p">]</span>
</code></pre></div></div>

<p>which returns something like:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="o">-</span><span class="mf">0.12</span><span class="p">,</span> <span class="mf">0.88</span><span class="p">,</span> <span class="mf">0.43</span><span class="p">,</span> <span class="p">...]</span>
</code></pre></div></div>

<p>this vector becomes the model’s internal representation of the token.</p>

<p>the important detail is that these dimensions are not manually labeled.</p>

<p>there is no column representing:</p>
<ul>
  <li>professionalism</li>
  <li>databases</li>
  <li>positivity</li>
  <li>syntax</li>
</ul>

<p>the structure emerges statistically during training.</p>

<p>initially these vectors are mostly random numbers.</p>

<p>over billions of training examples, tokens appearing in similar contexts gradually develop related numerical patterns.</p>

<p>the model never learns definitions.</p>

<p>it learns proximity and correlation.</p>

<hr />

<h1 id="transformers-exist-because-older-sequence-models-struggled-with-context">transformers exist because older sequence models struggled with context</h1>

<p>before transformers, sequence models like rnns and lstms processed text sequentially:</p>
<ul>
  <li>one token after another</li>
  <li>carrying forward compressed internal state</li>
</ul>

<p>this worked for short sequences.</p>

<p>it worked poorly for long ones.</p>

<p>information from earlier tokens gradually degraded as sequences became larger.</p>

<p>which meant relationships between distant words became difficult to preserve.</p>

<p>transformers changed this by introducing attention.</p>

<p>instead of compressing history into a single evolving state, attention allows tokens to directly compare themselves against other relevant tokens in the sequence.</p>

<p>given:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>the database server crashed because it ran out of memory
</code></pre></div></div>

<p>when processing <code class="language-plaintext highlighter-rouge">"memory"</code>, the model can directly evaluate relevance against:</p>
<ul>
  <li>database</li>
  <li>server</li>
  <li>crashed</li>
</ul>

<p>instead of hoping those relationships survived multiple sequential transformations.</p>

<p>attention mechanisms repeatedly:</p>
<ul>
  <li>compare vectors</li>
  <li>compute similarity scores</li>
  <li>combine contextual information</li>
  <li>forward updated representations deeper into the network</li>
</ul>

<p>this architecture dramatically improved long-range dependency handling, which is one of the main reasons transformers displaced older sequence models so aggressively.</p>

<hr />

<h1 id="autoregressive-generation-creates-a-computational-scaling-problem">autoregressive generation creates a computational scaling problem</h1>

<p>llms generate text one token at a time.</p>

<p>suppose the model generates:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>the server crashed because
</code></pre></div></div>

<p>to predict the next token, the model processes the entire sequence.</p>

<p>after generating another token, it processes the now longer sequence again.</p>

<p>without optimization, generating token 500 would repeatedly recompute large portions of tokens 1–499 again and again.</p>

<p>the redundancy becomes enormous.</p>

<p>this is why autoregressive inference becomes expensive very quickly for long contexts.</p>

<hr />

<h1 id="kv-caching-exists-because-recomputation-is-wasteful">kv caching exists because recomputation is wasteful</h1>

<p>during attention, tokens produce internal representations called:</p>
<ul>
  <li>keys</li>
  <li>values</li>
</ul>

<p>once computed, these representations do not change.</p>

<p>so recalculating them repeatedly during generation is unnecessary.</p>

<p>kv caching stores them directly in gpu memory and reuses them across future generation steps.</p>

<p>instead of recomputing the entire sequence every time, the model only processes the newest token.</p>

<p>this dramatically reduces compute overhead.</p>

<p>the tradeoff is memory pressure.</p>

<p>longer conversations require larger kv caches, which is why:</p>
<ul>
  <li>long context windows consume large amounts of vram</li>
  <li>concurrent inference becomes expensive</li>
  <li>serving llms at scale rapidly becomes infrastructure-heavy</li>
</ul>

<p>optimization simply relocates the constraint.</p>

<hr />

<h1 id="eventually-inference-becomes-a-memory-bandwidth-problem">eventually inference becomes a memory bandwidth problem</h1>

<p>most people assume llm inference is constrained primarily by compute.</p>

<p>in reality, modern inference is often constrained by memory movement.</p>

<p>a 7-billion-parameter model stored in fp32 requires roughly:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>7 billion × 4 bytes ≈ 28GB
</code></pre></div></div>

<p>during inference, large portions of those weights continuously move between:</p>
<ul>
  <li>gpu memory</li>
  <li>memory controllers</li>
  <li>compute units</li>
</ul>

<p>throughput therefore becomes limited by:</p>

<blockquote>
  <p>how fast hardware can move data</p>
</blockquote>

<p>not necessarily:</p>

<blockquote>
  <p>how fast it can multiply numbers</p>
</blockquote>

<p>this distinction matters because once memory bandwidth becomes dominant, architectural priorities shift completely.</p>

<hr />

<h1 id="quantization-exists-because-moving-less-data-is-faster">quantization exists because moving less data is faster</h1>

<p>quantization reduces numerical precision to shrink model size.</p>

<p>instead of storing weights as 32-bit floating point values, weights may be compressed into:</p>
<ul>
  <li>8-bit integers</li>
  <li>4-bit formats</li>
  <li>mixed precision representations</li>
</ul>

<p>this reduces:</p>
<ul>
  <li>memory usage</li>
  <li>bandwidth pressure</li>
  <li>inference latency</li>
</ul>

<p>while preserving most practical inference quality.</p>

<p>neural networks are statistically tolerant systems.</p>

<p>they usually do not require perfect decimal precision to preserve broader learned relationships.</p>

<p>quantization therefore became operationally necessary because hardware transfer costs eventually dominate inference economics.</p>

<hr />

<h1 id="large-scale-ai-infrastructure-is-increasingly-constrained-by-physics">large-scale ai infrastructure is increasingly constrained by physics</h1>

<p>once enough gpus participate in training or inference, software stops being the only concern.</p>

<p>modern accelerators consume enormous amounts of power because large-scale matrix operations are physically expensive.</p>

<p>high-density gpu servers routinely draw:</p>
<ul>
  <li>hundreds of watts per gpu</li>
  <li>multiple kilowatts per rack</li>
  <li>megawatts across large clusters</li>
</ul>

<p>all of that energy eventually becomes heat.</p>

<p>which means modern ai datacenters increasingly optimize:</p>
<ul>
  <li>liquid cooling</li>
  <li>airflow engineering</li>
  <li>rack density</li>
  <li>thermal isolation</li>
  <li>power delivery systems</li>
</ul>

<p>even machine placement matters.</p>

<p>distributed training constantly transfers tensors between gpus across high-speed interconnects. once clusters become large enough, network topology and interconnect latency materially affect training efficiency.</p>

<p>at sufficient scale, ai infrastructure starts resembling industrial power engineering as much as software engineering.</p>

<p>because every generated token ultimately depends on:</p>
<ul>
  <li>electricity delivery</li>
  <li>memory transfer</li>
  <li>thermal dissipation</li>
  <li>physical hardware limits</li>
</ul>

<hr />

<h1 id="once-the-execution-flow-becomes-visible-the-abstraction-stops-looking-magical">once the execution flow becomes visible, the abstraction stops looking magical</h1>

<p>the inference pipeline is mechanically straightforward:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>text
→ tokenization
→ token ids
→ embedding lookup
→ vector transformations
→ attention calculations
→ probability prediction
→ next token
→ repeat
</code></pre></div></div>

<p>everything else in modern ai systems exists to optimize this loop:</p>
<ul>
  <li>reduce recomputation</li>
  <li>compress memory footprint</li>
  <li>maximize bandwidth efficiency</li>
  <li>parallelize tensor operations</li>
  <li>manage thermal constraints</li>
  <li>improve hardware utilization</li>
</ul>

<p>which is why production ai engineering increasingly sits at the intersection of:</p>
<ul>
  <li>distributed systems</li>
  <li>hardware architecture</li>
  <li>numerical optimization</li>
  <li>infrastructure economics</li>
  <li>thermodynamics</li>
</ul>

<p>because eventually every “intelligent” response still has to survive contact with physics.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[modern ai abstractions are extremely convincing.]]></summary></entry><entry><title type="html">the unavoidable evolution of identity systems</title><link href="https://bhavesh-g.github.io/meta/2026/04/25/the-unavoidable-evolution-of-identity-systems.html" rel="alternate" type="text/html" title="the unavoidable evolution of identity systems" /><published>2026-04-25T00:00:00+00:00</published><updated>2026-04-25T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/04/25/the-unavoidable-evolution-of-identity-systems</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/04/25/the-unavoidable-evolution-of-identity-systems.html"><![CDATA[<blockquote>
  <p>state, scale, and cached lies.</p>
</blockquote>

<p>we argue about tokens when we should be arguing about topology. here is how your architecture actually forces your hand.</p>

<p>most identity writing starts too late.</p>

<p>by the time your engineering team is locked in a meeting room debating the merits of jwts against opaque tokens, the most critical architectural constraints have already been locked in. the servers are distributed. the latency budgets are tight. security is screaming for instant revocation while product is demanding a seamless, invisible login experience.</p>

<p>identity is not a menu of isolated design patterns you pick from. it is a sequence of forced responses to friction. every new layer in your stack exists solely because the simpler model before it buckled under the weight of scale.</p>

<p>if you want to understand why our systems look the way they do, you have to watch them break.</p>

<h3 id="the-friction-of-compute">the friction of compute</h3>

<p>think back to the simplest version of your app. one backend server, one database. a request comes in, the server hashes the provided password, checks it against the database, and serves the payload. the system that knows the truth is the exact same system enforcing it. identity is not a domain here; it is just a sql query.</p>

<p>but you hit your first wall almost immediately. cryptographic password hashing is intentionally slow. running a bcrypt validation on every single http request will melt your cpu. asking a user for multi-factor authentication on every click will ruin your product.</p>

<p>so, you compromise. you decide to perform the expensive mathematical proof exactly once, record the result in memory, and hand the client a random string to represent that successful check.</p>

<p>you just invented the session.</p>

<p>the cookie is not the identity. it is just a pointer to a cached decision. you traded compute cycles for memory space, and it works perfectly… until you need a second server.</p>

<h3 id="the-friction-of-state">the friction of state</h3>

<p>traffic spikes. you put ten application instances behind a load balancer. suddenly, a user logs in on node a, but their next api call lands on node b. node b has no idea who they are because the session state is trapped in node a’s memory. continuity shatters.</p>

<p>the obvious engineering fix is to pull that state out of the application and put it into a shared central store like redis. now, any node can verify the session.</p>

<p>but look at what you just did to your architecture. your identity store is now sitting directly in the critical path of every single request. if your api spans multiple regions, latency creeps up as nodes reach across the country to verify a session. if the cache cluster blips, the entire platform goes down. you solved the memory isolation problem, but you created a massive central bottleneck.</p>

<p>in distributed systems, local decisions are cheap, but central coordination is expensive. the next evolution has to preserve trust without making a network call.</p>

<h3 id="the-friction-of-network-and-the-cached-lie">the friction of network and the cached lie</h3>

<p>if your microservices cannot afford to constantly phone home to a central database, the proof of trust must travel alongside the request itself. you need a way for the auth server to make a statement that any downstream service can verify completely offline.</p>

<p>this is exactly how jwt enters your stack.</p>

<p>you package the user claims, sign them with a private key, and hand the package to the client. when a microservice receives that token, it does not look it up in a database. it simply runs a deterministic mathematical check using a public key. if the math checks out, the token is trusted. you just eliminated the central bottleneck.</p>

<p>but then a real-world scenario hits. it is 2:00 pm, and a rogue employee is terminated. hr deletes their account. but their jwt was issued at 1:00 pm and does not expire until 5:00 pm. the token is cryptographically flawless, but operationally toxic. the ex-employee spends the next three hours pulling internal repositories.</p>

<p>this is the hard lesson of stateless tokens. a signature only proves the token was issued correctly in the past. it guarantees integrity, not freshness. a jwt is essentially a cached lie with an expiration date. you are now stuck managing this gap, balancing agonizingly short token lifetimes against complex refresh flows, just to simulate the instant revocation you used to get for free with sessions.</p>

<h3 id="the-friction-of-trust-boundaries">the friction of trust boundaries</h3>

<p>eventually, your application becomes a platform. a third-party partner wants to build an integration that syncs user data.</p>

<p>historically, users solved this by just giving the partner their raw password. that is a structural disaster. a password is absolute authority. you cannot scope it, you cannot restrict it to “read-only,” and sharing it collapses your entire security perimeter.</p>

<p>you need a valet key. you need a way to grant a limited, highly specific capability without transferring primary credentials.</p>

<p>this forces the adoption of oauth. it fundamentally separates the proof of who you are from what you are allowed to do. the user authenticates with your central server, and the third party receives a token that only allows them to read calendar events for the next hour. you stop sharing identity secrets and start issuing constrained capabilities.</p>

<h3 id="the-friction-of-sprawl">the friction of sprawl</h3>

<p>fast forward a few years. your company has grown. you now have an internal wiki, an expense portal, a ci/cd dashboard, and a dozen other tools.</p>

<p>if every single one of those applications maintains its own local user database and login screen, the operational overhead becomes crushing. when an engineer leaves the company, it has to manually hunt down and revoke access across twenty different systems. a single missed account is a critical security breach waiting to happen.</p>

<p>you realize that managing identity per application does not scale. you have to centralize authentication the same way you centralized your logging or your network routing.</p>

<p>this is where sso becomes inevitable. you abstract the authentication layer entirely. the internal apps no longer care about passwords; they just trust the central identity provider. the visible benefit to the user is fewer login prompts. the actual engineering reality is a single, unified control plane for risk.</p>

<h3 id="designing-for-reality">designing for reality</h3>

<p>sessions, stateless tokens, delegated access, and federation are not competing ideologies. they are an evolutionary chain. they solve the distinct consequences of scaling up compute, state, network, and organizational sprawl.</p>

<p>the hard part of identity architecture is not proving who a user is on the first click. the hard part is deciding where the truth lives, how long you are willing to let downstream systems cache it, and exactly how much latency you are willing to pay to revoke it when things go wrong.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[state, scale, and cached lies. we argue about tokens when we should be arguing about topology. here is how your architecture actually forces your hand.]]></summary></entry><entry><title type="html">future belongs to engineers who control complexity</title><link href="https://bhavesh-g.github.io/meta/2026/02/10/the-future-belongs-to-engineers-who-control-complexity.html" rel="alternate" type="text/html" title="future belongs to engineers who control complexity" /><published>2026-02-10T00:00:00+00:00</published><updated>2026-02-10T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/02/10/the-future-belongs%20to%20engineers-who-control-complexity</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/02/10/the-future-belongs-to-engineers-who-control-complexity.html"><![CDATA[<blockquote>
  <p>ai did not change engineering. it removed the excuses.</p>
</blockquote>

<p>i have written systems where a small mistake took hours to trace.</p>

<p>i have also shipped work where most of the code was generated and reviewed only at the boundaries.</p>

<p>output increased, hence the need for judgment increased faster.</p>

<p>that is the shift.</p>

<hr />

<h2 id="typing-was-never-the-constraint">typing was never the constraint</h2>

<p>there is a lot of noise about ai writing code.</p>

<p>writing code was never the hard part.</p>

<p>the hard parts are:</p>

<ul>
  <li>making unclear requirements precise</li>
  <li>choosing tradeoffs when every option has cost</li>
  <li>understanding system behavior under failure</li>
  <li>knowing the impact of a small change</li>
  <li>keeping complexity under control</li>
</ul>

<p>example.</p>

<p>a product asks for “real time notifications”.</p>

<p>implementation is easy. websockets. pubsub.</p>

<p>the real questions are:</p>

<p>how many connections per user<br />
what happens when the service restarts<br />
do we guarantee delivery<br />
how do we prevent duplicate events<br />
what is the acceptable delay</p>

<p>ai can write the socket handler.</p>

<p>it cannot define the system contract.</p>

<p>in most production environments, engineers already spend more time thinking, aligning, and debugging than typing.</p>

<p>ai accelerates implementation.</p>

<p>engineering is not implementation.</p>

<hr />

<h2 id="what-ai-actually-gives-you">what ai actually gives you</h2>

<p>in real usage, the value is straightforward:</p>

<ul>
  <li>fast generation of standard patterns</li>
  <li>removal of repetitive work</li>
  <li>quick exploration of alternatives</li>
  <li>assistance with local reasoning</li>
</ul>

<p>this removes a large amount of mechanical effort.</p>

<p>boilerplate<br />
crud layers<br />
test scaffolding<br />
migrations<br />
basic integrations</p>

<p>example.</p>

<p>you need an internal admin panel for managing users.</p>

<p>routes<br />
forms<br />
validation<br />
api wiring</p>

<p>ai can generate the whole thing in minutes.</p>

<p>but then requirements change.</p>

<p>bulk operations<br />
audit logs<br />
role based access<br />
export for compliance</p>

<p>the first version was easy.</p>

<p>the system is where the work begins.</p>

<p>when the cost of building drops, the amount of software requested increases.</p>

<p>every productivity jump in this industry has done the same thing.</p>

<p>capacity expands. demand expands faster.</p>

<hr />

<h2 id="where-the-failure-shows-up">where the failure shows up</h2>

<p>ai produces the statistically likely solution.</p>

<p>systems fail because of the unlikely conditions.</p>

<p>this mismatch appears quickly.</p>

<p><strong>structure degrades</strong></p>

<p>without strict direction, generated code accumulates:</p>
<ul>
  <li>duplication</li>
  <li>inconsistent abstractions</li>
  <li>unclear ownership</li>
</ul>

<p>example.</p>

<p>three features are added over a month.</p>

<p>each generated separately.</p>

<p>now there are three ways to access the same data.</p>

<p>no single owner.</p>

<p>small changes require touching multiple paths.</p>

<p>complexity grows faster because generation is cheap.</p>

<p><strong>defects move to the edges</strong></p>

<p>the common paths work.</p>

<p>edge cases fail.</p>

<p>example.</p>

<p>a payment service works in testing.</p>

<p>in production:</p>

<p>timeout after charge<br />
retry triggers duplicate payment<br />
user is charged twice</p>

<p>the code was correct locally.</p>

<p>the system behavior was wrong.</p>

<p>someone still needs a correct mental model.</p>

<p><strong>no long term intent</strong></p>

<p>when something breaks, the model optimizes for the local fix.</p>

<p>example.</p>

<p>a test fails due to timing.</p>

<p>the generated fix increases timeout.</p>

<p>tests pass.</p>

<p>latency doubles in production.</p>

<p>local success. system regression.</p>

<hr />

<h2 id="the-real-bottleneck-now">the real bottleneck now</h2>

<p>strong engineers are not limited by typing speed.</p>

<p>they are limited by decision quality.</p>

<p>they carry working models of:</p>

<p>dependencies<br />
failure modes<br />
data movement<br />
operational constraints<br />
business impact</p>

<p>example.</p>

<p>a feature requires joining data from two services.</p>

<p>quick solution: synchronous call.</p>

<p>correct solution: async pipeline.</p>

<p>the difference shows up under load.</p>

<p>ai can write either.</p>

<p>it cannot choose based on future traffic patterns.</p>

<p>when code generation becomes cheap, judgment becomes the scarce resource.</p>

<hr />

<h2 id="the-new-gap">the new gap</h2>

<p>there used to be developers who assembled features without understanding the system.</p>

<p>now there will be developers who generate entire systems without understanding them.</p>

<p>they move fast.</p>

<p>until something non obvious fails.</p>

<p>example.</p>

<p>a search feature is generated using full table scans.</p>

<p>works with 10k records.</p>

<p>fails at 10 million.</p>

<p>fixing it later requires redesign, reindexing, and downtime.</p>

<p>speed becomes baseline.</p>

<p>understanding becomes differentiator.</p>

<hr />

<h2 id="demand-will-increase-not-decrease">demand will increase, not decrease</h2>

<p>faster development does not reduce software needs.</p>

<p>it changes expectations.</p>

<p>when building becomes cheap:</p>

<p>one system becomes many variants<br />
defaults become customization<br />
team tools become individual tools</p>

<p>example.</p>

<p>earlier one dashboard served the whole company.</p>

<p>now every team wants:</p>

<p>custom metrics<br />
custom alerts<br />
custom workflows</p>

<p>each version is small.</p>

<p>operational surface area grows quickly.</p>

<p>generated systems still require:</p>

<p>ownership<br />
monitoring<br />
integration<br />
security<br />
cost management<br />
continuous change</p>

<p>building software is the cheapest phase.</p>

<p>operating software is where the cost lives.</p>

<hr />

<h2 id="where-senior-engineers-create-leverage">where senior engineers create leverage</h2>

<p>the value is moving away from implementation.</p>

<p>the leverage is in:</p>

<p>defining system boundaries<br />
maintaining architectural consistency<br />
setting constraints for generated code<br />
reviewing for long term impact<br />
diagnosing failures under uncertainty<br />
aligning technical decisions with business risk</p>

<p>example.</p>

<p>without guardrails, teams generate services directly hitting the database.</p>

<p>short term speed.</p>

<p>long term coupling.</p>

<p>schema change becomes a company wide incident.</p>

<p>senior impact has never been about writing more code.</p>

<p>it has always been about preventing expensive mistakes.</p>

<p>more code generation means more opportunities for expensive mistakes.</p>

<hr />

<h2 id="what-this-means-for-engineers">what this means for engineers</h2>

<p>if your value is based on:</p>

<p>syntax knowledge<br />
framework familiarity<br />
writing standard features</p>

<p>you are competing with automation.</p>

<p>if your value is based on:</p>

<p>system design<br />
tradeoff decisions<br />
debugging under ambiguity<br />
long term ownership</p>

<p>your leverage increases.</p>

<p>the role is shifting.</p>

<p>less time producing code.</p>

<p>more time controlling what enters the system.</p>

<hr />

<h2 id="the-constraint-that-does-not-change">the constraint that does not change</h2>

<p>ai will improve.</p>

<p>larger context<br />
better reasoning<br />
faster generation</p>

<p>but one constraint remains.</p>

<p>when production breaks, someone is accountable.</p>

<p>example.</p>

<p>cpu spikes<br />
latency increases<br />
revenue drops</p>

<p>the question is never:</p>

<blockquote>
  <p>who wrote the code</p>
</blockquote>

<p>the question is:</p>

<blockquote>
  <p>who understands the system</p>
</blockquote>

<p>accountability requires understanding and tools do not carry responsibility.</p>

<p>engineers do.</p>

<hr />

<p>ai is not removing engineers.</p>

<p>it is removing the <code class="language-plaintext highlighter-rouge">value of low judgment work</code>.</p>

<p>when anyone can generate code, the industry stops rewarding people who produce code.</p>

<p>it rewards people who keep systems stable under change.</p>

<p>read that again.</p>

<p>the future belongs to engineers who control complexity.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[ai did not change engineering. it removed the excuses.]]></summary></entry><entry><title type="html">rethinking llm-as-a-judge for real products</title><link href="https://bhavesh-g.github.io/meta/2026/02/04/rethinking-llm-as-a-judge-for-real-products.html" rel="alternate" type="text/html" title="rethinking llm-as-a-judge for real products" /><published>2026-02-04T00:00:00+00:00</published><updated>2026-02-04T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/02/04/rethinking-llm-as-a-judge-for-real-products</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/02/04/rethinking-llm-as-a-judge-for-real-products.html"><![CDATA[<blockquote>
  <p>when preset metrics lie to you</p>
</blockquote>

<p>there is a moment every llm engineer quietly dreads.</p>

<p>a pm asks, “so… did the new ranking logic make answers better?”</p>

<p>the dashboard shows:</p>
<ul>
  <li>rouge-l: +4%</li>
  <li>bleu: +7%</li>
  <li>exact match: unchanged</li>
</ul>

<p>but when you read real conversations, users are confused. support tickets keep climbing. the “improvement” is just noise.</p>

<p>this post is about that gap. why preset metrics fail. and why custom llm-as-a-judge metrics, built around your product’s real definition of good, are the only way out.</p>

<h2 id="why-classic-metrics-feel-right-but-are-wrong">why classic metrics feel right but are wrong</h2>

<p>bleu, rouge, exact match, token f1. they come from a simple world.</p>

<p>small test sets. one ground truth answer. short outputs. goal is similarity to reference.</p>

<p>translation or summarization? fine. llm products? no.</p>

<p>we build systems that explain. reason. obey tone, compliance, safety rules. balance correctness, brevity, style.</p>

<p>rouge cannot spot a legal chatbot breaking policy. bleu misses a code assistant suggesting a vuln. exact match ignores if a summary skips the key clause.</p>

<p>metrics give clean numbers. but deeper the product, less those numbers match what users feel.</p>

<h2 id="llm-products-defy-easy-measurement">llm products defy easy measurement</h2>

<p>three patterns.</p>

<p>rag chatbot for internal docs. users need correct, grounded answers. no hallucinations. enough context to trust. no single ground truth wording.</p>

<p>code-review assistant. fewer targeted changes. catches race conditions. says “good as-is” when right. token overlap with reference? meaningless.</p>

<p>support triage. routes right queue. de-escalates. never mislabels legal complaints. error costs vary wildly.</p>

<p>quality here is multi-dimensional. tied to human judgment. not a scalar bleu can fake.</p>

<p>teams end up with dashboards that do not match their gut on samples. metrics answer the wrong question.</p>

<h2 id="llm-as-a-judge-the-promise-and-quick-cracks">llm-as-a-judge: the promise and quick cracks</h2>

<p>natural fix: use a strong llm as proxy human rater.</p>

<p>feed prompt, answer, maybe reference. give eval prompt and rubric. get score. maybe explanation.</p>

<p>unlocks what presets cannot: logic, helpfulness, safety, faithfulness, reasoning quality, format.</p>

<p>first run on real data? magic. flags hallucinations. explains misleading bits. spots “correct but useless”.</p>

<p>busy teams love it. tireless junior reviewer at api speed.</p>

<p>then cracks show.</p>

<h2 id="failure-modes-that-kill-trust">failure modes that kill trust</h2>

<p>goodhart first. train against judge, model learns judge-pleasing tricks. verbose fluff. fake confidence. hides uncertainty. product worsens, metric climbs.</p>

<p>vague rubrics. “rate 1-10”. chaos. high variance. drifts with model versions. confuses experiment compares.</p>

<p>judge bias. favors formal verbosity. misses domain facts. encodes training biases.</p>

<p>leakage. judge sees hidden metadata. penalizes user-ok answers. rewards weird conformity.</p>

<p>opacity. “score 0.83”. no tags or reasons. hard to debug. communicate. prioritize.</p>

<p>llm judge without design is fancy bleu.</p>

<h2 id="custom-metrics-encode-product-judgment">custom metrics encode product judgment</h2>

<p>shift: metrics are product decisions in text.</p>

<p>good ones start from user pain. break quality into failures. attach costs. explicit rubrics. living artifacts.</p>

<p>metric prompt is spec. “10 if fully correct and grounded”. “0 if fabricates”. explain violations.</p>

<p>version them. changelog. review.</p>

<h2 id="real-engineering-cases">real engineering cases</h2>

<p>rag compliance q&amp;a.</p>

<p>matters: no hallucinated rules. quotes right policy. conservative on uncertainty.</p>

<p>judge reads question, answer, retrieved policy. scores faithfulness, fabrication, alignment. tags safe/unsafe.</p>

<p>code gen assistant.</p>

<p>functional ok. project style. maintainable. no sec bugs.</p>

<p>judge sees code, diff, tests. simulates senior reviewer.</p>

<p>support bot.</p>

<p>resolves alone. escalates with context. no wrong promises. de-escalates.</p>

<p>labels convos. maps to real kpis: resolution time, refunds, nps.</p>

<h2 id="gaps-begging-for-fixes">gaps begging for fixes</h2>

<p>tooling buckets:</p>

<p>preset bundles. easy. rarely aligned.</p>

<p>raw judge wrappers. powerful. bad at versioning, combining, structure.</p>

<p>platforms. scalars per run. weak on failures, ci, drift.</p>

<p>missing:</p>

<p>metrics as versioned objects. names, owners, rubrics.</p>

<p>playgrounds to tweak rubrics. spot disagreements.</p>

<p>composability. faithfulness + helpfulness = product rating.</p>

<p>meta-eval. judge vs human correlation.</p>

<p>opportunity: better ways to express “good”. connect to model behavior.</p>

<h2 id="metrics-as-judgment-apis">metrics as judgment apis</h2>

<p>dataset: reality sample.<br />
llm: hypothesis.<br />
metric: team judgment interface.</p>

<p>vague metric = bad api. brittle = coupled api. good = documented, stable.</p>

<p>llm executes spec. spec is asset.</p>

<h2 id="build-rules-from-scars">build rules from scars</h2>

<p>principles.</p>

<p>start real failures. not synthetics.</p>

<p>name failures: halluc, verbose, under-explain.</p>

<p>rubric explicit. top/mid/zero cases. examples.</p>

<p>metrics as code. git. reviews.</p>

<p>validate vs humans. spot disagrees.</p>

<p>multi-scores. decide balances.</p>

<h2 id="metrics-that-drive-products">metrics that drive products</h2>

<p>preset metrics fake science. avoid hard questions: what is good here?</p>

<p>custom forces it. tolerable mistakes? style? risk level?</p>

<p>encode that. experiments match gut. team aligns. “improved?” means something.</p>

<h2 id="your-turn">your turn</h2>

<p>next feature live. skip rouge comfort.</p>

<p>ask: success feel? failure nightmares? test criteria? judge needs?</p>

<p>tools evolve. leverage is encoded judgment.</p>

<p>llm can judge. values come from you.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[when preset metrics lie to you]]></summary></entry><entry><title type="html">reflections</title><link href="https://bhavesh-g.github.io/rant/2026/01/26/reflections.html" rel="alternate" type="text/html" title="reflections" /><published>2026-01-26T00:00:00+00:00</published><updated>2026-01-26T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/rant/2026/01/26/reflections</id><content type="html" xml:base="https://bhavesh-g.github.io/rant/2026/01/26/reflections.html"><![CDATA[<blockquote>
  <p>a song about seeing someone and realizing you are being seen too</p>
</blockquote>

<p><strong>intro</strong></p>

<p>judging you juding me<br />
every stare, a memory<br />
what i see might just be me<br />
glass between us, clarity.</p>

<p><strong>verse 2</strong></p>

<p>two reflections, no referee,<br />
say nothing, still you’re hearing me,<br />
you look once, then carefully,<br />
tension writes the chemistry.</p>

<p><strong>verse 3</strong></p>

<p>judging you, judging me,<br />
same reflection, different frequency,<br />
glass between us, clarity,<br />
what you see is what you be.</p>

<p><strong>verse 4</strong></p>

<p>no confession, no decree,<br />
just this mutual scrutiny,<br />
judging you, judging me,<br />
till the mirror sets us free.</p>

<p><strong>outro</strong></p>

<p>judging you, judging me,<br />
end the trial, set it free,<br />
if the mirror broke tonight,<br />
we’d still know what we see.</p>

<hr />

<h2 id="explanation-from-the-writer">explanation from the writer</h2>

<p>this song is about silent moments where two people are aware of each other without speaking. it is not about criticism or judgment in the usual sense. it is about observation, awareness, and recognition.</p>

<p>when i say judging you judging me, i mean the quiet evaluation that happens through eye contact, pauses, and body language. both people are reading each other at the same time. no one is in control and no one is innocent. there is no referee because both sides are equally involved.</p>

<p>the mirror and glass imagery represents distance that still allows clarity. there is a barrier, but it is transparent. you can see the other person clearly, and in doing so, you start seeing yourself just as clearly. the tension in the song comes from that realization.</p>

<p>what i want to convey is that many times we think we are reacting to others, but we are actually confronting parts of ourselves. attraction, curiosity, and discomfort often reveal more about us than about the person we are looking at.</p>

<p>the song does not try to resolve this tension with a confession or a conclusion. instead, it accepts it. the trial ends not because someone wins, but because both sides recognize the truth. even if the mirror breaks, the understanding remains.</p>

<p>this song is about clarity without explanation, connection without pursuit, and self recognition through someone else’s eyes.</p>]]></content><author><name>Bhavesh</name></author><category term="rant" /><summary type="html"><![CDATA[a song about seeing someone and realizing you are being seen too]]></summary></entry><entry><title type="html">an observability problem nobody fixes</title><link href="https://bhavesh-g.github.io/meta/2026/01/09/an-observability-problem-nobody-fixes.html" rel="alternate" type="text/html" title="an observability problem nobody fixes" /><published>2026-01-09T00:00:00+00:00</published><updated>2026-01-09T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2026/01/09/an-observability-problem-nobody-fixes</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2026/01/09/an-observability-problem-nobody-fixes.html"><![CDATA[<blockquote>
  <p>and why it will cost you months when you finally do</p>
</blockquote>

<p>a patient sync failed silently four hours ago. not a crash. the logs were fine. the api was responding in 45ms. the database was healthy. everything looked green on the dashboards.</p>

<p>but four hours later a clinician pulled up a patient’s medication history and got stale data. made a clinical decision on that. the sync had never happened. the system had no way to tell us.</p>

<p>this is the thing i keep running into. infrastructure observability and domain observability are not the same thing. i spent years thinking they were. they’re not.</p>

<h2 id="i-used-to-think-observability-was-just-logging">i used to think observability was just logging</h2>

<p>early in my career i assumed that if you logged errors and tracked request latency and monitored cpu, you had observability. that was the setup i inherited at my first real job. dashboards everywhere. alerts for everything. we knew when the system was struggling.</p>

<p>we had no idea what the system was actually doing.</p>

<p>i remember needing to understand why prescriptions were being rejected at high rates. went to the logs. found a hundred different rejection messages, all logged differently, some with patient data, some without, some with the drug name, some just saying “validation failed.” nobody had coordinated on how to log this. each developer who touched the code added their own approach.</p>

<p>so i started adding more logging. got more consistent. then someone needed to understand if certain drug interactions were being caught. so i added events. then analytics wanted to track something. so we added event tracking. after a few years the prescription validation code had logging calls and metrics emission and analytics event firing woven through the entire path.</p>

<p>the code worked. but reading it felt like reading a debugging session. it was hard to understand what the actual validation logic was doing because everything was buried under observability calls.</p>

<h2 id="the-moment-this-became-a-real-problem">the moment this became a real problem</h2>

<p>we decided to migrate to a different observability platform. this should have been a tool swap. it turned into a nightmare.</p>

<p>the observability calls were so embedded in the business logic that we couldn’t just swap the client library. we had to touch the prescription validation code. we had to test it carefully. we had to coordinate across teams. it took weeks.</p>

<p>i remember thinking: i’m rewriting critical code to change a logging tool. how is this my problem?</p>

<p>that’s when i realized the issue wasn’t the logging. it was where the logging lived.</p>

<h2 id="what-i-started-seeing-in-other-systems">what i started seeing in other systems</h2>

<p>after that project i noticed the same pattern everywhere i looked. teams would build a feature, add observability to it, and six months later the business logic and the observability logic were so tangled that you couldn’t change one without being careful about the other.</p>

<p>sometimes it was because someone needed to add a new field to an event and it required understanding how the business logic worked. sometimes it was because the observability wasn’t giving you the context you needed, so people started adding more and more parameters to functions, just to pass context through.</p>

<p>i watched teams decide they needed better visibility into their systems, so they added more instrumentation. the code got more complex. testing got harder. but the visibility didn’t improve. they still couldn’t answer the question they cared about.</p>

<h2 id="the-pattern-i-started-recognizing">the pattern i started recognizing</h2>

<p>what became clear was that infrastructure observability and business observability are operating at different layers.</p>

<p>infrastructure observability tells you if your system is struggling. cpu is high. memory is climbing. requests are slow. outages.</p>

<p>business observability tells you if your system is working. did the prescription get to the pharmacy? did the lab result reach the ordering provider? did the payment actually complete?</p>

<p>most teams build infrastructure observability well. they get alerts for cpu, memory, errors. but business observability requires thinking about what actually matters in your domain. and that has to be deliberate.</p>

<p>i noticed that the teams doing this well weren’t building infrastructure observability differently. they were separating it from the business logic. they created a layer whose job was to translate domain events into whatever observability platform they were using.</p>

<h2 id="how-this-actually-looks">how this actually looks</h2>

<p>the pattern i started seeing was simple. the core business logic reported what happened in domain terms. a prescription was validated. a prescription was rejected due to interaction. a prescription was flagged for manual review.</p>

<p>instead of having the validation code call logging functions and metrics clients and analytics libraries, it reported these observations to a separate object whose sole job was to handle observability.</p>

<p>that observer could log it. could emit it to metrics. could send it to analytics. could store it in a database. the business logic didn’t care. it just reported what happened.</p>

<p>the observer could also handle context. when a request came in, we extracted all the metadata we needed. patient id, provider, facility, request id for tracing. we configured the observer with that context once. the business logic never had to think about context. every observation it made automatically carried all the necessary metadata.</p>

<p>this separation meant that the business logic stayed clean. it was just validation logic. pure. testable in isolation. readable.</p>

<p>it also meant that if we wanted to change how we observed things, we changed the observer, not the business logic.</p>

<h2 id="the-part-that-surprised-me">the part that surprised me</h2>

<p>i expected this to require more code. it doesn’t. it requires less code in the places that matter.</p>

<p>the prescription validation code is smaller. easier to read. easier to test. the observability logic lives somewhere else and is focused on one job: translating domain events into infrastructure events.</p>

<p>the bigger surprise was how much better the observability actually was. because now all the observations were being made from the same place, with the same context, following the same patterns. when i needed to understand which prescriptions were failing, the data was consistent. no more digging through logs that were all formatted differently.</p>

<h2 id="context-is-the-part-that-kills-most-attempts">context is the part that kills most attempts</h2>

<p>i’ve watched teams try this approach and fail because they didn’t handle context well. they ended up passing patient_id and provider_id and facility_id through every function signature. the code became messier, not cleaner.</p>

<p>the systems that got this right configured the observer once at the boundary. when the request came in, you extracted context. you created an observer pre-configured with that context. you passed that into the business logic. now the logic never had to think about context.</p>

<p>it sounds simple. it changes everything.</p>

<h2 id="the-cost-of-not-doing-this">the cost of not doing this</h2>

<p>i’ve seen the alternative. i’ve lived it. you build a system with observability scattered through it. it works. you add more observability because you need better visibility. the code gets more complex. the problem you’re trying to solve doesn’t actually get solved because the observability is at the wrong layer.</p>

<p>then someone wants to migrate to a different platform. or wants to change how you’re logging. or wants to add a new field to an event. and suddenly you’re touching critical code. taking on risk. spending weeks refactoring something you shouldn’t have to refactor.</p>

<p>i’ve measured this cost. it’s weeks. sometimes months. i’ve watched engineers spend more time refactoring observability code than they spent building the original feature.</p>

<h2 id="what-actually-matters">what actually matters</h2>

<p>what i keep coming back to is that observability is an architectural decision, not a tool decision.</p>

<p>you can pick the best logging library and still get observability wrong if it’s mixed into your business logic. you can pick the worst metrics client and get observability right if you’ve separated the concerns.</p>

<p>the systems i’ve seen age well are the ones where someone early on decided: observability is a design layer. the way we separate persistence from business logic, we separate observability from business logic.</p>

<p>that decision ripples through everything. how you test code. how you reason about code. how you change code. whether you can migrate platforms without rewriting critical logic.</p>

<h2 id="why-im-writing-this">why i’m writing this</h2>

<p>i keep seeing teams solve the wrong problem. they get better monitoring tools. better dashboards. more logging. and still can’t answer the basic questions about their system.</p>

<p>i also keep seeing teams pay months of engineering time to refactor observability that should never have been embedded in the business logic in the first place.</p>

<p>it’s not about picking the right tool. it’s about where the logic lives.</p>

<p>the next time you’re adding observability to a critical path, ask: is this business logic or is this observability logic? if they’re mixed, separate them. not later. now.</p>

<p>because that separation is what lets you change your system later. that separation is what makes your observability actually useful. and that separation is what keeps you from paying months to fix something that should have been designed right the first time.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[and why it will cost you months when you finally do]]></summary></entry><entry><title type="html">token validation doesn’t need to be a network call</title><link href="https://bhavesh-g.github.io/meta/2025/12/12/token-validation-doesn't-need-to-be-a-network-call.html" rel="alternate" type="text/html" title="token validation doesn’t need to be a network call" /><published>2025-12-12T00:00:00+00:00</published><updated>2025-12-12T00:00:00+00:00</updated><id>https://bhavesh-g.github.io/meta/2025/12/12/token-validation-doesn&apos;t-need-to-be-a-network-call</id><content type="html" xml:base="https://bhavesh-g.github.io/meta/2025/12/12/token-validation-doesn&apos;t-need-to-be-a-network-call.html"><![CDATA[<blockquote>
  <p>building your own auth control plane: a pragmatic analysis before you buy</p>
</blockquote>

<p>there’s a moment in every growing company when someone suggests using a managed authentication platform. it’s a reasonable suggestion. these platforms exist. they’re stable. they have funding. they have teams of people thinking about security.</p>

<p>and then you realize you’re going to need to customize almost everything about it.</p>

<h2 id="start-by-asking-what-youre-actually-solving">start by asking what you’re actually solving</h2>

<p>before you even look at a platform, sit down with your architecture and ask a specific question: what percentage of my auth problem is “i need to sign tokens” versus “i need to decide who gets tokens and what they can do with them?”</p>

<p>the first part is 15 percent of the work. it’s also the part that every platform does. rsa signatures, jwt encoding, key rotation. this is solved. genuinely solved. you don’t need to think about it.</p>

<p>the second part is 85 percent of the work. and that’s where managed platforms start getting interesting because they’re not actually built for the shape of your problem.</p>

<p>think about what you need to decide:</p>
<ul>
  <li>which client is calling this endpoint and should they be allowed?</li>
  <li>has this token been revoked since i last checked?</li>
  <li>how many tokens has this client issued in the last minute?</li>
  <li>what happens when a user logs in via our old identity provider but we want to migrate them to a new one?</li>
  <li>can i add a custom claim to tokens without restructuring my entire integration?</li>
  <li>if i need to revoke all tokens for a compromised client, how fast can i do it?</li>
</ul>

<p>these aren’t authentication questions. they’re policy questions. and every organization has different answers.</p>

<h2 id="the-hidden-tax-of-just-use-the-platform">the hidden tax of “just use the platform”</h2>

<p>here’s what actually happens when you integrate a managed auth platform into a microservice architecture.</p>

<p>your identity provider issues a token with a specific format. your services need to validate it. you have two choices:</p>

<p>option one: every service calls the platform’s validation endpoint on every request. this works until you do the math. you’ve got fifty services, each handling a thousand requests per second. that’s fifty thousand validation calls per second going to someone else’s infrastructure. your auth latency is now their network latency plus their database latency. you also now have a very specific dependency: if their validation endpoint is slow, your entire system is slow.</p>

<p>option two: you cache the validation decision locally. now you’re managing cache invalidation. you’re managing staleness. you’re hoping that when a token gets revoked, the revocation propagates before someone uses a cached validation result. you’re also managing the consistency model. if a token gets revoked and a service has a stale cache entry, what happens? this is where the operational overhead starts compounding.</p>

<p>neither option is wrong, but both have costs. costs that a platform doesn’t really talk about when they’re trying to sign you up.</p>

<p>then there’s the feature gap. you want to issue tokens with different ttls based on the client’s tier. the platform has one ttl setting. so you write code to adjust tokens after the fact. you want to track which client issued how many tokens. the platform logs this somewhere, but getting the data out requires hitting their api or exporting everything. you want to add a scope-based rate limit. they have rate limiting, but it’s per user, not per scope.</p>

<p>what you’re building is a shadow auth system. you’re running their auth in parallel with custom code that makes it do what you need. and now you’ve got two systems to maintain instead of one.</p>

<h2 id="what-you-actually-control-when-you-own-it">what you actually control when you own it</h2>

<p>let’s think about what changes when you decide to build the token policy layer yourself.</p>

<p>first, the schema becomes your design problem. you’re not fitting your needs into their token structure. you’re designing a token structure that fits your needs. need custom claims? add them. need tenant information? add it. need a way to encode the service tier that issued the token? you control that.</p>

<p>second, validation becomes your operational responsibility. but that also means you control it completely. you can validate tokens in-process using your service’s public key. zero network hops. zero cache inconsistency problems. your validation latency is bounded by your crypto library, which is typically microseconds.</p>

<p>third, the audit trail becomes actually queryable. you design the schema. you decide what gets logged. you can ask questions like “how many tokens did client x issue yesterday?” without exporting anything. you can ask “which services called which services?” without reconstructing it from logs. this matters more than you think when you need to investigate a security incident.</p>

<p>fourth, revocation becomes something you control. want to revoke a token in under a second? you can. want to revoke all tokens for a client immediately? you can. want a revocation that cascades to related tokens? you can implement that. managed platforms give you revocation, but it’s revocation designed for their general case, not your specific case.</p>

<h2 id="the-token-exchange-layer-youre-going-to-need-anyway">the token exchange layer you’re going to need anyway</h2>

<p>here’s a scenario that every company with multiple auth systems eventually faces.</p>

<p>you’ve got users logging in via your enterprise identity provider. you’ve also got internal services that need to act on behalf of users. you’ve got api clients that are third-party integrations. they all need different tokens, or at least tokens with different claims.</p>

<p>the naive approach is to pass the external token all the way through your stack. every service validates it. every service extracts claims from it. but now your internal contract depends on the external provider’s token format. when you want to switch providers, you’re touching every service. when you want to add an internal claim, you can’t, because the token format is locked.</p>

<p>the better approach is token exchange. your user logs in with the external provider and gets a token. they send that token to your auth service. your auth service verifies it with the external provider, extracts what you care about, and mints a new token in your internal format.</p>

<p>now your services never see external tokens. they only see internal tokens. which means you can change identity providers without touching your services. you can add custom claims. you can add scopes. you can add whatever internal structure makes sense.</p>

<p>this isn’t a novel idea. it’s literally part of the oauth spec. but most managed auth platforms don’t think about this pattern because they’re trying to be the identity provider, not a layer on top of identity providers.</p>

<p>when you own this layer, you can implement it in a weekend. you need to verify a jwt with an external provider’s jwks endpoint, extract claims, and mint your own token. that’s maybe two hundred lines of code. the complexity is in the operational semantics, not the implementation.</p>

<h2 id="designing-the-service-with-clear-eyes">designing the service with clear eyes</h2>

<p>if you’re going to do this, think about what the service actually needs to do.</p>

<p>issuing tokens: a client calls with credentials, you verify them, you issue a token. that’s one endpoint.</p>

<p>validating tokens: a service calls with a token, you verify the signature and check if it’s revoked. that’s one endpoint.</p>

<p>exchanging tokens: an external token comes in, you verify it with the external provider, you issue an internal token. that’s one endpoint.</p>

<p>refreshing tokens: a client calls with a refresh token, you issue a new access token. that’s one endpoint.</p>

<p>revoking tokens: a client calls saying this token should be invalid. you record it. that’s one endpoint.</p>

<p>that’s the core api. everything else is supporting infrastructure.</p>

<p>the database is simple. a clients table for who can get tokens. a tokens table for metadata about issued tokens. a revocation table for what’s been revoked. an audit table for compliance. indices on the obvious fields. maybe a rate limit tracking table if you want per-minute limits.</p>

<p>the token format is straightforward. an issuer claim. a subject claim identifying the client or user. a scope list if you care about scopes. an expiry. optionally custom claims for context. a jwt id for revocation tracking.</p>

<p>none of this is complex. none of this requires specialized knowledge. what it requires is being clear about what you need and building exactly that, nothing more.</p>

<h2 id="the-operational-characteristics-matter-more-than-you-think">the operational characteristics matter more than you think</h2>

<p>when you build this yourself, you’re committing to understanding how it works and being on call for when it breaks. this is worth thinking about.</p>

<p>but here’s the thing: you’re going to be on call for auth anyway. whether you own it or not, you need to understand it well enough to debug it when it’s broken. and when something breaks in a managed platform, you’re debugging without visibility. you’re sending support tickets. you’re hoping the platform team understands your use case.</p>

<p>when you own it, you’re debugging your own code. which is generally faster.</p>

<p>the operational burden is real, but it’s probably less than you think. a service with one job tends to be stable. it’s not doing complex business logic. it’s signing tokens and checking revocation lists. the failure modes are pretty limited. bad credentials. database down. network partition.</p>

<p>the tooling you need is standard. async http server. database. crypto library. structured logging. metrics. all of this exists and is boring. deploy it on your infrastructure like any other service.</p>

<h2 id="the-moment-you-realize-this-is-the-right-call">the moment you realize this is the right call</h2>

<p>you’ll know you should do this when you find yourself in one of these situations:</p>

<p>you’re using a managed platform and you discover that adding a custom claim requires redeploying all your services that rely on the claim structure.</p>

<p>you’re using a managed platform and you need to change how tokens are issued for a specific tier of clients, but the platform’s configuration doesn’t support tier-specific behavior.</p>

<p>you’re using a managed platform and you want to switch identity providers next quarter, but that’s going to require rewriting integration code in ten different services.</p>

<p>you’re using a managed platform and you realize that your audit and compliance team needs query access to token decisions that aren’t exposed through the api.</p>

<p>you’re using a managed platform and you calculate that the cost of validation calls plus the operational overhead of caching plus the cost of custom code you’re writing anyway is more than the cost of just owning it.</p>

<p>any one of these is a sign that the platform’s generality is working against you. at that point, you’re not getting leverage from managed auth. you’re fighting it.</p>

<h2 id="how-to-think-about-building-this">how to think about building this</h2>

<p>if you decide to do it, don’t overthink the implementation. the goal isn’t to build a platform. the goal is to build a service that solves your specific problem.</p>

<p>start with the core four endpoints: issue, validate, revoke, refresh. get those working. get them tested. deploy them.</p>

<p>add token exchange when you need it. it’s one more endpoint. it adds complexity around external provider integration, but that complexity is localized.</p>

<p>add rate limiting when you need it. it’s tracking counts in time windows. standard stuff.</p>

<p>add audit logging when you need it. it’s writing documents to a collection. standard stuff.</p>

<p>add custom policies when you need them. if you want different ttls for different tiers, that’s logic in the issue endpoint. if you want custom revocation cascading, that’s logic in the revoke endpoint.</p>

<p>build it incrementally. use boring technology. use a language you know well. use a database you understand. use standard libraries for crypto. the whole point is to be able to understand every line and every decision.</p>

<h2 id="what-youre-actually-betting-on">what you’re actually betting on</h2>

<p>when you decide to build this instead of using a platform, you’re not betting that you can do it better than the platform team. you’re betting that you can do it better for your specific constraints.</p>

<p>you’re betting that understanding your own auth system is worth the engineering time. that having full visibility into token decisions is worth maintaining a service. that being able to iterate on auth policy without platform limitations is worth the operational overhead.</p>

<p>these are reasonable bets if you’re at a certain scale and certain level of architectural sophistication. they’re not reasonable bets if you’re small or if your auth needs are simple.</p>

<p>but if you’re reading this because you’re already frustrated with a platform, if you’re already writing custom code to work around platform limitations, if you’re already maintaining integration code that’s basically a shadow auth system, then you’re not really betting on building your own anymore. you’re already maintaining your own. you’re just doing it in a way that’s split between their code and your code.</p>

<p>at that point, consolidation is probably the right move.</p>

<h2 id="the-thing-nobody-mentions">the thing nobody mentions</h2>

<p>here’s what makes this work in practice: once you own the token validation, you own the validation latency. validation happens locally. it’s not a network call. it’s not a cache hit or miss. it’s your service’s public key and your service’s crypto library.</p>

<p>this changes what’s possible architecturally. services can be strict about validation because validation is fast. you can add validation to endpoints that wouldn’t normally have it because there’s no performance penalty. you can iterate on token structure because you only need to update your service and the places that consume tokens.</p>

<p>it’s not flashy. it’s not the kind of thing that makes for impressive architecture diagrams. but it’s the kind of thing that makes systems feel responsive and makes teams feel like they’re not fighting their infrastructure.</p>

<h2 id="closing-without-wrapping-up-too-much">closing without wrapping up too much</h2>

<p>the decision to build versus buy is ultimately about honesty.</p>

<p>be honest about how much of the platform you actually use. be honest about how much code you’re writing to work around platform limitations. be honest about what your compliance and operational teams actually need. be honest about whether the platform’s defaults align with your architecture or work against it.</p>

<p>if you’re honest about those things, the decision usually becomes pretty clear.</p>

<p>and if you do decide to build it, remember that you’re not building an identity platform. you’re building a token policy service. keep it small. keep it focused. keep it yours.</p>

<p>the platforms aren’t going anywhere. if you decide this isn’t worth it, you can always switch later. but at least you’ll have made the decision with your eyes open, understanding the actual shape of your problem instead of fitting your problem into someone else’s shape.</p>]]></content><author><name>Bhavesh</name></author><category term="meta" /><summary type="html"><![CDATA[building your own auth control plane: a pragmatic analysis before you buy]]></summary></entry></feed>