<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/blog/feed.xsl"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel>
<title>Saumya Dabi — Writing</title><link>https://saumya.dabi.live/blog</link><description>Essays on AI product management, evals, agents and 0→1 product craft.</description><language>en</language>
<lastBuildDate>Wed, 04 Nov 2026 16:24:59 +0000</lastBuildDate><image><url>https://saumya.dabi.live/assets/apple-touch-icon.png</url><title>Saumya Dabi — Writing</title><link>https://saumya.dabi.live/blog</link></image>
<atom:link href="https://saumya.dabi.live/blog/feed.xml" rel="self" type="application/rss+xml"/><item><title>Deactivation is a product decision, not a billing task</title><link>https://saumya.dabi.live/blog/deactivation-is-a-product-decision</link><guid isPermaLink="true">https://saumya.dabi.live/blog/deactivation-is-a-product-decision</guid>
<pubDate>Tue, 03 Nov 2026 02:00:00 +0000</pubDate><category>Fintech &amp; SaaS</category>
<description>In B2B SaaS, involuntary churn often stems from rigid billing logic and blunt deactivation scripts. Treat account cutting as a core product journey.</description><content:encoded><![CDATA[<p>When growth discussions stall, product teams almost always look outward. They audit acquisition funnels, experiment with onboarding copy, adjust pricing tiers, or commission expensive attribution studies to find where prospective buyers slip away. Meanwhile, inside the product, paying customers who actively want to use the software are being silently shown the door by administrative friction, rigid billing scripts, and automated deactivation rules built by engineering departments that never spoke to finance.</p>
<p>In B2B SaaS, losing customers to mechanics rather than intent is an avoidable operational leak. When account termination is treated as a back-end database task instead of a critical product journey, churn numbers mask a deeper failure of workflow design.</p>
<h2 id="the-silent-drain-of-involuntary-churn">The silent drain of involuntary churn<a class="hlink" href="#the-silent-drain-of-involuntary-churn" title="Permanent link">#</a></h2>
<p>In assisted-onboarding products where sales teams drive initial acquisition, customer acquisition cost is high. Weeks of relationship building, contract negotiation, and security reviews go into landing a single business subscriber. Once onboarded, that subscriber&rsquo;s intent to use the product is established. </p>
<p>Yet, data consistently shows that a measurable percentage of churn is entirely involuntary. Customers do not leave because they found a competitor or rejected the pricing. They leave because:
- A corporate credit card expired or failed an automated payment mandate.
- An annual renewal invoice was sent to an accounts payable email address that was rotated out after staff turnover.
- A grace period script executed a hard deactivation after seven days without alerting the account relationship manager.</p>
<p>To a naive product metric, these accounts simply appear as churned cohorts on the monthly dashboard. The product team responds by building new feature upsells or engagement loops to win them back, assuming the product failed to deliver value. In reality, the customer was ready to pay, but the system refused their money.</p>
<pre><code class="language-text">Payment Failure Occurs → 7-Day Hard Cutoff → Account Suspended → Relationship Manager Unaware → Permanent Churn
</code></pre>
<p>When you treat billing failures as customer churn, you diagnose a mechanical problem with a product solution. You end up shipping retention features to users who never wanted to leave in the first place.</p>
<h2 id="when-data-is-the-product-churn-costs-twice">When data is the product, churn costs twice<a class="hlink" href="#when-data-is-the-product-churn-costs-twice" title="Permanent link">#</a></h2>
<p>The cost of involuntary churn is magnified significantly in B2B data-network products—such as credit bureaus, vendor rating platforms, accounts receivable databases, and supplier directories. </p>
<p>On a standard software-as-a-service platform, a churned user represents lost recurring revenue and nothing more. Their departure frees up database storage and server compute. On a data-network platform, however, every member is both a consumer of intelligence and a contributor of proprietary data.</p>
<p>On a B2B credit-data SaaS I worked on, we discovered that account deactivation had compounding downstream consequences:</p>
<ol>
<li><strong>The contributor loss:</strong> When a business member was cut off due to a lapsed billing cycle, their active debtor reports, payment defaults, and trade credit records were pulled offline.</li>
<li><strong>The network degradation:</strong> Because every other member&rsquo;s credit assessment relied on the collective pool of reported trade experiences, losing a contributing member slightly degraded the data coverage for all surviving members.</li>
<li><strong>The acquisition treadmill:</strong> Re-acquiring a churned business customer costs nearly as much as landing a new one, but recovering them requires overcoming the friction of a broken relationship.</li>
</ol>
<blockquote>
<p>A data-network product does not just lose revenue when a member is cut off prematurely; it loses the proprietary inputs that make the product valuable to everyone else.</p>
</blockquote>
<p>Deactivation cannot be left to a cron job that checks an invoice table and flips a boolean flag in the user database. Cutting off a business account is a high-stakes product decision that requires orchestration across billing, customer success, and data integrity.</p>
<h2 id="the-manual-invoice-paradox">The manual invoice paradox<a class="hlink" href="#the-manual-invoice-paradox" title="Permanent link">#</a></h2>
<p>There is a recurring irony in financial technology and B2B SaaS startups: companies that sell sophisticated automated receivables management, invoicing portals, and cash-flow forecasting tools to their clients often run their own internal billing operations through manual spreadsheets, ad-hoc payment links, and scattered email threads.</p>
<p>When internal billing is manual, finance teams spend hours reconciling bank transfers against accounts, matching invoice IDs, and manually notifying customer success when a payment lags. Because the feedback loop between the bank account and the database is slow, customer accounts are frequently flagged as unpaid while the wire transfer is already clearing in the clearinghouse.</p>
<p>During an audit of our retention and billing flows, we found that a significant fraction of escalations labelled as &ldquo;pricing pushback&rdquo; or &ldquo;subscription cancellation&rdquo; were actually accounts where finance had mailed an invoice to an unmonitored inbox, received no response within an arbitrary window, and requested a system-level suspension.</p>
<p>Fixing this required treating internal invoicing with the same product rigor applied to customer-facing features:
- <strong>Centralized billing states:</strong> Connecting payment gateway webhooks directly to account status tables so that subscription renewals update instantly without manual intervention.
- <strong>Relationship manager visibility:</strong> Routing failed payment alerts into the internal CRM dashboard of the account owner 72 hours before any automated restriction triggers.
- <strong>Tiered grace handling:</strong> Differentiating between a first-time payment failure (where corporate card updates are common) and a deliberate cancellation request.</p>
<h2 id="designing-deactivation-as-a-product-journey">Designing deactivation as a product journey<a class="hlink" href="#designing-deactivation-as-a-product-journey" title="Permanent link">#</a></h2>
<p>To stop leaking revenue through administrative gaps, deactivation must be elevated from a database script to a first-class product workflow. </p>
<div class="tbl"><table>
<thead>
<tr>
<th>Traditional Billing Deactivation</th>
<th>Product-Led Account Lifecycle</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Trigger</strong></td>
<td>Invoice overdue by $N$ days</td>
</tr>
<tr>
<td><strong>Action</strong></td>
<td>Immediate boolean flag update (<code>is_active = false</code>)</td>
</tr>
<tr>
<td><strong>Notification</strong></td>
<td>Generic automated email from billing robot</td>
</tr>
<tr>
<td><strong>Data Handling</strong></td>
<td>Immediate teardown or cascade lock</td>
</tr>
<tr>
<td><strong>Resolution</strong></td>
<td>Full re-onboarding required to restore</td>
</tr>
</tbody>
</table></div>
<p>When you redesign account suspension around how businesses actually operate, several principles emerge:</p>
<ol>
<li><strong>Never deactivate without human context:</strong> If an enterprise or high-value SMB account hits a billing wall, the account relationship manager must be notified before the user is locked out. A five-minute phone call from an account lead resolves more billing failures than fifty automated reminder emails.</li>
<li><strong>Preserve data integrity across suspensions:</strong> In network models, maintain contributor records in a read-only or quarantined state during grace periods so that aggregate reporting remains stable while commercial terms are sorted out.</li>
<li><strong>Make payment recovery frictionless:</strong> When a user logs in during a grace period, present a single, prominent payment recovery banner with multiple checkout rails (UPI, corporate card, net banking) rather than directing them to contact support.</li>
</ol>
<h2 id="the-operational-checklist-for-billing-resilience">The operational checklist for billing resilience<a class="hlink" href="#the-operational-checklist-for-billing-resilience" title="Permanent link">#</a></h2>
<p>Before your team prioritizes new acquisition features or roadmap expansion, audit your current account lifecycle infrastructure against these four operational rules:</p>
<ul>
<li><strong>Audit your grace periods:</strong> Measure how many days elapse between an initial payment failure and a permanent account lockout. If it is under seven days in a B2B context, you are firing customers over temporary banking delays.</li>
<li><strong>Trace the notification path:</strong> Verify whether your billing alerts go to the person who holds the corporate card or to an abandoned AP inbox that nobody checks.</li>
<li><strong>Connect billing state to your CRM:</strong> Ensure that account managers receive an in-app or Slack notification the moment a key account enters payment grace, long before the user experiences a service interruption.</li>
<li><strong>Treat billing code as core product logic:</strong> Subject your invoicing, retry schedules, and deactivation scripts to the same rigorous code reviews, automated testing, and QA cycles as your core user-facing features.</li>
</ul>
<p>Demand is hard to win and expensive to acquire. Do not let administrative mechanics throw away customers who already want to stay.</p>]]></content:encoded></item><item><title>Hyperlocal growth stalls on supply coverage before user demand</title><link>https://saumya.dabi.live/blog/hyperlocal-growth-stalls-on-supply-coverage</link><guid isPermaLink="true">https://saumya.dabi.live/blog/hyperlocal-growth-stalls-on-supply-coverage</guid>
<pubDate>Sat, 31 Oct 2026 02:00:00 +0000</pubDate><category>India Tech</category>
<description>In hyperlocal marketplaces, funnel drop-offs rarely stem from bad UX. Instrumenting unavailable screens reveals when your real growth constraint is partner density.</description><content:encoded><![CDATA[<p>When a consumer marketplace conversion rate drops from 12% to 4%, the standard growth playbook blames the product funnel. Teams audit the onboarding flow, copywriters polish the value proposition, and performance marketing runs another round of discount experiments. In pure software products or national e-commerce with centralized warehouse logistics, that diagnostic makes sense. In an Indian hyperlocal services marketplace, it almost always points to the wrong problem.</p>
<p>The drop-off is rarely caused by user hesitation, payment friction, or confusing typography. It happens because the user tried to buy, and the product said no.</p>
<h2 id="the-phantom-funnel-leak">The phantom funnel leak<a class="hlink" href="#the-phantom-funnel-leak" title="Permanent link">#</a></h2>
<p>Standard product analytics aggregate actions into a single linear progression:</p>
<pre><code class="language-text">app_opened → signup_completed → address_entered → service_selected → checkout_completed
</code></pre>
<p>Between address submission and checkout, the line falls off a cliff. Product managers look at the chart and assume friction: Was the address picker difficult to operate? Did the pricing surprise the user? Did the service description fail to build trust?</p>
<p>In reality, the user typed their home address, selected a date, and hit a blocking dialog: <em>“Service is currently unavailable in your area. We will notify you when we expand to your neighborhood.”</em></p>
<p>The user did not abandon the funnel. The product abandoned the user.</p>
<p>When you collapse that rejection into a generic drop-off step in Mixpanel or PostHog, your analytics tell a misleading story. The marketing team celebrates meeting its cost-per-install target while complaining that product conversion is soft. Meanwhile, product engineering spends sprint capacity tuning onboarding screens that were already functioning as intended.</p>
<h2 id="what-70-unavailable-looks-like-in-the-data">What 70% unavailable looks like in the data<a class="hlink" href="#what-70-unavailable-looks-like-in-the-data" title="Permanent link">#</a></h2>
<p>At a services marketplace I worked on, we spent weeks investigating why acquisition cohorts showed an agonizing drop-off between account setup and the first completed booking. The growth dashboards were full of theories about onboarding friction and price sensitivity.</p>
<p>When I stepped back from the standard funnel and audited what users actually encountered after entering their address, the real bottleneck became obvious: 70% of users who checked eligibility saw &ldquo;unavailable&rdquo; at least once.</p>
<p>Think about what that denominator means. Out of every ten qualified users who downloaded the app, completed registration, and told us exactly where they lived, seven were turned away by our own software.</p>
<blockquote>
<p>An unavailable screen is not an edge case or an operational footnote. It is an unfulfilled purchase order.</p>
</blockquote>
<p>Marketing was hitting its download targets and burning ad spend across major metropolitan pin codes, but our field partners were clustered in a handful of tight residential pockets. We were paying full acquisition costs to deliver a rejection notice to seven out of ten prospective customers. Worse, a user who is told &ldquo;service unavailable&rdquo; does not patiently wait for your ops team to recruit partners. They uninstall the app, solve the problem through an unorganized local vendor, and rarely return when you eventually launch in their sector.</p>
<h2 id="the-geometry-of-urban-indian-supply">The geometry of urban Indian supply<a class="hlink" href="#the-geometry-of-urban-indian-supply" title="Permanent link">#</a></h2>
<p>Hyperlocal services in Indian cities face physical constraints that centralized e-commerce platforms never encounter. You cannot treat a city like Mumbai, Bengaluru, or Delhi NCR as a single operational zone or a neat radial circle.</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Operational Factor</th>
<th>Centralized E-Commerce</th>
<th>Hyperlocal Field Services</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fulfillment unit</td>
<td>Warehouse parcel</td>
<td>Mobile field partner</td>
</tr>
<tr>
<td>Transit window</td>
<td>24–48 hours</td>
<td>30–90 minutes</td>
</tr>
<tr>
<td>Geographic boundary</td>
<td>Broad pin code zone</td>
<td>Polygon (1–3 km radius)</td>
</tr>
<tr>
<td>Capacity ceiling</td>
<td>Inventory depth</td>
<td>Finite partner hours per day</td>
</tr>
<tr>
<td>Unmatched check</td>
<td>Backorder or delayed delivery</td>
<td>Hard refusal (<em>Service unavailable</em>)</td>
</tr>
</tbody>
</table></div>
<p>Urban density in India fractures service radiuses. In Mumbai, a field partner based in Bandra West cannot reliably take an 11:00 AM booking in Kurla, even though the straight-line distance is only six kilometers. Railway crossings, arterial traffic bottlenecks, and monsoon waterlogging turn short physical trips into hour-long commutes. In Bengaluru, crossing the Outer Ring Road during peak hours breaks partner punctuality and destroys schedule predictability for the rest of the day.</p>
<p>Furthermore, address checks fail for two completely distinct reasons:</p>
<ol>
<li><strong>Geographic absence:</strong> The submitted location sits outside any active partner polygon.</li>
<li><strong>Temporal exhaustion:</strong> The location is inside an active polygon, but every available partner is already booked for the requested morning or evening window.</li>
</ol>
<p>Both conditions present the exact same message to the customer: <em>Service unavailable</em>. But one is a partner recruitment problem, and the other is a slot scheduling and dispatch problem. If your analytics cannot tell them apart, your operations team cannot fix either.</p>
<h2 id="instrumenting-rejection-as-a-demand-signal">Instrumenting rejection as a demand signal<a class="hlink" href="#instrumenting-rejection-as-a-demand-signal" title="Permanent link">#</a></h2>
<p>To turn supply coverage into a growth lever, you must treat every address check as a structured demand event rather than an incidental view.</p>
<pre><code class="language-text">Event: service_eligibility_checked
Properties:
  - latitude: 19.0760
  - longitude: 72.8777
  - pincode: &quot;400051&quot;
  - service_category: &quot;home_grooming&quot;
  - requested_slot_start: &quot;2026-11-01T10:00:00Z&quot;
  - outcome: &quot;unavailable&quot;
  - rejection_reason: &quot;slot_exhausted&quot;  // or &quot;out_of_coverage&quot;
  - active_partners_in_polygon: 4
  - attempt_count: 2
</code></pre>
<p>By logging the exact rejection reason and geo-coordinates, the &ldquo;service unavailable&rdquo; screen stops being ops noise and becomes your supply acquisition roadmap:</p>
<ul>
<li><strong>Demand clustering:</strong> Aggregating coordinates reveals high-intent clusters that span across arbitrary postal pin code boundaries. You can see exactly which two-kilometer micro-markets have enough density to justify recruiting new partners.</li>
<li><strong>Slot elasticity:</strong> Logging requested timestamps highlights where demand spikes exceed capacity. If 80% of rejections in a zone happen between 8:00 AM and 11:00 AM on Saturdays, hiring more full-time partners is the wrong answer; you need part-time weekend shift incentives.</li>
<li><strong>Repeat intent:</strong> Tracking attempt counts separates casual browsers from frustrated buyers who returned three times trying to find an open slot.</li>
</ul>
<aside class="callout tip"><span class="mono">Try this</span><p>Never let an unavailable screen end as a dead end. Collect the customer&rsquo;s preferred day and time, and trigger an automated push notification the moment partner capacity opens in their polygon.</p></aside>
<h2 id="the-supply-coverage-audit">The supply coverage audit<a class="hlink" href="#the-supply-coverage-audit" title="Permanent link">#</a></h2>
<p>Before approving another marketing budget increase or commissioning an onboarding redesign for a local services product, run this diagnostic on your last 30 days of data:</p>
<ol>
<li><strong>Calculate the Supply Coverage Ratio (SCR):</strong> Divide the number of eligibility checks that found an open slot by the total number of eligibility checks submitted. If your overall SCR is below 60%, pause paid acquisition campaigns immediately.</li>
<li><strong>Segment SCR by micro-polygon:</strong> Identify the specific neighborhoods where SCR exceeds 80%. Confine paid performance marketing strictly to those geofenced boundaries.</li>
<li><strong>Split rejections by cause:</strong> Measure the ratio of <code>out_of_coverage</code> versus <code>slot_exhausted</code>. Use the former to guide partner onboarding targets, and the latter to restructure partner shift incentives and weekend payout multipliers.</li>
<li><strong>Remove supply drops from the product funnel:</strong> In your primary conversion dashboards, separate users who had access to inventory from users who were blocked by coverage. Measure product UX performance only on the cohort that could actually buy.</li>
</ol>
<p>In hyperlocal marketplaces, product design and operations cannot operate in silos. When conversion drops, check the street before you refactor the code.</p>]]></content:encoded></item><item><title>A model lineup is a pricing portfolio</title><link>https://saumya.dabi.live/blog/a-model-lineup-is-a-pricing-portfolio</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-model-lineup-is-a-pricing-portfolio</guid>
<pubDate>Tue, 27 Oct 2026 02:00:00 +0000</pubDate><category>Teardown</category>
<description>OpenAI&#x27;s GPT-6.1 Terra cuts price 50% while keeping the premium tier intact. The product lesson for PMs building on top of AI APIs is that model choice is cost architecture, not just capability selection.</description><content:encoded><![CDATA[<p>OpenAI released <a href="https://openai.com/index/introducing-gpt-6-1-terra">GPT-6.1 Terra</a> on October 26, 2026 at $1 per million input tokens and $5 per million output tokens — 50% below both GPT-5.6 Terra and GPT-6.1 Sol. It matches GPT-6 Astra on computer-use benchmarks at roughly one-tenth the price per token. The headline capability is &ldquo;fast and capable computer use at scale,&rdquo; but the more interesting signal is what the pricing structure tells product teams building on these APIs.</p>
<h2 id="the-mid-tier-is-not-a-downgrade">The mid-tier is not a downgrade<a class="hlink" href="#the-mid-tier-is-not-a-downgrade" title="Permanent link">#</a></h2>
<p>Terra is not a stripped-down version of the flagship. On Agents&rsquo; Last Exam, it scores within range of Astra at medium reasoning effort. On MMMU-Pro (visual reasoning), it nearly matches both Astra and Sol. The hallucination rate on error-inducing prompts dropped from 53.6% to 16.7% at low effort compared to the previous Terra generation.</p>
<p>This is meaningful because it changes the decision from &ldquo;which model is best&rdquo; to &ldquo;which tasks actually need the premium tier.&rdquo; If Terra handles invoice reading, form filling, and document drafting at a comparable quality level, the only reason to use Astra is tasks where its remaining edge — the hardest code, the most complex multi-step reasoning — is the bottleneck.</p>
<h2 id="price-cuts-create-re-architecture-decisions">Price cuts create re-architecture decisions<a class="hlink" href="#price-cuts-create-re-architecture-decisions" title="Permanent link">#</a></h2>
<p>A 50% price drop does not just save money on existing usage. It makes previously uneconomical tasks viable. An application that could not justify AI-assisted data entry at $2 per million input tokens might clear the business case at $1. That is not a small operational tweak — it changes what you build next.</p>
<p>For a PM managing an AI-powered product, the practical questions after a pricing shift like this are:</p>
<ul>
<li><strong>Which tasks are currently on the premium tier that Terra could handle?</strong> Run a sample of production inputs through Terra and compare output quality. If completion rates and accuracy are within your error budget, migrate those tasks and redeploy the savings.</li>
<li><strong>Which tasks were rejected on cost grounds?</strong> Re-run the unit economics. A task that cost $0.12 per completion on Sol might cost $0.06 on Terra. If your product charges $0.50 per use, that margin change is significant.</li>
<li><strong>Where does latency matter more than capability?</strong> Terra completes tasks over three times faster than Astra. For user-facing features where wait time affects conversion, the faster model at lower cost is the better product choice even if the premium model scores a few points higher on a benchmark.</li>
</ul>
<h2 id="track-cost-per-task-not-cost-per-token">Track cost per task, not cost per token<a class="hlink" href="#track-cost-per-task-not-cost-per-token" title="Permanent link">#</a></h2>
<p>Token pricing is a proxy, not the metric that matters. Two models at the same per-token price can have very different costs per completed task if one uses fewer tokens or fewer retries to reach the same outcome.</p>
<p>OpenAI&rsquo;s own framing hints at this: they report that Terra is &ldquo;over three times as token efficient as Claude Opus 5.5&rdquo; on computer-use tasks, resulting in roughly 9x lower cost per task. The efficiency gain compounds with the price cut.</p>
<p>A useful internal dashboard for any PM shipping on top of LLM APIs tracks three numbers per task type:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Metric</th>
<th>What it tells you</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cost per completed task</td>
<td>The real unit cost, including retries and token overhead</td>
</tr>
<tr>
<td>Completion rate</td>
<td>Whether the model finishes the task without human rescue</td>
</tr>
<tr>
<td>Latency (p50 and p95)</td>
<td>Whether speed is a user-experience constraint</td>
</tr>
</tbody>
</table></div>
<p>If you only track cost per token, you will miss the moment when a cheaper model actually delivers a lower cost per task because it is more efficient. You will also miss the moment when a premium model is worth its price because it avoids expensive failure recovery.</p>
<h2 id="a-lineup-is-a-portfolio-strategy">A lineup is a portfolio strategy<a class="hlink" href="#a-lineup-is-a-portfolio-strategy" title="Permanent link">#</a></h2>
<p>What OpenAI is doing with Terra, Sol, and Astra is not just releasing better models. It is building a pricing portfolio: a set of options at different price-performance points, each designed to capture a segment of demand that the others leave on the table.</p>
<p>This is a familiar pattern in enterprise software (Salesforce editions, AWS instance families), but it is newer in the foundation-model market. For PMs building downstream products, it creates real work. You need to map your task types to model tiers, set rules for when to route up or down, and monitor whether the boundaries are still right after each new release.</p>
<p>The risk of not doing this is straightforward: you either overpay by using the premium tier for every request, or you under-deliver by using the cheapest model for tasks that need more capability. Neither is a good default.</p>
<h2 id="the-decision-rule">The decision rule<a class="hlink" href="#the-decision-rule" title="Permanent link">#</a></h2>
<p>After a major pricing shift like Terra, I would run this sequence:</p>
<ol>
<li><strong>Inventory your task types</strong> by volume, current model, and current cost per completion.</li>
<li><strong>Sample-test the new tier</strong> on your top-five highest-volume task types.</li>
<li><strong>Compare completion rate and quality</strong> against your error budget, not against the premium model&rsquo;s maximum score.</li>
<li><strong>Migrate tasks where the new tier meets the bar</strong>, and track the cost and quality delta for two weeks before committing.</li>
<li><strong>Re-evaluate rejected features</strong> that failed on unit economics at the old price point.</li>
</ol>
<p>A model launch is not just a capabilities announcement. For product teams consuming these APIs, it is a pricing event that can reshape your product&rsquo;s cost structure overnight. The teams that treat model selection as an ongoing portfolio decision — not a one-time architecture choice — will capture the margin before their competitors notice the price moved.</p>]]></content:encoded></item><item><title>Human overrides should expire</title><link>https://saumya.dabi.live/blog/a-human-override-needs-a-timeout</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-human-override-needs-a-timeout</guid>
<pubDate>Sat, 24 Oct 2026 02:00:00 +0000</pubDate><category>AI Product</category>
<description>A manual override can rescue a stuck workflow, then quietly become permanent policy. Give overrides an owner, a reason, and an expiry so exceptions return to deliberate review.</description><content:encoded><![CDATA[<p>A manual override feels like the fastest way to unblock a user. The trouble starts when the exception outlives the incident: a one-off setting remains in place, nobody remembers why, and the next person assumes it is intended behavior. I would design every override with an expiry from the start.</p>
<h2 id="an-override-changes-the-operating-rule">An override changes the operating rule<a class="hlink" href="#an-override-changes-the-operating-rule" title="Permanent link">#</a></h2>
<p>Suppose a workflow normally routes a case for review when required information is missing. An operator bypasses that check because a specific case has an urgent deadline. The immediate action may be reasonable, but the system has now departed from its standard path.</p>
<p>That is more than a button click. It is a temporary policy change with a scope, a risk, and a future decision attached. If the product records only “override enabled,” it loses the context needed to decide whether the exception should continue.</p>
<p>A useful override record has five fields:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Field</th>
<th>Question it answers</th>
</tr>
</thead>
<tbody>
<tr>
<td>Scope</td>
<td>Which case, user, or workflow is affected?</td>
</tr>
<tr>
<td>Owner</td>
<td>Who is accountable for reviewing it?</td>
</tr>
<tr>
<td>Reason</td>
<td>What condition justified the exception?</td>
</tr>
<tr>
<td>Expiry</td>
<td>When does the exception stop applying?</td>
</tr>
<tr>
<td>Return state</td>
<td>What should happen after expiry?</td>
</tr>
</tbody>
</table></div>
<p>These fields make the exception legible to the next person. They also expose when an override is too broad: “all cases” may be a product change disguised as a workaround.</p>
<h2 id="make-expiry-a-real-state-transition">Make expiry a real state transition<a class="hlink" href="#make-expiry-a-real-state-transition" title="Permanent link">#</a></h2>
<p>An expiry timestamp alone is not enough. At the deadline, the system needs a defined behavior. It might restore the prior rule, pause the affected workflow, or send the item to a review queue. Which choice is right depends on the consequence of reverting.</p>
<p>The important constraint is that expiry must not silently create a more dangerous state. If reverting a setting could interrupt a critical process, the product should warn the owner before the deadline and specify what happens if nobody responds.</p>
<p>For a low-risk formatting exception, automatic restoration may be fine. For a decision that changes what can be approved, expiry could instead move the case back to a pending state. The system should show the transition in the activity history, not make the user infer why behavior changed.</p>
<h2 id="keep-extensions-explicit">Keep extensions explicit<a class="hlink" href="#keep-extensions-explicit" title="Permanent link">#</a></h2>
<p>Sometimes the exception is still needed when it expires. Let the owner extend it, but make the extension a new decision rather than a hidden reset of the clock.</p>
<p>A review card can show:</p>
<ul>
<li>the original reason and scope;</li>
<li>what has happened under the override;</li>
<li>the current expiry and return state;</li>
<li>the options to end, extend, or replace it.</li>
</ul>
<p>An extension should ask for a current reason and a new expiry. That small amount of friction distinguishes “still justified” from “nobody got around to cleaning it up.” If the same exception is repeatedly extended, surface that pattern as evidence that the standard workflow may need redesign. Do not automatically turn frequency into permission.</p>
<h2 id="separate-emergency-access-from-ordinary-exceptions">Separate emergency access from ordinary exceptions<a class="hlink" href="#separate-emergency-access-from-ordinary-exceptions" title="Permanent link">#</a></h2>
<p>Some products need a way to act during an outage or urgent incident. That should not be conflated with a routine override. Emergency access may require stronger identity checks, narrower scope, immediate logging, and review after the event.</p>
<p>The product can still make it usable: state what the emergency action changes, who will be notified, and what recovery or review follows. A vague “break glass” control provides power without making its consequences clear.</p>
<p>For ordinary exceptions, the default path should remain easy to understand and auditable. For genuine emergencies, the path can be fast while leaving a clear record. Both need boundaries; urgency is not a reason to erase them.</p>
<h2 id="review-the-exception-queue">Review the exception queue<a class="hlink" href="#review-the-exception-queue" title="Permanent link">#</a></h2>
<p>A practical weekly review does not need a complex dashboard. Start with a list of active overrides sorted by expiry, showing owner, scope, reason, age, and next action. Then ask three questions:</p>
<ol>
<li>Is the exception still needed for the stated reason?</li>
<li>Is its scope still the smallest useful scope?</li>
<li>Should the standard workflow change, or should this exception end?</li>
</ol>
<p>The queue also reveals missing ownership. If nobody can decide whether an exception should continue, assign a responsible role before adding more overrides. Otherwise the system has created a permanent policy with no explicit approval.</p>
<p>My design rule: every override needs a named owner, a bounded scope, a reason that can be reviewed, an expiry, and a safe return state. If one of those is missing, it is not a controlled exception; it is an undocumented change to how the product works.</p>]]></content:encoded></item><item><title>Give agents permissions in steps, not all at once</title><link>https://saumya.dabi.live/blog/an-agent-needs-a-permission-gradient</link><guid isPermaLink="true">https://saumya.dabi.live/blog/an-agent-needs-a-permission-gradient</guid>
<pubDate>Tue, 20 Oct 2026 02:00:00 +0000</pubDate><category>Agents</category>
<description>Giving an agent one broad permission setting makes routine work either risky or frustrating. A permission gradient ties each action to its impact, reversibility, and evidence.</description><content:encoded><![CDATA[<p>An agent that can only observe is safe but limited. An agent that can do everything is capable until it makes one consequential mistake. The product problem is not choosing between those extremes; it is deciding which actions can happen automatically, which need a check, and which should remain outside the agent’s authority.</p>
<h2 id="permission-is-a-product-decision">Permission is a product decision<a class="hlink" href="#permission-is-a-product-decision" title="Permanent link">#</a></h2>
<p>A single “autonomous” toggle hides several distinct capabilities. Reading a record, drafting a reply, sending that reply, and changing an account are not equivalent just because they appear in one workflow. Their consequences differ, and so should the permission model.</p>
<p>I would start by describing each action along three dimensions:</p>
<ul>
<li><strong>Impact:</strong> How much harm or disruption could a mistake cause?</li>
<li><strong>Reversibility:</strong> Can the action be undone completely and cheaply?</li>
<li><strong>Evidence:</strong> Can a person or system inspect why the agent chose it?</li>
</ul>
<p>These dimensions are not a scoring formula that magically determines policy. They are prompts for a product decision. A low-impact, reversible action with clear evidence may be a reasonable candidate for automatic execution. A high-impact action that is difficult to reverse should require explicit authorization, even when the agent’s confidence is high.</p>
<p>For example, in a hypothetical support workflow, categorizing a message could be automatic. Drafting a response could be automatic but remain unsent. Sending a response might require approval when it contains a commitment or handles a sensitive case. Issuing a refund could be restricted to an authorized person. The important distinction is not “AI versus human.” It is what the action does and what recourse exists if it is wrong.</p>
<h2 id="use-a-ladder-not-a-toggle">Use a ladder, not a toggle<a class="hlink" href="#use-a-ladder-not-a-toggle" title="Permanent link">#</a></h2>
<p>A practical permission gradient might have four levels:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Level</th>
<th>Agent may</th>
<th>Typical control</th>
</tr>
</thead>
<tbody>
<tr>
<td>Observe</td>
<td>Read approved context and summarize</td>
<td>Scope access to relevant records</td>
</tr>
<tr>
<td>Prepare</td>
<td>Draft, classify, or propose a change</td>
<td>Keep the result as a draft</td>
</tr>
<tr>
<td>Execute reversible actions</td>
<td>Make changes with a clear undo path</td>
<td>Log the action and expose undo</td>
</tr>
<tr>
<td>Execute consequential actions</td>
<td>Commit external, financial, or hard-to-reverse changes</td>
<td>Require an authorized confirmation</td>
</tr>
</tbody>
</table></div>
<p>This ladder is a starting point, not a universal taxonomy. Some “reversible” actions are expensive to repair; some seemingly minor actions expose private information. The product team should classify the actual domain action, not trust a label such as <code>update</code> or <code>send</code> to explain its risk.</p>
<p>The levels should also be assigned per action, not per agent. A workflow may allow an agent to organize files while requiring approval before sharing them. If the user has to grant broad access to get one useful capability, the product is asking them to accept unnecessary risk.</p>
<h2 id="make-confirmation-informative">Make confirmation informative<a class="hlink" href="#make-confirmation-informative" title="Permanent link">#</a></h2>
<p>A confirmation dialog that says “Allow agent to continue?” transfers the decision to the user without giving them enough information to make it. A useful approval request should answer four questions:</p>
<ol>
<li><strong>What will happen?</strong> Name the action in ordinary language.</li>
<li><strong>To what or whom?</strong> Identify the target and affected parties.</li>
<li><strong>Why now?</strong> Show the relevant evidence or condition that triggered it.</li>
<li><strong>What happens after approval?</strong> State whether the action is reversible and how to recover.</li>
</ol>
<p>Imagine a proposed change to a customer record. “Update record” is not enough. The request should identify the field, old and proposed values, the source of the proposed value, and whether the change can be undone. If the agent cannot show its source, that is a reason to ask for more review, not a reason to produce a more confident-sounding prompt.</p>
<p>Approval should be scoped as narrowly as possible. Approving one named action should not silently authorize similar future actions. If repeated approvals become burdensome, the product can offer a bounded rule: for example, permit a defined action for a specified set of records, within a limit, for a stated period. The user should be able to inspect and revoke that rule.</p>
<h2 id="treat-automation-as-something-earned">Treat automation as something earned<a class="hlink" href="#treat-automation-as-something-earned" title="Permanent link">#</a></h2>
<p>A common design mistake is to begin with broad autonomy and add confirmation after users discover failures. A safer sequence is to start with observation and preparation, then expand authority only when the action boundary and recovery path are understood.</p>
<p>That does not mean every action needs a human in the loop forever. It means each step up the ladder should answer a concrete question. What failure could occur? Who would notice? How quickly? Can it be reversed? What evidence would tell us that the action is safe enough to automate in this context?</p>
<p>A product team can write a short policy for each action:</p>
<blockquote>
<p>Action: [specific operation]<br />
Automatic when: [bounded conditions]<br />
Approval required when: [risk or uncertainty]<br />
Evidence shown: [inputs and rationale]<br />
Recovery: [undo, escalation, or remediation]</p>
</blockquote>
<p>For instance, a team might permit automatic categorization only when the record is in a defined queue, keep proposed edits as drafts, and require an authorized person to approve an external commitment. The policy is specific enough to test and revise. “The agent can handle routine cases” is not: different people will disagree about what routine means.</p>
<h2 id="measure-the-boundary-not-just-task-completion">Measure the boundary, not just task completion<a class="hlink" href="#measure-the-boundary-not-just-task-completion" title="Permanent link">#</a></h2>
<p>If a workflow reports only whether the agent completed its task, it may reward unsafe behavior. A more useful review asks whether the agent stayed within its authority, whether approval requests were necessary and understandable, and whether a person could recover from a mistaken action.</p>
<p>For a sample of actions, inspect:</p>
<ul>
<li>Was the action permitted under the policy in force?</li>
<li>Did the agent present the evidence needed to justify it?</li>
<li>Did it stop when a condition was outside its authority?</li>
<li>Could the affected user identify and reverse an error?</li>
</ul>
<p>These are review questions, not claims that one metric can prove safety. A team can use them to find vague policies, missing controls, and approval steps that have become rote clicks. If people repeatedly approve without reading, the response should not automatically be to remove the check; first determine whether the request is poorly designed or the action is genuinely low-risk and can be bounded more tightly.</p>
<p>The decision rule I would take into a design review is simple: grant the least authority that still makes the workflow useful, and make every increase in authority conditional on a clear boundary, inspectable evidence, and a credible recovery path. If any one of those is missing, keep the action at the draft or approval stage.</p>]]></content:encoded></item><item><title>Answer product interview questions with a bet</title><link>https://saumya.dabi.live/blog/a-product-interview-needs-a-bet</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-product-interview-needs-a-bet</guid>
<pubDate>Sat, 17 Oct 2026 02:00:00 +0000</pubDate><category>Career</category>
<description>Strong product interview answers do more than list options. They make a clear bet, show what evidence would change it, and name the next test.</description><content:encoded><![CDATA[<p>A polished product answer can still feel strangely empty. It covers users, metrics, constraints and alternatives, yet the interviewer cannot tell what you would actually do. The missing piece is usually a bet: a decision made under uncertainty, with a reason and a way to learn whether it was wrong.</p>
<h2 id="analysis-is-not-the-decision">Analysis is not the decision<a class="hlink" href="#analysis-is-not-the-decision" title="Permanent link">#</a></h2>
<p>Consider a prompt about improving activation for a service with a multi-step sign-up. A candidate might map the funnel, segment users, propose research and brainstorm changes. All useful. But if the answer never picks a first move, it is an inventory of product activities rather than a product decision.</p>
<p>A bet is not a guess dressed up as certainty. It is a provisional choice among plausible actions. It says: given what I know now, I would spend the next unit of effort here, because this outcome matters more than the alternatives.</p>
<p>I use a simple sentence to force the choice:</p>
<blockquote>
<p>I would do <strong>X</strong> for <strong>Y</strong> because <strong>Z</strong>; I would change course if <strong>W</strong>.</p>
</blockquote>
<p>For the activation example: “I would first investigate the step where users must provide information they may not have readily available, because that could create avoidable abandonment; I would change course if the largest loss is actually before users reach that step.” This is deliberately a hypothesis, not a claim about real funnel data. The answer makes a direction legible while admitting what is unknown.</p>
<h2 id="make-the-trade-off-inspectable">Make the trade-off inspectable<a class="hlink" href="#make-the-trade-off-inspectable" title="Permanent link">#</a></h2>
<p>A recommendation becomes useful when the alternative is visible. “I’d simplify sign-up” sounds agreeable until we ask what gets delayed or put at risk. Perhaps the team could instead improve the explanation of why a field is needed, or move a field later. Each option has a different cost and a different failure mode.</p>
<p>A compact decision note helps:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Choice</th>
<th>Expected benefit</th>
<th>Cost or risk</th>
<th>What I would inspect</th>
</tr>
</thead>
<tbody>
<tr>
<td>Remove a field</td>
<td>Less effort now</td>
<td>Could lose information needed later</td>
<td>Completion and downstream rework</td>
</tr>
<tr>
<td>Explain the field</td>
<td>Better understanding</td>
<td>Adds copy, may not remove effort</td>
<td>User comprehension and continuation</td>
</tr>
<tr>
<td>Defer the field</td>
<td>Faster first step</td>
<td>Could create a later interruption</td>
<td>Return completion and support burden</td>
</tr>
</tbody>
</table></div>
<p>The table does not prove which option wins. It makes the decision criteria explicit. In an interview, I would select one based on the prompt’s constraints, then say what fact would reverse that selection. If the interviewer supplies new information, I can update the bet rather than defend the first answer as if it were a fixed identity.</p>
<p>This is also a better way to handle missing data. Instead of inventing a metric, say what you would want to know and how the answer would affect the choice. “If most abandonment occurs before this field appears, removing it is unlikely to address the main problem” is a valid conditional. “This change will improve conversion by 20%” is not valid unless the prompt provides evidence for that estimate.</p>
<h2 id="separate-the-bet-from-the-test">Separate the bet from the test<a class="hlink" href="#separate-the-bet-from-the-test" title="Permanent link">#</a></h2>
<p>Candidates often jump from recommendation to a grand experiment. But a test should answer the uncertainty that matters to the decision, not merely produce a dashboard.</p>
<p>Write the chain in four parts:</p>
<ol>
<li><strong>Decision:</strong> What will we do if the current belief is right?</li>
<li><strong>Uncertainty:</strong> What could make that decision wrong?</li>
<li><strong>Evidence:</strong> What observation would distinguish the possibilities?</li>
<li><strong>Next action:</strong> What will we do for each plausible result?</li>
</ol>
<p>For the sign-up example, the uncertainty might be whether the field itself creates friction or whether users do not understand its purpose. A small usability session could reveal confusion, while a controlled product change might be needed to estimate behavioral impact. Those methods answer different questions; neither should be promised as a universal solution.</p>
<p>The fourth part matters most. If both outcomes lead to the same action, the test may be interesting but not decision-relevant. State the branches before collecting evidence: if users cannot explain the field’s purpose, improve the explanation; if they understand it but lack the information, explore deferral or an alternative input. The branches need not be perfect. They need to show that learning will affect what happens next.</p>
<h2 id="show-how-the-bet-changes">Show how the bet changes<a class="hlink" href="#show-how-the-bet-changes" title="Permanent link">#</a></h2>
<p>A strong answer is not a performance of confidence. It is a small demonstration of how you reason when evidence changes. When an interviewer adds a constraint, acknowledge its effect on the choice directly: “That changes my priority. I would no longer remove the field first; I would look for a way to reduce effort without losing the required information.”</p>
<p>This keeps the conversation collaborative. It also prevents a common failure mode: continuing to elaborate the original plan after the premise that supported it has disappeared.</p>
<p>One useful self-check is to remove the analysis and read only the recommendation, its reason and its reversal condition. Can someone tell what you would do next? Can they tell what you are uncertain about? If not, the answer may be comprehensive without being decisive.</p>
<h2 id="a-closing-check-before-you-answer">A closing check before you answer<a class="hlink" href="#a-closing-check-before-you-answer" title="Permanent link">#</a></h2>
<p>Before finishing a product case or behavioral example, ask yourself:</p>
<ul>
<li>What is my actual choice—not just the categories I considered?</li>
<li>What am I giving up by choosing it?</li>
<li>Which assumption carries the decision?</li>
<li>What evidence would change my mind?</li>
<li>What would I do under each likely result?</li>
</ul>
<p>A useful interview answer does not need false precision or a perfect forecast. It needs a defensible first move and an honest account of how you would learn. Make the bet explicit, then make its escape hatch explicit too.</p>]]></content:encoded></item><item><title>Write the state contract before you automate the workflow</title><link>https://saumya.dabi.live/blog/a-workflow-needs-a-state-contract</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-workflow-needs-a-state-contract</guid>
<pubDate>Tue, 13 Oct 2026 02:00:00 +0000</pubDate><category>PM Craft</category>
<description>When an AI feature hands work between a model, a person, and software, define which states are real and who can change them. A practical contract prevents silent drift.</description><content:encoded><![CDATA[<p>A workflow can look complete while its state is nonsense. The model says “done,” the interface says “review needed,” and the underlying record still looks untouched. Each component may be behaving as designed; the product is not.</p>
<p>I treat this as a contract problem. Before specifying screens or prompts, write down what states mean, who may move between them, and what evidence makes a transition valid. This is useful anywhere an AI feature hands work between a model, a person, and ordinary software.</p>
<h2 id="start-with-user-visible-states">Start with user-visible states<a class="hlink" href="#start-with-user-visible-states" title="Permanent link">#</a></h2>
<p>A common mistake is to copy internal implementation steps into the product vocabulary. “Prompt sent,” “tool returned,” and “response parsed” may help an engineer debug, but they rarely tell a user what they can do next.</p>
<p>Instead, list the states that change the user’s choices. A document assistant might have:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>State</th>
<th>User meaning</th>
<th>Available next action</th>
</tr>
</thead>
<tbody>
<tr>
<td>Draft</td>
<td>No work has been submitted</td>
<td>Edit or submit</td>
</tr>
<tr>
<td>Processing</td>
<td>Work is underway; no final result yet</td>
<td>Wait or cancel</td>
</tr>
<tr>
<td>Review required</td>
<td>A proposed result exists, but needs a decision</td>
<td>Accept, edit, or reject</td>
</tr>
<tr>
<td>Applied</td>
<td>The accepted change is reflected in the target record</td>
<td>Inspect or undo, if supported</td>
</tr>
<tr>
<td>Blocked</td>
<td>The workflow cannot continue without new input</td>
<td>Provide input or abandon</td>
</tr>
</tbody>
</table></div>
<p>This is a product model, not a claim that every system needs these exact labels. The test is practical: can a user infer whether the system is waiting, asking, finished, or unable to proceed?</p>
<p>Keep hidden technical detail out of the state list unless it changes a user decision. A retry counter might be operationally important without needing its own user-facing state. Conversely, “waiting for approval” deserves a distinct state if the user must act before anything else can happen.</p>
<h2 id="specify-transitions-not-just-destinations">Specify transitions, not just destinations<a class="hlink" href="#specify-transitions-not-just-destinations" title="Permanent link">#</a></h2>
<p>A state diagram without transition rules is decoration. For each move, specify the trigger, the actor, and the evidence that permits it.</p>
<p>For example:</p>
<ul>
<li><code>Draft → Processing</code>: a user submits; the system records the submitted input.</li>
<li><code>Processing → Review required</code>: the model returns a proposal that passes basic structural checks.</li>
<li><code>Review required → Applied</code>: an authorized person accepts the proposal and the target system confirms the change.</li>
<li><code>Processing → Blocked</code>: a required dependency fails or the workflow reaches a defined limit.</li>
</ul>
<p>The distinction between a proposal and an applied change matters. A model returning plausible text is not proof that a record was updated. Likewise, a button click is not proof that the server accepted the action. The transition should depend on the authoritative event, not whichever component reports success first.</p>
<p>A useful specification row is: <strong>from state; trigger; authorized actor; validation; resulting state; user-visible evidence; recovery path.</strong> Fill one row for each transition that changes a user’s options. If two transitions have different permissions or consequences, they are not the same transition just because they end in the same state.</p>
<h2 id="decide-who-owns-the-truth">Decide who owns the truth<a class="hlink" href="#decide-who-owns-the-truth" title="Permanent link">#</a></h2>
<p>Distributed workflows often have several versions of “current”: the model’s conversation, a browser view, an application record, and a queued job. If those disagree, the product needs a rule for which one wins.</p>
<p>Write the rule in plain language. For instance: “The application record is authoritative for whether a change was applied; the model’s response is only a proposal.” Then specify what the interface does when the record cannot be checked. It should not quietly display “Applied” based only on the model’s assertion.</p>
<p>This also clarifies what counts as evidence. A transition might require a saved record identifier, a confirmed status from the destination service, or a human approval event. Avoid vague conditions like “when the task succeeds.” Success must name an observable signal and its source.</p>
<p>For asynchronous work, include timestamps or freshness cues where stale information could mislead. A status read five minutes ago may be accurate enough for a long-running report and dangerously stale for a payment or permission change. The product requirement should state the acceptable freshness based on the consequence of acting on old state.</p>
<h2 id="make-interruption-a-first-class-path">Make interruption a first-class path<a class="hlink" href="#make-interruption-a-first-class-path" title="Permanent link">#</a></h2>
<p>Users navigate away, networks fail, and workers restart. If the workflow has no defined behavior for interruption, users will interpret missing progress as either failure or success, often incorrectly.</p>
<p>For every in-progress state, answer three questions:</p>
<ol>
<li>Can the work continue without the user’s session?</li>
<li>If the user returns, how is the same task found rather than duplicated?</li>
<li>If the outcome is unknown, what can the user safely do next?</li>
</ol>
<p>Suppose a request times out after submission. The system may not know whether the downstream action completed. Offering “try again” without checking can create a duplicate. Better behavior is to show “Checking status,” use a stable task identifier to reconcile the result, and only offer a retry when the original action is known not to have applied—or when the action is safe to repeat.</p>
<p>Not every product needs sophisticated recovery. A one-off draft generator may simply let the user restart. But that should be a deliberate decision based on the cost of duplicated work and lost progress, not an accidental consequence of the implementation.</p>
<h2 id="put-the-contract-in-the-acceptance-criteria">Put the contract in the acceptance criteria<a class="hlink" href="#put-the-contract-in-the-acceptance-criteria" title="Permanent link">#</a></h2>
<p>A state contract becomes useful when it changes what the team tests. For each transition, define at least one normal case and one case where the expected evidence is missing or contradictory.</p>
<p>A compact acceptance checklist:</p>
<ul>
<li>The visible label matches the authoritative state.</li>
<li>Only permitted actors can trigger the transition.</li>
<li>A transition is not shown as complete before its evidence arrives.</li>
<li>Refreshing or reopening the workflow does not invent a new state.</li>
<li>A timeout or partial failure leaves a safe next action.</li>
<li>Repeating an action cannot silently duplicate a consequential change.</li>
</ul>
<p>These are not all model-quality tests. They are workflow tests around the model. That distinction helps teams avoid treating every inconsistency as a prompt problem when the real gap is ownership, persistence, or permissions.</p>
<p>The next time a feature crosses a model boundary, ask in review: <strong>What is the source of truth for each state, and what evidence authorizes the next transition?</strong> If the answer is unclear, the workflow is not ready for a polished status badge.</p>]]></content:encoded></item><item><title>What would have happened anyway? The missing line in case studies</title><link>https://saumya.dabi.live/blog/a-case-study-needs-a-counterfactual</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-case-study-needs-a-counterfactual</guid>
<pubDate>Sat, 10 Oct 2026 02:00:00 +0000</pubDate><category>Career</category>
<description>A polished launch story shows what happened, not whether the product choice mattered. Use a counterfactual to make the decision, evidence, and trade-off legible.</description><content:encoded><![CDATA[<p>A case study can show a thoughtful process and still leave the reader unsure whether the product decision mattered. The missing piece is often a counterfactual: what would likely have happened if the team had chosen the next-best option instead?</p>
<h2 id="make-the-alternative-explicit">Make the alternative explicit<a class="hlink" href="#make-the-alternative-explicit" title="Permanent link">#</a></h2>
<p>A launch narrative tends to move in one direction: problem, solution, result. That structure hides the choice. If the case study never names a credible alternative, the selected path can look inevitable rather than reasoned.</p>
<p>Write one sentence before describing the solution: “We could have <strong><em>_, but chose </em></strong>_ because ____.” The alternative should be something a competent team might actually do, not a straw man.</p>
<p>For example, imagine a workflow where users repeatedly submit incomplete requests. The options might be to add more required fields up front, or let people submit quickly and route missing details to a follow-up queue. Neither is universally right. The trade-off is between preventing incomplete work at entry and preserving a low-friction start while accepting operational follow-up.</p>
<h2 id="state-what-the-alternative-predicts">State what the alternative predicts<a class="hlink" href="#state-what-the-alternative-predicts" title="Permanent link">#</a></h2>
<p>A counterfactual is useful when it makes a prediction. “The longer form would reduce missing information” is testable; “the longer form would be worse” is not.</p>
<p>Use a compact comparison:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Choice</th>
<th>Expected benefit</th>
<th>Cost or risk</th>
<th>Evidence to watch</th>
</tr>
</thead>
<tbody>
<tr>
<td>Require more fields</td>
<td>Fewer incomplete requests</td>
<td>More abandonment before submission</td>
<td>Completion rate and missing-field rate</td>
</tr>
<tr>
<td>Accept, then follow up</td>
<td>Lower entry friction</td>
<td>More manual queue work</td>
<td>Submission rate and follow-up volume</td>
</tr>
</tbody>
</table></div>
<p>The table does not need invented results. If the work was not measured, label the evidence as a hypothesis or an instrumentation gap. Clear uncertainty is more credible than a precise-looking number without a trustworthy source.</p>
<h2 id="show-why-the-decision-changed">Show why the decision changed<a class="hlink" href="#show-why-the-decision-changed" title="Permanent link">#</a></h2>
<p>The strongest case studies show how evidence moved the team from one option to another. Say what you knew at the decision point, what remained unknown, and what observation would have caused you to reverse the choice.</p>
<p>A useful template:</p>
<blockquote>
<p>We chose <strong>A</strong> over <strong>B</strong> because <strong>[evidence or constraint]</strong>. We accepted <strong>[specific cost]</strong>. If <strong>[observable signal]</strong> crosses <strong>[threshold or condition]</strong>, we will revisit the choice.</p>
</blockquote>
<p>If no threshold existed, say what review point or qualitative trigger the team used. Do not retrofit certainty after the outcome is known. Separate the original reasoning from what you learned later.</p>
<h2 id="keep-the-claim-proportional">Keep the claim proportional<a class="hlink" href="#keep-the-claim-proportional" title="Permanent link">#</a></h2>
<p>A case study should not claim that a decision caused an outcome when several changes happened together. It can still be useful: describe the decision, the evidence available, the observed result, and the limits on attribution.</p>
<p>Before publishing, ask: What was the real alternative? What did each option predict? What cost did the chosen path accept? What evidence could have changed my mind? If those answers fit in a few sentences, the reader can evaluate your judgment—not just admire the screenshots.</p>]]></content:encoded></item><item><title>Mistral Large 4 makes the wait part of the launch</title><link>https://saumya.dabi.live/blog/mistral-large-4-preview</link><guid isPermaLink="true">https://saumya.dabi.live/blog/mistral-large-4-preview</guid>
<pubDate>Tue, 06 Oct 2026 02:00:00 +0000</pubDate><category>Teardown</category>
<description>Mistral announced a public API preview for Large 4 while saying the weights would arrive later. That staged release is a product promise about who can test what, and when.</description><content:encoded><![CDATA[<p>A model launch is often described as a single moment. Mistral Large 4&rsquo;s announcement makes the rollout itself part of the product: the public preview API is available now, while the company says the weights will be released by the end of the month. <a href="https://mistral.ai/news/mistral-large-4/">Mistral, “Introducing Mistral Large 4,” published 2026-10-06</a>.</p>
<p>That split creates two different promises. API users can begin testing an early preview through the hosted service. People waiting for weights have to wait for another milestone. The announcement also says Mistral is using the preview period for real-world red-teaming with selected partners and authorities before the weights release.</p>
<h2 id="the-useful-question-is-who-can-learn-now">The useful question is who can learn now<a class="hlink" href="#the-useful-question-is-who-can-learn-now" title="Permanent link">#</a></h2>
<p>A staged launch can be a sensible way to collect feedback and reduce risk. But “available” needs a noun after it. Available to whom, through which interface, under what limits, and with what production expectations?</p>
<p>A PM evaluating a model release should separate three things: access to a preview, evidence about performance on the intended workload, and permission to build a dependable product around it. A preview can help answer the first technical questions. It does not automatically answer the other two.</p>
<p>For the user, the key contract is what can be tried now and what remains promised for later. For the team, the key decision is whether the preview is enough to start an experiment or whether the missing weights are a dependency. Those are different launch outcomes and should not be collapsed into one headline.</p>
<h2 id="make-the-milestone-testable">Make the milestone testable<a class="hlink" href="#make-the-milestone-testable" title="Permanent link">#</a></h2>
<p>The announcement gives a timing expectation for the weights, not a substitute for the evaluation a product team needs. Before adopting any preview, write down the workload, the failure cases, the access mode, and the point at which you will decide to continue or wait.</p>
<p>The teardown rule: treat staged availability as separate product states. Evaluate each one on what it lets your users and team do today, not on what the next milestone may unlock.</p>]]></content:encoded></item><item><title>Make AI tools show their work before asking for trust</title><link>https://saumya.dabi.live/blog/make-ai-tools-show-their-work</link><guid isPermaLink="true">https://saumya.dabi.live/blog/make-ai-tools-show-their-work</guid>
<pubDate>Sat, 03 Oct 2026 02:00:00 +0000</pubDate><category>AI Product</category>
<description>A fluent answer can hide missing evidence. Design AI interfaces to expose the source, the transformation and the uncertainty behind consequential outputs, so users can verify the part that matters.</description><content:encoded><![CDATA[<p>A polished answer can feel more reliable than it is. That is a dangerous interface property: fluency is visible immediately, while missing context may only become obvious after a user acts. If an AI feature asks people to trust a consequential recommendation, it should also give them a practical way to inspect how that recommendation was formed.</p>
<h2 id="explainability-is-a-user-task">Explainability is a user task<a class="hlink" href="#explainability-is-a-user-task" title="Permanent link">#</a></h2>
<p>“Explain the model” is too broad to guide a product decision. A user usually has a narrower question: Which document supports this claim? What changed between the source and the summary? What assumption drove this recommendation? Can I correct the input?</p>
<p>The interface should answer the task, not offer a vague promise of transparency. Consider a tool that summarizes a policy and flags a possible exception. A useful result might show:</p>
<ul>
<li><strong>Source:</strong> the exact passage and document date.</li>
<li><strong>Interpretation:</strong> the condition the system believes the passage expresses.</li>
<li><strong>Application:</strong> why the current case appears to match or differ.</li>
<li><strong>Next step:</strong> inspect the cited section, provide missing context, or route for review.</li>
</ul>
<p>These parts should not collapse into one confident paragraph. A quoted passage is evidence; the interpretation is a transformation; the recommendation is a decision aid. Keeping them distinct lets the user challenge the right layer.</p>
<h2 id="put-evidence-beside-the-claim">Put evidence beside the claim<a class="hlink" href="#put-evidence-beside-the-claim" title="Permanent link">#</a></h2>
<p>A source link at the bottom of a long response is technically available but often practically disconnected. The user has to guess which source supports which sentence, then reconstruct the reasoning themselves.</p>
<p>Instead, attach evidence at the smallest useful unit. In a generated summary, each material claim can open its supporting passage. In a classification result, show the input fields that influenced the category. In a draft, mark which sentences are extracted facts and which are proposed language.</p>
<p>This does not mean exposing every internal step or dumping a chain of thought. The product needs to present verifiable inputs and outputs, not private reasoning traces. Evidence should be selected for user inspection: source locations, relevant values, timestamps, and transformations that can be checked.</p>
<p>A practical design test is to hide the main answer and ask a reviewer to find the support for one claim. If the reviewer cannot do that without searching the entire source set, the evidence is not close enough to the decision.</p>
<h2 id="show-the-transformation-not-just-the-citation">Show the transformation, not just the citation<a class="hlink" href="#show-the-transformation-not-just-the-citation" title="Permanent link">#</a></h2>
<p>A citation can be correct while the conclusion is wrong. The source may say that a condition applies only after a date; the summary may omit the date. The document may describe a proposed rule, while the interface presents it as active. A matching phrase may be irrelevant to the user&rsquo;s specific case.</p>
<p>So show the transformation in a compact form. For example:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Stage</th>
<th>What the interface can show</th>
</tr>
</thead>
<tbody>
<tr>
<td>Source</td>
<td>Passage, page or record, and its date</td>
</tr>
<tr>
<td>Extracted fact</td>
<td>The specific value or condition identified</td>
</tr>
<tr>
<td>Applied rule</td>
<td>The rule or criterion used for this case</td>
</tr>
<tr>
<td>Result</td>
<td>The conclusion, with any unresolved condition</td>
</tr>
</tbody>
</table></div>
<p>The table is not a demand to reveal model internals. It is a way to let the user inspect the product&rsquo;s observable reasoning: what information was used, what rule was applied, and what conclusion followed.</p>
<p>When a result depends on a missing field, say so directly. “Needs review” is less useful than “The effective date is absent; check the original record before applying this rule.” The latter makes uncertainty actionable.</p>
<h2 id="design-for-correction">Design for correction<a class="hlink" href="#design-for-correction" title="Permanent link">#</a></h2>
<p>Inspection without correction can become theater. If users can spot an error but cannot fix its input or route it to someone who can, the interface has exposed a problem without providing a safe path forward.</p>
<p>For each kind of evidence, define an appropriate correction:</p>
<ul>
<li>Wrong source: remove it or select the correct record.</li>
<li>Wrong extracted value: edit the value or mark it as unavailable.</li>
<li>Wrong interpretation: choose the correct condition or request review.</li>
<li>Incomplete context: add the missing information before rerunning.</li>
</ul>
<p>Keep these edits explicit. A user changing an extracted value should be able to see whether the final result updates, and whether the correction is local to this case or changes a reusable source record. Those are different permissions and consequences.</p>
<p>A useful design exercise is to write one error-recovery path for each visible evidence element. If a cited passage is outdated, what can the user do? If a field was misread, what changes downstream? If the system cannot decide, who receives the case and what information arrives with it?</p>
<h2 id="match-confidence-language-to-evidence">Match confidence language to evidence<a class="hlink" href="#match-confidence-language-to-evidence" title="Permanent link">#</a></h2>
<p>A percentage can look precise without telling a user what to do. Unless the number is calibrated for the relevant task and population, “92% confident” may communicate more certainty than the product can justify.</p>
<p>Prefer confidence language tied to observable conditions: “Two required fields are present,” “The source documents disagree,” or “No supporting passage was found.” These statements point to the basis of the result and can be checked. If a score is needed for ranking, explain what it measures and avoid presenting it as a probability unless that interpretation is warranted.</p>
<p>The same principle applies to visual hierarchy. Do not style an unverified suggestion like a completed action. A recommendation awaiting confirmation should look and behave differently from a transaction that has been confirmed by the destination system.</p>
<h2 id="a-review-checklist-for-the-interface">A review checklist for the interface<a class="hlink" href="#a-review-checklist-for-the-interface" title="Permanent link">#</a></h2>
<p>Before shipping an AI-generated answer that could change a user&rsquo;s decision, test it with a reviewer who did not build the feature. Give them a realistic example and ask them to locate the evidence, identify what the system inferred, correct a wrong input, and explain what remains uncertain.</p>
<p>Use this checklist:</p>
<ol>
<li>Can the user connect each important claim to its supporting source?</li>
<li>Can they distinguish source facts from system interpretation?</li>
<li>Can they identify missing, conflicting, or stale information?</li>
<li>Is there a safe correction or escalation path?</li>
<li>Does the interface distinguish a suggestion from a confirmed outcome?</li>
</ol>
<p>If the reviewer cannot answer those questions, adding a longer explanation probably will not fix the core problem. Change the interaction so the evidence is inspectable and the next action is clear. Trust should be earned at the point where the user needs to decide.</p>]]></content:encoded></item><item><title>Log every action before an agent retries</title><link>https://saumya.dabi.live/blog/retries-need-an-action-ledger</link><guid isPermaLink="true">https://saumya.dabi.live/blog/retries-need-an-action-ledger</guid>
<pubDate>Tue, 29 Sep 2026 02:00:00 +0000</pubDate><category>Agents</category>
<description>When an agent times out after taking an action, retrying can duplicate the work. Give every attempted action a durable record, a status and a recovery rule before trusting automation.</description><content:encoded><![CDATA[<p>An agent calls a tool to create a booking. The request times out. Did the booking fail, or did the tool create it and lose the response? If the agent simply tries again, the user may get two bookings. If it gives up, the user may be left with none. A reliable agent needs a record of what it tried to do and a rule for resolving the unknown state.</p>
<h2 id="a-timeout-is-not-an-outcome">A timeout is not an outcome<a class="hlink" href="#a-timeout-is-not-an-outcome" title="Permanent link">#</a></h2>
<p>A tool call has at least two separate events: the system receives a request, and the agent receives a response. A timeout tells us the second event did not happen in time. It does not tell us whether the first event caused a change.</p>
<p>This distinction matters whenever a tool can create an external side effect: sending a message, issuing a refund, submitting a form, changing access, or reserving a resource. The agent&rsquo;s conversational context may say “I tried,” but that is not evidence about the external system.</p>
<p>Start by classifying each action in a workflow:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Action type</th>
<th>Example</th>
<th>Safe default after timeout</th>
</tr>
</thead>
<tbody>
<tr>
<td>Read-only</td>
<td>Fetch a status</td>
<td>Retry within a bounded limit</td>
</tr>
<tr>
<td>Naturally idempotent</td>
<td>Set a record&rsquo;s status to a specific value</td>
<td>Retry if the operation truly replaces the same state</td>
</tr>
<tr>
<td>Non-idempotent</td>
<td>Create a new booking</td>
<td>Check for the original result before repeating</td>
</tr>
<tr>
<td>Irreversible or high impact</td>
<td>Send a payment instruction</td>
<td>Stop and reconcile or request human review</td>
</tr>
</tbody>
</table></div>
<p>“Idempotent” needs to describe the actual tool behavior, not the intention in the prompt. Setting a field to “approved” may be repeatable; calling “approve next item” may approve a different item on the second attempt. Ask what the external system does when it receives the same request twice.</p>
<h2 id="give-each-action-a-durable-identity">Give each action a durable identity<a class="hlink" href="#give-each-action-a-durable-identity" title="Permanent link">#</a></h2>
<p>For every consequential action, create an action record before calling the tool. It should have a stable identifier that survives model retries, plus enough context to reconcile the call without storing unnecessary sensitive data.</p>
<p>A minimal ledger entry could contain:</p>
<ul>
<li><strong>Action ID:</strong> a unique identifier for this intended operation.</li>
<li><strong>Intent:</strong> the specific change requested, in a compact and inspectable form.</li>
<li><strong>Target:</strong> the external record or resource, if known.</li>
<li><strong>Status:</strong> prepared, submitted, confirmed, failed, or needs reconciliation.</li>
<li><strong>Attempt details:</strong> timestamps, tool response identifiers, and error category.</li>
<li><strong>Recovery rule:</strong> what the system may safely do next.</li>
</ul>
<p>The key is to persist the action ID before sending the request. If the process crashes after the external action but before updating its own state, the record still tells the recovery path that an operation may be in flight.</p>
<p>Where the external service supports an idempotency key, pass the same action ID on each retry of the same intended operation. Do not generate a fresh key just because the previous response was missing; that would turn a retry into a new operation. If the service has no idempotency support, use a lookup or reconciliation step before attempting another write.</p>
<p>A ledger is not a transcript of the agent&rsquo;s reasoning. It is a state machine for side effects. Keep it small enough that software can enforce it and an operator can understand it during an incident.</p>
<h2 id="separate-intent-from-execution">Separate intent from execution<a class="hlink" href="#separate-intent-from-execution" title="Permanent link">#</a></h2>
<p>A useful state model distinguishes what the agent decided from what the external system confirmed. One practical sequence is:</p>
<ol>
<li><strong>Prepared:</strong> the action and its authorization are recorded, but no request has been sent.</li>
<li><strong>Submitted:</strong> the request was sent; the outcome may still be unknown.</li>
<li><strong>Confirmed:</strong> a response or subsequent lookup proves the intended result exists.</li>
<li><strong>Failed:</strong> the system has evidence the action did not happen and will not complete later.</li>
<li><strong>Needs reconciliation:</strong> the outcome is ambiguous or the system cannot safely decide.</li>
</ol>
<p>Avoid treating a caught exception as equivalent to “failed.” A connection reset after submission is ambiguous; a validation error returned before execution may be a definite failure. The tool adapter should classify errors according to its contract, not ask the language model to guess from an arbitrary message.</p>
<p>The agent&rsquo;s user-facing language should follow the same distinction. If the action is merely submitted, say it is being checked. Do not claim success until there is confirmation. If the result is unknown, explain that clearly and avoid inviting the user to repeat the request before reconciliation.</p>
<h2 id="make-retries-a-policy-not-a-reflex">Make retries a policy, not a reflex<a class="hlink" href="#make-retries-a-policy-not-a-reflex" title="Permanent link">#</a></h2>
<p>Retry logic should be attached to the action type and error category. A generic “retry three times” wrapper can multiply side effects while appearing robust.</p>
<p>A decision rule can be simple:</p>
<ul>
<li>If the operation is read-only and the error is transient, retry with a bounded delay.</li>
<li>If the operation is idempotent and uses the same key, retry that same operation.</li>
<li>If the operation is non-idempotent and the outcome is unknown, query by action ID or target before any new write.</li>
<li>If reconciliation cannot establish the outcome, mark it for a person rather than guessing.</li>
<li>If the error is a permission or validation failure, do not retry unchanged; surface the missing prerequisite or correct the input.</li>
</ul>
<p>Set a maximum retry window as well as a count. A request that remains ambiguous after the useful recovery period should leave the automated path and enter a visible queue. Otherwise, retries can continue long after the user has moved on, or a backlog can quietly accumulate.</p>
<p>For example, suppose an agent submits a support ticket through a tool that has no idempotency key. The request times out. The recovery worker searches for a ticket matching the action&rsquo;s external reference. If exactly one match exists, it marks the action confirmed. If none exists and the service can guarantee the earlier request is no longer processing, it may submit once more. If there are multiple matches or the search is inconclusive, it routes the record for review. The rule is explicit about what the system knows and what it does not.</p>
<h2 id="design-reconciliation-before-launch">Design reconciliation before launch<a class="hlink" href="#design-reconciliation-before-launch" title="Permanent link">#</a></h2>
<p>A ledger only helps if the system can use it to resolve uncertainty. During design, answer these questions for every external write:</p>
<ul>
<li>Can the destination accept an idempotency key or client-generated reference?</li>
<li>Can the system query for the result using that reference?</li>
<li>How long can a request remain in progress after the caller times out?</li>
<li>Who owns unresolved actions, and where do they see them?</li>
<li>What should the user see while the result is unknown?</li>
<li>How can an operator safely retry, cancel, or mark the action resolved?</li>
</ul>
<p>Test the failure sequence, not only the happy path. Simulate a timeout before the request reaches the service, after the service commits but before its response returns, and while the destination is still processing. The expected result should be different where the evidence differs: no action to reconcile, a completed action to recognize, or an in-flight action to wait on.</p>
<p>Track operational signals that reveal whether the design is working: actions in the needs-reconciliation state, their age, duplicate effects found, and manual resolutions. These are not interchangeable with model-quality scores. They tell you whether the product can account for its side effects.</p>
<p>Before an agent can perform a consequential write, complete this checklist: <strong>stable action ID; persisted intent; known idempotency behavior; timeout classification; reconciliation query; bounded retry rule; owner for unresolved outcomes.</strong> If any item is missing, keep the action behind a human confirmation step until the recovery path exists.</p>]]></content:encoded></item><item><title>Set a failure budget before you read the eval score</title><link>https://saumya.dabi.live/blog/an-eval-score-needs-a-failure-budget</link><guid isPermaLink="true">https://saumya.dabi.live/blog/an-eval-score-needs-a-failure-budget</guid>
<pubDate>Sat, 26 Sep 2026 02:00:00 +0000</pubDate><category>Evals</category>
<description>A single pass rate hides which failures your product is accepting. Turn an eval into a failure budget with explicit limits, owners and release decisions.</description><content:encoded><![CDATA[<p>A model can improve its overall eval score while getting worse at the one mistake users cannot safely recover from. The aggregate number will still look reassuring if enough easy cases pass. I would treat an eval as a failure budget: a small set of explicit limits on the failures the product can tolerate, each tied to a decision.</p>
<h2 id="a-pass-rate-is-not-a-risk-statement">A pass rate is not a risk statement<a class="hlink" href="#a-pass-rate-is-not-a-risk-statement" title="Permanent link">#</a></h2>
<p>Suppose an assistant answers questions from a support knowledge base. An eval has 100 examples: 80 ordinary questions, 15 questions with incomplete information, and 5 where the correct response is to refuse or ask for clarification. A model that passes all ordinary questions and fails all five refusal cases posts a 95% pass rate. That number says little about whether the assistant is safe to expose to users.</p>
<p>The denominator is doing too much work. Each example counts equally, but examples differ in consequence and frequency. Worse, the eval designer may have included many variations of the easy cases because they were simple to write. The resulting score measures the shape of the test set as much as product quality.</p>
<p>A failure budget makes the hidden trade-off visible. Instead of one threshold, define limits by failure class. For example:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Failure class</th>
<th>Release limit</th>
<th>Product response</th>
</tr>
</thead>
<tbody>
<tr>
<td>Incorrect answer when source is clear</td>
<td>At most 3 of 80</td>
<td>Fix retrieval or answer grounding</td>
</tr>
<tr>
<td>Confident answer when source is missing</td>
<td>0 of 15</td>
<td>Block release; require abstention or clarification</td>
</tr>
<tr>
<td>Refusal on an answerable question</td>
<td>At most 2 of 30</td>
<td>Review refusal boundary and user recovery</td>
</tr>
</tbody>
</table></div>
<p>These are illustrative limits, not universal benchmarks. The important move is to make the threshold a product decision before looking at the result.</p>
<h2 id="define-failures-in-terms-users-experience">Define failures in terms users experience<a class="hlink" href="#define-failures-in-terms-users-experience" title="Permanent link">#</a></h2>
<p>A taxonomy should describe what went wrong for the user, not only which model component may have caused it. “Retrieval failure” is useful to engineers, but it does not tell a PM what to count. “The assistant gave a specific answer despite having no supporting source” is observable and actionable.</p>
<p>For each class, write three things:</p>
<ol>
<li><strong>Observable condition:</strong> What would a reviewer mark as a failure? Avoid labels such as “bad answer.”</li>
<li><strong>User consequence:</strong> What happens next if this failure reaches production? Can the user notice, correct, or reverse it?</li>
<li><strong>Exposure:</strong> How often is the triggering situation likely to occur in the real workflow?</li>
</ol>
<p>Consider a generated summary that omits a qualification. A generic omission might be minor when a user can inspect the original document. The same omission could be critical if the summary is used as the only input to a later decision. The output text alone may be identical; the workflow context changes the consequence.</p>
<p>A practical annotation rule could say: “Mark unsupported certainty when the answer asserts a fact absent from the provided sources, without signalling uncertainty or asking for the missing information.” Two reviewers can apply that rule more consistently than “mark hallucinations.” Include counterexamples: a concise answer that leaves out irrelevant detail should not fail merely because it is not exhaustive.</p>
<h2 id="set-limits-using-consequence-and-exposure">Set limits using consequence and exposure<a class="hlink" href="#set-limits-using-consequence-and-exposure" title="Permanent link">#</a></h2>
<p>A failure budget is not just a severity ranking. A rare catastrophic failure and a frequent recoverable error require different controls. Start with a simple risk sketch for each failure class:</p>
<ul>
<li><strong>Severity:</strong> What is the worst plausible user consequence?</li>
<li><strong>Exposure:</strong> How often does the relevant situation appear?</li>
<li><strong>Recoverability:</strong> Can the user detect and undo the result before harm?</li>
</ul>
<p>This need not pretend to be precise math. A low/medium/high rating is often more honest than multiplying made-up numbers into a risk score. Use the ratings to decide whether a class needs a zero-tolerance gate, a maximum count, or monitoring after launch.</p>
<p>For instance, unsupported certainty in a high-consequence workflow might have a zero-failure release limit on a targeted challenge set. A stylistic mismatch in a low-stakes draft assistant might be tracked as a trend rather than a blocker. The test size also matters: “zero failures” across five examples is weak evidence. State the number and type of examples next to the limit, then expand the set as you observe real cases.</p>
<p>Do not silently change the limit when the model misses it. If a release is still justified, record who accepts the exception, the affected workflow, the temporary mitigation, and when the decision expires. Otherwise a budget is just a dashboard decoration.</p>
<h2 id="make-every-limit-lead-to-an-action">Make every limit lead to an action<a class="hlink" href="#make-every-limit-lead-to-an-action" title="Permanent link">#</a></h2>
<p>A threshold without an owner creates debate at the worst time: after a release candidate has failed. For each failure class, decide in advance what happens when the limit is exceeded.</p>
<p>A compact release table can include:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Field</th>
<th>Question to answer</th>
</tr>
</thead>
<tbody>
<tr>
<td>Failure class</td>
<td>What user-visible outcome are we counting?</td>
</tr>
<tr>
<td>Test slice</td>
<td>Which examples exercise it, and how many?</td>
</tr>
<tr>
<td>Limit</td>
<td>What is the maximum acceptable count?</td>
</tr>
<tr>
<td>Owner</td>
<td>Who decides whether the result is understood?</td>
</tr>
<tr>
<td>Action</td>
<td>Block, fix, add a guardrail, or accept a time-limited exception?</td>
</tr>
</tbody>
</table></div>
<p>The action can depend on the cause. If a failure appears only when source material is absent, a product-level clarification step may be more effective than prompt tuning. If the model ignores a clear source, retrieval or generation may need investigation. The taxonomy should support diagnosis without assuming the cause in advance.</p>
<p>This also separates two questions teams often collapse: “Did the system get better?” and “Is it safe enough for this workflow?” A model may improve on average while still exceeding a critical failure limit. Conversely, a small regression in a low-consequence class may be acceptable if a major user risk has been reduced. The budget preserves both facts instead of squeezing them into one score.</p>
<h2 id="keep-the-budget-alive-after-launch">Keep the budget alive after launch<a class="hlink" href="#keep-the-budget-alive-after-launch" title="Permanent link">#</a></h2>
<p>Offline examples are a sample, not a complete map of production. When users correct an answer, abandon a flow, or ask for a human, those events can reveal missing failure classes. Review a small, privacy-appropriate sample of these signals regularly and turn confirmed patterns into eval cases.</p>
<p>Do not add every odd output as a new test. First ask whether it represents a distinct user-visible failure, whether the existing taxonomy already covers it, and what decision the test would change. A test that has no plausible effect on a release or product decision adds maintenance without adding control.</p>
<p>On Monday, take your current eval and write down its three most consequential failure classes. For each one, name the observable failure, the test slice and denominator, the release limit, and the person who owns the next action. If you cannot fill those in, the headline score is not yet a release criterion.</p>]]></content:encoded></item><item><title>Name the assumption that would kill your product bet</title><link>https://saumya.dabi.live/blog/name-the-assumption-that-would-kill-your-bet</link><guid isPermaLink="true">https://saumya.dabi.live/blog/name-the-assumption-that-would-kill-your-bet</guid>
<pubDate>Tue, 22 Sep 2026 02:00:00 +0000</pubDate><category>PM Craft</category>
<description>Roadmap bets often list upside and effort while hiding the belief they depend on. Write the disconfirming observation first, then design the smallest test that could change your plan.</description><content:encoded><![CDATA[<p>A roadmap proposal can look rigorous and still rest on one sentence nobody has tested: users will change their behavior if we make this easier. The estimate may be detailed; the dependency is not. Before I argue for a bet, I want to name the observation that would make me stop believing in it.</p>
<h2 id="separate-the-evidence-from-the-belief">Separate the evidence from the belief<a class="hlink" href="#separate-the-evidence-from-the-belief" title="Permanent link">#</a></h2>
<p>A product bet usually mixes three things: an observed problem, an explanation for it, and a proposed response. They deserve separate lines because each has a different confidence level.</p>
<p>Consider a hypothetical scheduling tool. A team sees that people abandon a booking form. That is an observation, assuming the event is measured correctly. “They abandon because the form asks for too much detail” is an explanation. “A shorter form will increase completed bookings without creating costly follow-up” is a bet.</p>
<p>Those statements can all be plausible while only the first is directly evidenced. If a roadmap discussion jumps from abandonment to shorter form, it quietly treats the explanation as fact and the outcome as guaranteed.</p>
<p>Write the chain explicitly:</p>
<ul>
<li><strong>Observed:</strong> What did we see, and where did the evidence come from?</li>
<li><strong>Believed:</strong> What explanation are we using?</li>
<li><strong>Bet:</strong> What change do we expect to improve, for whom?</li>
<li><strong>Risk:</strong> What else might happen if the belief is wrong?</li>
</ul>
<p>The purpose is not to make a proposal sound cautious. It is to locate the uncertainty that could invalidate the work. A team can then debate the actual belief rather than arguing about a feature as if it were the only possible response.</p>
<h2 id="write-the-disconfirming-observation">Write the disconfirming observation<a class="hlink" href="#write-the-disconfirming-observation" title="Permanent link">#</a></h2>
<p>“Validate the idea” is too vague to guide a test. Ask instead: what could we observe that would make us change the plan?</p>
<p>For the shorter booking form, one disconfirming observation might be that people who abandon do so before reaching the long section. Another might be that users complete a short version but then require enough follow-up that the total task becomes harder. These observations point in different directions: the first challenges the diagnosis; the second challenges the proposed trade-off.</p>
<p>A useful statement has four parts:</p>
<blockquote>
<p>If we observe <strong>[specific evidence]</strong> in <strong>[defined context]</strong>, we will <strong>[change, pause or stop]</strong> because it weakens <strong>[assumption]</strong>.</p>
</blockquote>
<p>For example: “If people who leave the current form mostly exit before seeing the detailed questions, we will not build a shorter-form project around those questions as the primary cause.” That does not prove what the cause is. It prevents the team from treating one untested explanation as settled.</p>
<p>Make the threshold concrete enough that two people would interpret the result similarly. “Users dislike it” is not a threshold. “In five moderated sessions, at least four participants reach the same point of confusion without prompting” is more observable, though it is still a small qualitative signal, not a population estimate. The method and the claim must stay proportional to each other.</p>
<h2 id="test-the-uncertainty-not-the-solution-polish">Test the uncertainty, not the solution polish<a class="hlink" href="#test-the-uncertainty-not-the-solution-polish" title="Permanent link">#</a></h2>
<p>When the risky assumption concerns the cause of a problem, a polished prototype may answer the wrong question. It can show that someone can use the proposed interface while telling you little about why the existing behavior occurs.</p>
<p>Start with the least expensive test that can distinguish between plausible explanations. Depending on the question, that could be:</p>
<ul>
<li>Inspecting a small, clearly defined sample of session replays or support cases, if they are available and appropriate to use.</li>
<li>Asking users to complete the current task while narrating what they expect at the point of hesitation.</li>
<li>Showing a low-fidelity alternative only after understanding the current behavior.</li>
<li>Running a manual concierge version of the proposed change, with a clear record of what was done by a person.</li>
</ul>
<p>A small sample is useful for finding a mechanism or a surprising failure. It does not establish prevalence. If the decision depends on how common a behavior is, plan a measurement that can answer that question rather than stretching a handful of interviews into a percentage.</p>
<p>Write down the test before running it: target participant or case, task, evidence to capture, stopping condition, and the decision that follows each plausible result. If every result leads to the same roadmap item, the test is not informing the decision. Either the bet is already committed for another reason, or the experiment needs a different question.</p>
<h2 id="keep-the-cost-of-being-wrong-in-view">Keep the cost of being wrong in view<a class="hlink" href="#keep-the-cost-of-being-wrong-in-view" title="Permanent link">#</a></h2>
<p>Not every uncertain belief deserves an experiment. The cost of delay and the cost of a wrong bet both matter. A reversible copy change may justify a quick test; a workflow that can exclude eligible users or create substantial manual work deserves stronger evidence and a safer rollout.</p>
<p>Use a simple two-axis check:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>If the assumption is wrong…</th>
<th>Cost to reverse is low</th>
<th>Cost to reverse is high</th>
</tr>
</thead>
<tbody>
<tr>
<td>Impact is limited</td>
<td>Ship narrowly and observe</td>
<td>Test the risky edge before expanding</td>
</tr>
<tr>
<td>Impact is substantial</td>
<td>Set a visible guardrail and rollback</td>
<td>Pause for stronger evidence or redesign</td>
</tr>
</tbody>
</table></div>
<p>This is a conversation aid, not a scoring formula. “High impact” should refer to a concrete consequence for users, operations, or the business. “Easy to reverse” should include hidden costs such as migration, retraining, or trust lost after a bad experience.</p>
<p>Also name the downside you will monitor. If shortening the form reduces effort but increases incomplete submissions, the primary success signal cannot be the only metric on the launch readout. A guardrail makes the trade-off visible before the team starts celebrating the number that moved.</p>
<h2 id="turn-the-result-into-a-decision">Turn the result into a decision<a class="hlink" href="#turn-the-result-into-a-decision" title="Permanent link">#</a></h2>
<p>A test should end in a decision, not a slide labeled “learnings.” Before starting, write the possible outcomes and the action attached to each:</p>
<ol>
<li><strong>The explanation holds up:</strong> proceed to a bounded test of the proposed change.</li>
<li><strong>A different cause is more plausible:</strong> revise the problem framing and test that cause.</li>
<li><strong>Evidence is mixed or too weak:</strong> reduce the scope, gather a more discriminating signal, or defer the bet.</li>
<li><strong>The downside appears:</strong> stop or add a control before expanding.</li>
</ol>
<p>Keep the record short: the original assumption, evidence collected, limitation, decision, and owner of the next step. If the result changes the roadmap, preserve the reason alongside the change. That helps the team avoid repeating the same debate later, and gives future reviewers a fair account of what was knowable at the time.</p>
<p>Before you commit a roadmap bet, ask: what specific observation would make us change our mind, what is the smallest credible way to look for it, and what will we do if we find it?</p>]]></content:encoded></item><item><title>Show the decisions, not more screenshots</title><link>https://saumya.dabi.live/blog/a-portfolio-needs-a-decision-trail</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-portfolio-needs-a-decision-trail</guid>
<pubDate>Sat, 19 Sep 2026 02:00:00 +0000</pubDate><category>Career</category>
<description>Screenshots show what a product looked like, but not how a PM made the hard calls. Build each portfolio case around the decision, evidence, trade-off and result a reviewer can inspect.</description><content:encoded><![CDATA[<p>A portfolio can contain polished screens and still leave a hiring manager unsure how its author thinks. Screenshots prove that an interface existed; they rarely explain why one option was chosen over another. The strongest case study is not a gallery. It is a decision trail that lets a reader follow the reasoning from uncertainty to action.</p>
<h2 id="choose-a-decision-that-had-real-alternatives">Choose a decision that had real alternatives<a class="hlink" href="#choose-a-decision-that-had-real-alternatives" title="Permanent link">#</a></h2>
<p>Start by naming a decision, not the project. “We built an onboarding flow” describes activity. “We chose to let an operator review incomplete applications rather than reject them automatically” gives the reader something to assess.</p>
<p>A useful test: can you name at least two plausible options that were considered? If not, the case may be a feature description rather than a product decision. The alternatives need not be elaborate. They might be ship now versus wait for more evidence, automate versus keep a human checkpoint, or solve the common path versus handle an exception first.</p>
<p>Use a compact opening card:</p>
<ul>
<li><strong>Decision:</strong> What choice needed to be made?</li>
<li><strong>Constraint:</strong> What made the choice difficult (time, risk, data, dependencies, or user behavior)?</li>
<li><strong>My role:</strong> Which parts did I own, influence, or support?</li>
<li><strong>Outcome:</strong> What changed, and how do I know?</li>
</ul>
<p>For example, a hypothetical case might say: “We needed to decide whether to launch an automated document check or first improve the manual review queue. I owned the problem framing and workflow proposal; engineering and operations shaped feasibility. The available evidence was a small set of observed review cases, not a representative sample.” That is more informative than a long introduction to the company or product.</p>
<p>Be precise about responsibility. “I led” can mean anything from setting the direction to coordinating a meeting. Name the work: framed the problem, compared options, wrote acceptance criteria, designed the evaluation, or aligned the launch gate. A collaborative result does not become less impressive when the reader can tell who contributed what.</p>
<h2 id="show-what-you-knew-when-you-chose">Show what you knew when you chose<a class="hlink" href="#show-what-you-knew-when-you-chose" title="Permanent link">#</a></h2>
<p>A decision trail is strongest when it separates what was known then from what became clear later. Otherwise, a successful outcome can make an uncertain decision look obvious in retrospect.</p>
<p>Include the evidence that actually informed the choice: user interviews, support themes, workflow observations, a prototype test, operational records, or an evaluation set. State its limits in the same breath. “We reviewed 18 cases” is not a claim about all users; it tells a reader what the sample was. If the evidence was qualitative, say what pattern it suggested and what it could not establish.</p>
<p>A simple evidence block is enough:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Evidence at the time</th>
<th>What it suggested</th>
<th>What it could not prove</th>
</tr>
</thead>
<tbody>
<tr>
<td>A handful of observed handoffs</td>
<td>Reviewers repeatedly needed missing context</td>
<td>How often the issue occurred across the full user base</td>
</tr>
<tr>
<td>A prototype walkthrough</td>
<td>The proposed status labels were understood in that session</td>
<td>Whether users would complete the workflow in production</td>
</tr>
<tr>
<td>Existing operational notes</td>
<td>Exceptions consumed attention</td>
<td>Whether automation would reduce total work</td>
</tr>
</tbody>
</table></div>
<p>Use real details only when you can substantiate them and are permitted to share them. If confidentiality prevents naming a client or showing data, describe the structure of the problem at a safe level. Do not replace missing evidence with an invented metric. A clear limitation is a sign of judgment, not a weakness in the case.</p>
<h2 id="make-the-trade-off-visible">Make the trade-off visible<a class="hlink" href="#make-the-trade-off-visible" title="Permanent link">#</a></h2>
<p>The reader needs to see why the chosen option beat the alternatives. Put the options side by side and make the deciding criterion explicit. A paragraph can do this, but a small comparison is often faster to scan:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Option</th>
<th>Benefit</th>
<th>Cost or risk</th>
<th>Why it did or did not win</th>
</tr>
</thead>
<tbody>
<tr>
<td>Automate the full check</td>
<td>Less manual handling on the common path</td>
<td>Mistakes could reject a valid case</td>
<td>Deferred because the evidence did not cover enough exceptions</td>
</tr>
<tr>
<td>Add context to manual review</td>
<td>Improves the existing decision step</td>
<td>Keeps a human in the loop</td>
<td>Chosen as a narrower, reversible first move</td>
</tr>
</tbody>
</table></div>
<p>This is a hypothetical illustration, not a universal recommendation. In a different setting, the costs may reverse. The point is to expose the criterion: safety, time-to-learn, reversibility, customer effort, or operational capacity.</p>
<p>Also include the cost of the option you chose. Every product decision leaves something undone. If you chose a narrow launch, say what capability remained out of scope. If you chose speed, say which uncertainty you accepted and how you planned to watch it. A case that only lists benefits reads like a pitch; a case that names the compromise reads like product work.</p>
<h2 id="connect-the-artifact-to-the-decision">Connect the artifact to the decision<a class="hlink" href="#connect-the-artifact-to-the-decision" title="Permanent link">#</a></h2>
<p>Screens and diagrams are useful when they answer a question. Show the old and proposed workflow if the decision changed the sequence of work. Annotate the interface where a status, prompt, or fallback embodies the trade-off. Include a short excerpt of a spec or evaluation rubric if that artifact makes the reasoning concrete.</p>
<p>For each visual, add a caption with three parts: what the reader is seeing, what decision it represents, and what changed because of it. Avoid decorative device mockups that enlarge a screenshot without adding context. One annotated diagram can explain more than six screens in a carousel.</p>
<p>When the work cannot be shown, use a redacted or reconstructed artifact and label it honestly. Never imply that synthetic examples are production data. The purpose is to make the decision inspectable while respecting the boundaries around the underlying work.</p>
<h2 id="close-the-loop-without-overclaiming">Close the loop without overclaiming<a class="hlink" href="#close-the-loop-without-overclaiming" title="Permanent link">#</a></h2>
<p>End with the outcome and the next decision it enabled. Use a measured result if you have one, along with its denominator and time window. If measurement was unavailable, report the observable operational change and say what remained unmeasured. “The team shipped the workflow” is an output, not proof that the user problem improved.</p>
<p>Separate three things that portfolios often blur:</p>
<ol>
<li><strong>Delivery:</strong> What was released or changed?</li>
<li><strong>Effect:</strong> What user or business outcome was observed, and by what measure?</li>
<li><strong>Learning:</strong> What would you change now, given what happened or what you later learned?</li>
</ol>
<p>Do not claim causality from a before-and-after number unless the evidence supports it. If other changes happened at the same time, describe the result as an observed movement, not an effect caused solely by your work. If the launch was too recent to judge, say so and name the signal you would monitor next.</p>
<p>A useful final paragraph is often short: “We released the narrower review aid. It made the missing-context decision visible, but we did not yet have enough evidence to claim that it reduced total handling time. The next decision was whether to expand coverage after observing the exception types.” This gives the reader an outcome, a limit and a credible next step.</p>
<h2 id="use-the-same-trail-in-every-case">Use the same trail in every case<a class="hlink" href="#use-the-same-trail-in-every-case" title="Permanent link">#</a></h2>
<p>A portfolio does not need a new storytelling format for every project. Repeating a decision structure helps reviewers compare how you work across different problem spaces. Keep the case concise enough to scan, then provide detail where the evidence or trade-off deserves it.</p>
<p>Before publishing a case, check:</p>
<ul>
<li>Is the central decision clear in the first few lines?</li>
<li>Are at least two plausible options visible?</li>
<li>Can a reader distinguish my contribution from the team’s work?</li>
<li>Does each claim match evidence I can share?</li>
<li>Is the trade-off and the option not chosen explicit?</li>
<li>Does each screenshot or diagram explain a decision?</li>
<li>Are delivery, measured effect and learning kept separate?</li>
<li>Have I removed confidential names, data and identifying details?</li>
</ul>
<p>If a reviewer can trace what you knew, what you chose, what you gave up and what happened next, the portfolio is doing more than displaying finished work. It is showing how you make decisions when the answer is not yet obvious.</p>]]></content:encoded></item><item><title>Write the error budget before you add an AI feature</title><link>https://saumya.dabi.live/blog/write-the-error-budget-first</link><guid isPermaLink="true">https://saumya.dabi.live/blog/write-the-error-budget-first</guid>
<pubDate>Tue, 15 Sep 2026 02:00:00 +0000</pubDate><category>AI Product</category>
<description>A product brief for an AI feature should name which mistakes are tolerable, who absorbs them, and what happens when the system runs out of room. Here is a practical way to set that boundary.</description><content:encoded><![CDATA[<p>An AI feature can look reliable in a demo and still be a poor product bet. The missing question is often not “How accurate is it?” but “Which mistakes are we willing to let reach a user, and what do we do when they happen?” If the team cannot answer that before launch, it has not defined the feature’s reliability contract.</p>
<h2 id="start-with-the-user-level-harm">Start with the user-level harm<a class="hlink" href="#start-with-the-user-level-harm" title="Permanent link">#</a></h2>
<p>“Wrong answer” is too broad to guide a product decision. A spelling mistake in a generated draft and a fabricated eligibility reason may both count as incorrect outputs, but the cost and recovery path are different.</p>
<p>Write down a small set of outcome classes before selecting a metric. For a hypothetical assistant that drafts replies to customer messages, the classes might be:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Outcome</th>
<th>Example</th>
<th>Product response</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cosmetic defect</td>
<td>Awkward phrasing, meaning preserved</td>
<td>User edits before sending</td>
</tr>
<tr>
<td>Material omission</td>
<td>Misses a detail needed for a complete reply</td>
<td>Show the source details and require review</td>
</tr>
<tr>
<td>Unsupported claim</td>
<td>Adds a fact absent from the message or policy</td>
<td>Block sending; let the user rewrite</td>
</tr>
<tr>
<td>Sensitive disclosure</td>
<td>Includes private information belonging to someone else</td>
<td>Stop the workflow and route for investigation</td>
</tr>
</tbody>
</table></div>
<p>This is not a universal taxonomy. It is a prompt to describe failures in terms of what a user experiences and what the product must do next. Keep the categories few enough that a reviewer can classify a test case without a debate.</p>
<h2 id="give-each-class-its-own-boundary">Give each class its own boundary<a class="hlink" href="#give-each-class-its-own-boundary" title="Permanent link">#</a></h2>
<p>A single “acceptable error rate” hides the distribution of risk. If harmless style defects dominate a test set, the overall score can look strong while a rarer but consequential failure remains invisible.</p>
<p>For each outcome class, set a boundary in plain language. For instance: “A draft may need stylistic edits, but it must not invent a policy or send without a human confirmation.” If you have enough representative examples to quantify the boundary, record the denominator and the threshold. If you do not, say that the evidence is insufficient; do not manufacture a percentage to make the brief look precise.</p>
<p>A useful planning table has four fields:</p>
<ol>
<li><strong>Failure class:</strong> the observable user-level mistake.</li>
<li><strong>Exposure:</strong> where in the workflow it can reach a person or trigger an action.</li>
<li><strong>Limit:</strong> what frequency or severity is tolerable for this release, supported by evidence where possible.</li>
<li><strong>Containment:</strong> what prevents further impact once the limit is crossed.</li>
</ol>
<p>“Improve accuracy” belongs nowhere in that table. It is an intention, not an operating boundary.</p>
<h2 id="make-the-denominator-match-the-decision">Make the denominator match the decision<a class="hlink" href="#make-the-denominator-match-the-decision" title="Permanent link">#</a></h2>
<p>A model score is meaningful only when the test examples represent the product decision. For a drafting feature, counting every generated sentence as an observation can obscure the more useful unit: a draft that a user might send. For a triage feature, the consequential unit may be a case routed to the wrong queue.</p>
<p>Write the denominator beside every proposed threshold. “No more than two unsupported claims per hundred evaluated drafts” is interpretable only if the drafts cover the relevant customer intents, policy exceptions, and incomplete inputs. A hundred near-identical happy-path prompts do not provide that evidence.</p>
<p>Separate severity from frequency as well. A low-frequency event can still be unacceptable if its impact is severe. In that case, the release boundary may be categorical: the feature cannot take the consequential action autonomously, regardless of its average score. That can be a more defensible product decision than trying to justify a tiny numerical allowance from a small test set.</p>
<h2 id="treat-the-budget-as-something-that-can-be-spent">Treat the budget as something that can be spent<a class="hlink" href="#treat-the-budget-as-something-that-can-be-spent" title="Permanent link">#</a></h2>
<p>An error budget is useful only if a team knows what consumes it. Decide whether a failure counts when it appears in pre-release evaluation, in monitored production traffic, or both. These answer different questions: evaluation estimates readiness; production monitoring tracks what users actually encounter.</p>
<p>For example, a team might require zero observed unsupported policy claims in a defined set of high-risk test scenarios before enabling automatic sending. In production, it could track user-reported or reviewer-confirmed incidents separately, with a pause condition if any such incident occurs. The exact gate depends on the product’s consequences and evidence; the important point is to define the event and response before launch.</p>
<p>Do not let a budget become a target to spend. A threshold is a ceiling for decision-making, not permission to expose users to predictable failures. If the budget is exceeded, pause the relevant capability, preserve the context needed to investigate, and assign an owner to decide whether to repair, narrow, or remove it.</p>
<h2 id="design-the-exhausted-budget-state">Design the exhausted-budget state<a class="hlink" href="#design-the-exhausted-budget-state" title="Permanent link">#</a></h2>
<p>Many briefs describe only the successful path. The reliability contract must also say what happens when the system is uncertain, unavailable, or beyond its error boundary.</p>
<p>For the draft assistant, the exhausted-budget state could be: stop generating send-ready text, keep the original customer message visible, and offer a blank reply editor. The user still completes the task, but the product no longer presents an unsafe draft as if it were usable. That fallback should preserve the user’s progress and explain the limitation without claiming certainty about why the model failed.</p>
<p>Assign an operational owner too. Who reviews a flagged incident? Who can disable the capability? What signal tells them to act? A feature flag without a named responder is not a complete containment plan.</p>
<h2 id="put-the-contract-in-the-brief">Put the contract in the brief<a class="hlink" href="#put-the-contract-in-the-brief" title="Permanent link">#</a></h2>
<p>Before implementation, add a compact reliability section to the product brief:</p>
<ul>
<li><strong>Allowed:</strong> errors the user can safely notice and correct.</li>
<li><strong>Blocked:</strong> errors or actions that must not reach the user uncontained.</li>
<li><strong>Evidence:</strong> evaluation examples, denominator, and known gaps.</li>
<li><strong>Fallback:</strong> the usable non-AI path when confidence or service is insufficient.</li>
<li><strong>Tripwire:</strong> the event that pauses or narrows the feature.</li>
<li><strong>Owner:</strong> the person responsible for reviewing the tripwire and deciding next steps.</li>
</ul>
<p>Then ask one question in review: <em>If this exact failure happens tomorrow, what does the user see, and who acts?</em> If the answer is vague, the feature’s error budget is not ready to ship.</p>]]></content:encoded></item><item><title>Account Aggregator drop-offs happen before the OTP</title><link>https://saumya.dabi.live/blog/account-aggregator-drop-offs-before-otp</link><guid isPermaLink="true">https://saumya.dabi.live/blog/account-aggregator-drop-offs-before-otp</guid>
<pubDate>Sat, 12 Sep 2026 02:00:00 +0000</pubDate><category>India Tech</category>
<description>Fintech onboarding flows treat Account Aggregator as an API pipe and wonder why half the users abandon. The real bottleneck is consent UX, phone number mismatch, and brittle fallbacks.</description><content:encoded><![CDATA[<p>When a product team integrates India&rsquo;s Account Aggregator framework, launch metrics usually follow a disappointing curve. Technical integration tests pass with 100% reliability in sandbox environments, but the moment real users hit the production funnel, drop-off rates spike above 40% before a single byte of financial data is fetched.</p>
<p>The engineering post-mortem often points to bank downtime or third-party SDK latency. But session replays show that drop-offs rarely happen because of an API failure. They happen because product teams treat Account Aggregator (AA) as a background data pipeline rather than a high-friction consent interaction.</p>
<p>In India&rsquo;s financial ecosystem, regulated under the <a href="https://rbi.org.in/">Reserve Bank of India Master Directions for Account Aggregators</a>, the AA network acts as a consent broker between Financial Information Providers (banks) and Financial Information Users (lenders or personal finance apps). The framework is architected for privacy, but privacy requires explicit user comprehension. When an interface fails to explain what is happening, who is asking, and why a second SMS just arrived, users close the app.</p>
<h2 id="the-dual-sim-reality-breaks-account-discovery">The dual-SIM reality breaks account discovery<a class="hlink" href="#the-dual-sim-reality-breaks-account-discovery" title="Permanent link">#</a></h2>
<p>The single largest drop-off in the Account Aggregator journey occurs in the first five seconds, during account discovery. An applicant signs up for your product using their primary data SIM. But their legacy salary or savings account at a bank is tied to a secondary mobile number they have used for years.</p>
<p>When your client initiates discovery against the AA registry using the signup mobile number, the API returns zero discovered accounts. </p>
<p>Most default SDK implementations handle this scenario with a dead-end message: <em>&ldquo;No accounts found for this mobile number.&rdquo;</em> The user assumes the service is broken or their bank is unsupported, and leaves. In unoptimized funnels, this single failure mode accounts for 18% to 24% of all initiated sessions.</p>
<pre><code>[ App Signup: Number A ] ──&gt; [ AA Discovery: Number A ] ──&gt; [ FIP Bank: 0 Accounts ] ──&gt; Abandon
</code></pre>
<p>To fix this, decouple your application&rsquo;s identity phone number from the bank discovery phone number. Before calling the discovery API, present an explicit confirmation prompt:</p>
<blockquote>
<p><em>&ldquo;We will look for your accounts linked to +91 98XXX XXXXX. Is your bank account registered with this number or a different SIM?&rdquo;</em></p>
</blockquote>
<p>Providing a simple &ldquo;Use a different bank-linked number&rdquo; input with inline SMS verification recovers more than half of these stranded sessions. It treats dual-SIM usage not as an edge case, but as the default operating reality of the Indian smartphone market.</p>
<h2 id="consent-scope-paranoia-kills-conversion">Consent scope paranoia kills conversion<a class="hlink" href="#consent-scope-paranoia-kills-conversion" title="Permanent link">#</a></h2>
<p>Once accounts are discovered, the user faces the consent approval screen. This screen is governed by regulatory requirements specified by <a href="https://sahamati.org.in/">Sahamati&rsquo;s operational framework</a>, detailing the Financial Information User, data life, fetch frequency, and date range. </p>
<p>Too many product managers accept the default SDK consent template, which often requests recurring access for three years with daily fetch frequencies. When an applicant applying for a one-time credit check sees a legal screen stating that an external app will monitor their bank account daily until 2029, trust evaporates.</p>
<div class="tbl"><table>
<thead>
<tr>
<th style="text-align: left;">Consent Parameter</th>
<th style="text-align: left;">Default SDK Setting</th>
<th style="text-align: left;">Optimized Underwriting Setting</th>
<th style="text-align: left;">Conversion Impact</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><strong>Consent Mode</strong></td>
<td style="text-align: left;"><code>PERIODIC</code></td>
<td style="text-align: left;"><code>ONE_TIME</code></td>
<td style="text-align: left;">Users avoid ongoing monitoring fears</td>
</tr>
<tr>
<td style="text-align: left;"><strong>Fetch Frequency</strong></td>
<td style="text-align: left;">Daily / Weekly</td>
<td style="text-align: left;">Single fetch on submission</td>
<td style="text-align: left;">Reduces regulatory scrutiny and user doubt</td>
</tr>
<tr>
<td style="text-align: left;"><strong>Date Range</strong></td>
<td style="text-align: left;">12 to 24 months</td>
<td style="text-align: left;">Exactly 6 months</td>
<td style="text-align: left;">Matches underwriting policy minimum</td>
</tr>
<tr>
<td style="text-align: left;"><strong>Data Life</strong></td>
<td style="text-align: left;">3 years</td>
<td style="text-align: left;">30 to 90 days</td>
<td style="text-align: left;">Clear retention boundary builds trust</td>
</tr>
</tbody>
</table></div>
<p>Every additional month of requested transaction history and every recurring fetch permission decreases consent completion. If your underwriting policy only scores based on the last 180 days of cash flows, request exactly 180 days. </p>
<blockquote>
<p>Consent conversion is inversely proportional to the blast radius of the requested permission.</p>
</blockquote>
<p>Pair this minimal scope with plain-language assurances directly above the consent trigger: confirm that consent does not permit moving money, and that consent can be revoked at any time from their Account Aggregator profile.</p>
<h2 id="the-two-step-otp-journey-needs-a-visual-contract">The two-step OTP journey needs a visual contract<a class="hlink" href="#the-two-step-otp-journey-needs-a-visual-contract" title="Permanent link">#</a></h2>
<p>In payment workflows, Indian consumers expect a single Two-Factor Authentication (2FA) prompt. In the Account Aggregator flow, users encounter two distinct OTP steps:</p>
<ol>
<li><strong>AA Registration / Login OTP:</strong> Validates the user&rsquo;s mobile number with the regulated Account Aggregator entity (such as Setu, Finvu, or Anumati).</li>
<li><strong>Bank FIP Linking OTP:</strong> Sent directly by the user&rsquo;s bank to authorize linking and data sharing for the selected account.</li>
</ol>
<p>When users receive the first OTP, they enter it and expect immediate completion. When a second OTP prompt instantly appears on screen from a completely different SMS header (such as <code>VK-HDFCBK</code> instead of the AA provider), confusion sets in. Users assume the system looped or that a duplicate action is being authorized.</p>
<aside class="callout tip"><span class="mono">Try this</span><p>Display a persistent two-step progress indicator at the top of the modal before triggering the first SMS: Step 1 as &ldquo;Verify AA Identity&rdquo; and Step 2 as &ldquo;Authorize Bank Statement&rdquo;. When users expect two verification codes upfront, second-OTP completion rates improve by double digits.</p></aside>
<p>Include the bank&rsquo;s brand mark alongside the second OTP input field. Seeing their bank&rsquo;s familiar logo grounds the interaction and reassures the applicant that the incoming bank SMS is legitimate.</p>
<h2 id="set-a-hard-latency-budget-and-preserve-state-on-fallback">Set a hard latency budget and preserve state on fallback<a class="hlink" href="#set-a-hard-latency-budget-and-preserve-state-on-fallback" title="Permanent link">#</a></h2>
<p>Financial Information Provider (FIP) APIs operate on heterogeneous banking infrastructure. While leading private banks respond to discovery and consent requests within two to three seconds, public sector banks and regional cooperative institutions can experience latency spikes exceeding 15 seconds, or suffer temporary timeouts during peak hours.</p>
<p>If your onboarding screen displays an indefinite spinning loader, the user will abandon the session within 10 to 12 seconds. Worse, if your frontend handles a timeout by throwing a generic error alert that resets the application form, you force the user to re-enter personal details from scratch.</p>
<p>Implement a strict client-side latency budget of 12 seconds for the entire AA exchange:</p>
<pre><code>AA Request Initiated (0s)
   │
   ├─► Success within 12s ──► Ingestion &amp; Parsing ──► Complete
   │
   └─► Timeout at 12s ─────► Graceful Degradation
                                │
                                ├─► Net Banking Login (Alternative)
                                └─► Direct PDF Statement Upload (Prefilled)
</code></pre>
<p>If the 12-second window lapses without a successful consent callback, transition the screen immediately to your secondary ingestion rail: Net Banking login or encrypted PDF statement upload. Pass forward the user&rsquo;s verified mobile number, selected bank name, and application ID so the fallback screen is already pre-configured. The user experiences the fallback as an intentional, helpful alternative rather than a broken journey.</p>
<h2 id="the-four-funnel-metrics-that-diagnose-drop-off">The four funnel metrics that diagnose drop-off<a class="hlink" href="#the-four-funnel-metrics-that-diagnose-drop-off" title="Permanent link">#</a></h2>
<p>Measuring Account Aggregator performance using a single top-level metric like &ldquo;AA Success Rate&rdquo; conceals the exact point of friction. To maintain a healthy ingestion pipeline, track four isolated ratios across your analytics pipeline:</p>
<ol>
<li><strong>Discovery Rate (<code>Discovered Accounts / Initiated Flows</code>):</strong> Diagnoses phone number mismatches and FIP lookup downtime. If this drops below 75%, audit your phone-number matching logic and prompt copy.</li>
<li><strong>Account Selection Rate (<code>Selected Accounts / Discovered Accounts</code>):</strong> Diagnoses confusion when a user holds multiple accounts at the same institution (such as dormant joint accounts versus active salary accounts).</li>
<li><strong>Consent Completion Rate (<code>Verified Bank OTPs / Rendered Consent Screens</code>):</strong> Measures user trust and OTP delivery reliability. If this drops, review consent scope duration, recurring fetch permissions, and two-step progress messaging.</li>
<li><strong>Data Delivery Rate (<code>Parsed Statements / Verified Consents</code>):</strong> Measures technical reliability of the FIP data payload transfer. A drop here indicates downstream bank server latency or schema deserialization bugs.</li>
</ol>
<p>At your next product review, look at your drop-offs through this four-part lens. If your discovery rate or consent completion rate lags behind technical uptime, stop debugging the API client. Fix the consent messaging, give users control over their bank-linked mobile numbers, and set up state-preserving fallbacks before writing another line of backend code.</p>]]></content:encoded></item><item><title>Voice is an input mode, not the whole interface</title><link>https://saumya.dabi.live/blog/india-voice-interface</link><guid isPermaLink="true">https://saumya.dabi.live/blog/india-voice-interface</guid>
<pubDate>Tue, 08 Sep 2026 02:00:00 +0000</pubDate><category>India Tech</category>
<description>Voice products can lower the cost of expressing a request, but the rest of the workflow still needs visible control. Design for corrections, confirmation and a graceful switch back to text.</description><content:encoded><![CDATA[<p>Voice can make a computer feel more approachable, especially when a person is more comfortable speaking than typing. But speaking is only the first step in a task. The user still needs to review the result, correct a misunderstanding, and know what the system is about to do.</p>
<p>That distinction matters in multilingual products. A voice feature is not finished when it recognizes speech; it is finished when a person can safely move from spoken intent to a visible outcome.</p>
<h2 id="support-the-way-people-actually-speak">Support the way people actually speak<a class="hlink" href="#support-the-way-people-actually-speak" title="Permanent link">#</a></h2>
<p>People may switch languages mid-sentence, use a borrowed technical word, or pronounce a name differently from the spelling stored in a system. Treat those as expected inputs, not edge cases to be cleaned up later.</p>
<p>Sarvam&rsquo;s Kivi product description, for example, presents a voice app for computer tasks and says it supports 22+ Indian languages and code-switching. That is a useful product signal: language coverage is part of the interface contract, not a model benchmark hidden in the implementation. <a href="https://www.sarvam.ai/epoch/summary">Sarvam AI, “Everything we announced at Sarvam Epoch,” accessed 2026-09-08</a>.</p>
<p>The experience still needs to show what happened when recognition is uncertain. A transcript that silently changes a name or amount can be more dangerous than an obvious failure. Let the person correct a word, switch the output script when needed, or replay the source before the task continues.</p>
<h2 id="separate-dictation-from-action">Separate dictation from action<a class="hlink" href="#separate-dictation-from-action" title="Permanent link">#</a></h2>
<p>Typing a message and asking an assistant to send it are different actions. The first creates text; the second changes state outside the conversation. A good interface makes that boundary visible.</p>
<p>Use a three-step pattern for consequential tasks:</p>
<ol>
<li><strong>Capture:</strong> show the recognized words and keep the original request available.</li>
<li><strong>Review:</strong> highlight the fields that carry risk, such as a recipient, amount, date, or account.</li>
<li><strong>Confirm:</strong> state the action in plain language and wait for an intentional confirmation.</li>
</ol>
<p>The same rule applies even when recognition is excellent. Accuracy does not replace consent. If a system can both draft and execute, the interface should not make the user guess which mode is active.</p>
<h2 id="make-repair-cheap">Make repair cheap<a class="hlink" href="#make-repair-cheap" title="Permanent link">#</a></h2>
<p>A voice interface needs a repair path that is easier than restarting the task. If the system heard the wrong recipient, let the user correct that field without dictating the entire message again. If it heard a sentence in the wrong language, let the user switch without losing the draft.</p>
<p>Test repairs with realistic conditions: a noisy room, a pause, a code-switched phrase, a proper noun, and an interruption. Do not claim support for a language just because the recognizer emits text in that script. Check whether the user can edit, understand and finish the task in that language too.</p>
<p>A useful test case records the spoken input, the expected meaning, the recognized output, the correction available, and the final action. Keep the user&rsquo;s intent distinct from the model&rsquo;s transcript so a reviewer can see where the error entered.</p>
<h2 id="keep-the-non-voice-path-first-class">Keep the non-voice path first-class<a class="hlink" href="#keep-the-non-voice-path-first-class" title="Permanent link">#</a></h2>
<p>Voice is not always available or comfortable. A person may be in a shared space, have a speech disability, prefer a keyboard for precise data, or simply want to review silently. These are ordinary conditions, not exceptions.</p>
<p>Offer a visible text alternative and preserve progress when switching modes. Keep buttons and fields usable without speech. If a task is too consequential to complete without review, make that review possible with touch, keyboard, or another accessible input.</p>
<h2 id="test-the-whole-language-journey">Test the whole language journey<a class="hlink" href="#test-the-whole-language-journey" title="Permanent link">#</a></h2>
<p>A language checkbox is a weak launch criterion. Test whether the person can start in one language, correct a phrase in another, read the resulting text, and finish the task without losing meaning. A recognizer may handle a spoken sentence while the rest of the product fails on a field label, validation error, or confirmation message.</p>
<p>Build a small test set from real, consented speech or carefully designed examples. Include code-switches, names that resemble common words, numbers spoken in local conventions, and quiet versus noisy audio. For each case, record the intended meaning, the recognized text, the correction offered, and whether the user can recover. Ask a fluent reviewer to assess meaning, not just spelling. Do not claim a language works end to end because one model benchmark looks strong.</p>
<h2 id="measure-task-completion-not-only-recognition">Measure task completion, not only recognition<a class="hlink" href="#measure-task-completion-not-only-recognition" title="Permanent link">#</a></h2>
<p>Word error rate can help diagnose transcription, but it does not tell a product team whether the task succeeded. Track where users abandon, how often they correct recognized text, whether a consequential action gets confirmed, and whether a text alternative is used. Keep denominators explicit: “correction rate among voice sessions” is more informative than a count of edits with no session context.</p>
<p>Review failures by language and task, not only in aggregate. A strong average can hide that one interaction or language pairing is consistently difficult. If a failure cannot be attributed safely, offer a neutral correction path instead of guessing which language the person intended.</p>
<p>Also test the response, not just the input. A product may recognize the spoken request correctly but answer with terminology the person does not use, or show a confirmation that is difficult to read in the chosen script. Let people replay the source audio, edit the transcript, and inspect the final action. Ask reviewers to check whether the response preserves intent and whether the recovery controls are understandable without a training session. A language setting should govern the whole exchange, while still letting a user switch it at any point.</p>
<p>For every voice task, ask: can the user see what was heard, correct it, understand the next action, and complete the job without speaking? If one answer is no, the product has not yet designed the whole interface.</p>]]></content:encoded></item><item><title>Treat your prompt library as a codebase, not a folder of text files</title><link>https://saumya.dabi.live/blog/treat-prompts-as-a-codebase</link><guid isPermaLink="true">https://saumya.dabi.live/blog/treat-prompts-as-a-codebase</guid>
<pubDate>Sat, 05 Sep 2026 02:00:00 +0000</pubDate><category>AI Tooling</category>
<description>Prompts degrade silently when treated as static copy. Version control them, test them against golden inputs, and treat prompt edits like code refactoring.</description><content:encoded><![CDATA[<p>The most vulnerable artifact in an AI-powered product is not the model weights or the vector database; it is the prompt file. In many early-stage codebases, prompts begin life as clean markdown files or Python multi-line strings. Within three months, they evolve into sprawling monoliths of concatenated conditional logic, hardcoded few-shot examples, and patchwork instructions added to fix edge cases discovered during a live demo.</p>
<p>When something breaks, developers tweak the text, eyeball the response in a local playground, and push straight to production. There is no diff review, no regression suite, and no record of why a specific constraint was added. </p>
<p>If your prompts dictate the behavior of your product, you cannot afford to treat them like casual documentation. You need to treat your prompt library with the exact same engineering rigor you apply to application source code.</p>
<h2 id="the-unversioned-markdown-trap">The unversioned markdown trap<a class="hlink" href="#the-unversioned-markdown-trap" title="Permanent link">#</a></h2>
<p>The moment multiple team members start editing system prompts without version control, subtle regressions creep in. A product manager adjusts tone to sound more professional; a developer removes a formatting rule to save tokens; an engineer inserts a new instruction to fix a parsing bug on Tuesday, inadvertently breaking zero-shot classification on Wednesday.</p>
<p>In a traditional code repository, these changes are caught by pull request reviews and automated tests. In an unmanaged prompt directory, they propagate silently. You only discover them when users complain that the output format changed or the model hallucinated a tool call.</p>
<p>To fix this, prompts must live inside your repository alongside the code that invokes them, tracked by Git. Every modification needs a clear commit message explaining the behavioural hypothesis: <em>“Add explicit JSON schema constraint for extraction step to prevent markdown codeblock wrapping.”</em> If a prompt change causes downstream parsing failures in production, your git log should tell you precisely when and why the instruction was altered.</p>
<h2 id="versioning-templates-and-variables">Versioning templates and variables<a class="hlink" href="#versioning-templates-and-variables" title="Permanent link">#</a></h2>
<p>A prompt is rarely static text; it is a parameterized template that combines system instructions, user context, few-shot examples, and state history. When you scatter string formatting across multiple backend services, you invite prompt drift.</p>
<p>Centralize your prompt templates into a dedicated directory structure with explicit schema validation. Each prompt file should declare its required input variables and their expected types. </p>
<pre><code>prompts/
├── extraction/
│   ├── v1_0.jinja
│   ├── v1_1.jinja
│   └── metadata.yaml
</code></pre>
<p>When you update a prompt template, bump its version identifier rather than overwriting in place. This allows you to safely canary new prompt versions against a subset of production traffic or test suites before cutting over entirely. Hardcoding version tags in your API calls ensures that an upstream deployment never accidentally relies on an unreviewed template update.</p>
<aside class="callout tip"><span class="mono">Try this</span><p>Treat template variables with strict type checking. If a prompt expects a list of structured items, validate the list length and schema before rendering the template string.</p></aside>
<h2 id="regression-testing-against-golden-inputs">Regression testing against golden inputs<a class="hlink" href="#regression-testing-against-golden-inputs" title="Permanent link">#</a></h2>
<p>Unlike traditional unit tests that assert deterministic equality (<code>assert result == 42</code>), prompt evaluation requires probabilistic assertions. A prompt change might improve clarity while altering token count or latency by twenty percent.</p>
<p>You cannot rely on manual playground testing to catch these shifts. Establish a golden test set consisting of 20 to 50 representative input payloads paired with expected output schemas or scoring rubrics. Every time a prompt template changes, run your evaluation harness against both the old and new prompt versions.</p>
<div class="tbl"><table>
<thead>
<tr>
<th style="text-align: left;">Metric</th>
<th style="text-align: left;">Baseline Prompt (v1.0)</th>
<th style="text-align: left;">Candidate Prompt (v1.1)</th>
<th style="text-align: left;">Delta</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Schema Compliance</td>
<td style="text-align: left;">92%</td>
<td style="text-align: left;">98%</td>
<td style="text-align: left;">+6%</td>
</tr>
<tr>
<td style="text-align: left;">Hallucination Rate</td>
<td style="text-align: left;">4.1%</td>
<td style="text-align: left;">1.2%</td>
<td style="text-align: left;">-2.9%</td>
</tr>
<tr>
<td style="text-align: left;">Average Latency</td>
<td style="text-align: left;">1,420ms</td>
<td style="text-align: left;">1,510ms</td>
<td style="text-align: left;">+90ms</td>
</tr>
<tr>
<td style="text-align: left;">Token Cost / Run</td>
<td style="text-align: left;">1,240 tokens</td>
<td style="text-align: left;">1,180 tokens</td>
<td style="text-align: left;">-60 tokens</td>
</tr>
</tbody>
</table></div>
<p>If the candidate prompt improves compliance and reduces hallucinations while keeping latency within acceptable bounds, it earns a merge. If schema compliance drops even if the prose sounds better, the pull request is blocked.</p>
<h2 id="refactoring-for-conciseness-and-clarity">Refactoring for conciseness and clarity<a class="hlink" href="#refactoring-for-conciseness-and-clarity" title="Permanent link">#</a></h2>
<p>As prompts age, they accumulate defensive instructions—extra sentences added to prevent past failures. Over time, this bloat degrades model attention and inflates costs. </p>
<p>Refactoring a prompt is identical to refactoring legacy code: you look for redundant instructions, conflicting constraints, and overly complex few-shot examples. When an edge case is fixed by adding code validation or schema parsing on the backend, remove the corresponding defensive instruction from the prompt. </p>
<p>Keep your system prompts lean, modular, and focused on behavioral intent. Let deterministic code handle data formatting, retry logic, and validation, leaving the model free to focus on reasoning and extraction.</p>
<h2 id="the-path-to-reliable-prompt-engineering">The path to reliable prompt engineering<a class="hlink" href="#the-path-to-reliable-prompt-engineering" title="Permanent link">#</a></h2>
<p>Managing prompts as code transforms prompt engineering from an intuitive art into a disciplined engineering discipline. </p>
<p>Before your next release, take inventory of where your prompts live. Move them into version control, attach a minimal golden test suite, and treat every prompt modification as a pull request. Your error logs will thank you.</p>]]></content:encoded></item><item><title>Give your agent domain actions, not database CRUD</title><link>https://saumya.dabi.live/blog/agent-domain-actions-not-crud</link><guid isPermaLink="true">https://saumya.dabi.live/blog/agent-domain-actions-not-crud</guid>
<pubDate>Tue, 01 Sep 2026 02:00:00 +0000</pubDate><category>Agents</category>
<description>Exposing raw CRUD endpoints to an AI agent leads to brittle multi-step plans and corrupted states. Design coarse-grained domain tools that enforce invariants in code.</description><content:encoded><![CDATA[<p>The natural temptation when giving an AI agent tool access is to point it at your existing backend API. Your team already maintains well-documented REST endpoints: <code>GET /accounts</code>, <code>PATCH /accounts/{id}</code>, <code>POST /audit_events</code>, and <code>POST /notifications</code>. Exposing those endpoints as tool definitions feels like the cleanest architecture. The assumption is that an agent with frontier reasoning capability can inspect the schema, choreograph the sequence, and handle edge cases across successive model turns.</p>
<p>In production, delegating database-level orchestration to a probabilistic model produces a brittle agent. It bloats the context window across chatty roundtrips, leaks internal database quirks into the prompt, and creates partial-execution failures where step two mutates state but step three crashes. </p>
<p>If you want an agent to act reliably across complex systems, stop giving it raw database verbs. Give it coarse-grained domain actions that enforce business invariants in code.</p>
<h2 id="the-illusion-of-granular-flexibility">The illusion of granular flexibility<a class="hlink" href="#the-illusion-of-granular-flexibility" title="Permanent link">#</a></h2>
<p>Teams usually choose low-level CRUD tools for two reasons: convenience and the promise of autonomy. An engineer can generate twenty OpenAPI tool schemas in an afternoon by exporting existing route controllers. Product teams often encourage this because fine-grained tools seem to give the agent maximum flexibility to solve unforeseen user problems.</p>
<p>The math behind multi-step tool calls works against that flexibility. Suppose each individual tool selection, parameter extraction, and execution step has an optimistic 95% success rate. If completing a user request requires four sequential CRUD invocations—fetch the record, update status, append an audit entry, and dispatch an alert—the probability of the complete sequence succeeding without error drops to roughly 81% (<code>0.95^4</code>). At six steps, reliability falls to 73%.</p>
<p>Every unnecessary turn between the model and the host environment introduces compounding points of failure:</p>
<ul>
<li>The model might misread intermediate JSON output and invent an argument on step three.</li>
<li>A transient network error on step four can leave the database in an invalid, half-committed state.</li>
<li>The token cost multiplies because each subsequent turn resends the entire conversation history, including previous tool arguments and verbose payloads.</li>
</ul>
<p>When you ask an agent to string together five low-level mutations, you are asking a language model to do determinism&rsquo;s job. Deterministic pipelines belong in application code; probabilistic models belong at the decision boundary.</p>
<h2 id="why-multi-step-crud-loops-break-in-production">Why multi-step CRUD loops break in production<a class="hlink" href="#why-multi-step-crud-loops-break-in-production" title="Permanent link">#</a></h2>
<p>When an agent interacts with granular APIs, three specific failure modes appear repeatedly in production logs.</p>
<h3 id="1-partial-execution-and-orphaned-state">1. Partial execution and orphaned state<a class="hlink" href="#1-partial-execution-and-orphaned-state" title="Permanent link">#</a></h3>
<p>In standard web applications, a backend developer wraps related mutations inside a database transaction. If the payment record fails to save, the order status does not advance to paid. </p>
<p>When an agent calls granular endpoints across multiple chat turns, there is no shared database transaction across the turns. If the agent calls <code>update_account_status(status="suspended")</code> and then encounters a rate limit or a malformed response when attempting <code>create_compliance_log()</code>, the account remains suspended without an audit trail. The agent cannot reliably roll back previous HTTP calls on its own.</p>
<h3 id="2-parameter-hallucination-on-wide-schemas">2. Parameter hallucination on wide schemas<a class="hlink" href="#2-parameter-hallucination-on-wide-schemas" title="Permanent link">#</a></h3>
<p>Backend CRUD schemas are notoriously wide. A typical <code>update_user</code> endpoint might accept thirty optional fields, from billing addresses to internal feature flags. When presented with twenty optional parameters, models frequently set defaults that clobber existing data, confuse deprecated fields with active ones, or pass null values for fields they were never meant to touch.</p>
<h3 id="3-context-pollution-from-raw-payloads">3. Context pollution from raw payloads<a class="hlink" href="#3-context-pollution-from-raw-payloads" title="Permanent link">#</a></h3>
<p>A standard <code>GET /customers/{id}</code> response often returns hundreds of lines of JSON: internal UUIDs, created_at timestamps, hash digests, and nested metadata. Ninety percent of this payload is noise for the task at hand. This raw data consumes context tokens, degrades model attention, and increases latency on every subsequent turn.</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Architectural Dimension</th>
<th>Granular CRUD Tools</th>
<th>Domain Action Tools</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Mutation scope</strong></td>
<td>Single database table or field</td>
<td>Multi-table atomic business transaction</td>
</tr>
<tr>
<td><strong>Error recovery</strong></td>
<td>Relies on LLM to catch and rollback</td>
<td>Handled deterministically in application code</td>
</tr>
<tr>
<td><strong>Parameter surface</strong></td>
<td>10–30 generic database fields</td>
<td>2–4 explicit task-specific arguments</td>
</tr>
<tr>
<td><strong>Context efficiency</strong></td>
<td>High token overhead from chatty roundtrips</td>
<td>Single turn with targeted evidence output</td>
</tr>
<tr>
<td><strong>Invariant guarantees</strong></td>
<td>Probabilistic (agent must remember all steps)</td>
<td>Deterministic (code enforces business rules)</td>
</tr>
</tbody>
</table></div>
<h2 id="the-domain-action-pattern">The domain action pattern<a class="hlink" href="#the-domain-action-pattern" title="Permanent link">#</a></h2>
<p>The solution is to design tools around domain intents rather than database tables. A domain action is a coarse-grained function that encapsulates a complete business workflow behind a single, tightly constrained interface.</p>
<p>Instead of exposing four separate tools:
- <code>fetch_invoice_details(id)</code>
- <code>update_invoice_status(id, status)</code>
- <code>record_discrepancy_note(id, note)</code>
- <code>notify_finance_reviewer(id, email)</code></p>
<p>You expose one domain tool:
- <code>resolve_invoice_discrepancy(invoice_id, resolution_type, explanation, notify_reviewer)</code></p>
<p>The backend implementation behind <code>resolve_invoice_discrepancy</code> executes inside a single application boundary. It validates whether the invoice is in an editable state, performs the database mutation, writes the audit log, triggers the notification, and wraps everything in a database transaction that rolls back if any step fails.</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;resolve_invoice_discrepancy&quot;,
  &quot;description&quot;: &quot;Resolves an invoice hold by applying an approved resolution rule, updating the audit trail, and notifying the assigned reviewer.&quot;,
  &quot;parameters&quot;: {
    &quot;type&quot;: &quot;object&quot;,
    &quot;properties&quot;: {
      &quot;invoice_id&quot;: {
        &quot;type&quot;: &quot;string&quot;,
        &quot;description&quot;: &quot;Unique identifier of the invoice on hold.&quot;
      },
      &quot;resolution_type&quot;: {
        &quot;type&quot;: &quot;string&quot;,
        &quot;enum&quot;: [&quot;waive_fee&quot;, &quot;rebill_line_item&quot;, &quot;escalate_to_controller&quot;],
        &quot;description&quot;: &quot;The approved business resolution path.&quot;
      },
      &quot;explanation&quot;: {
        &quot;type&quot;: &quot;string&quot;,
        &quot;description&quot;: &quot;The justification grounded in the customer contract or dispute history.&quot;
      }
    },
    &quot;required&quot;: [&quot;invoice_id&quot;, &quot;resolution_type&quot;, &quot;explanation&quot;]
  }
}
</code></pre>
<p>Notice what disappears: internal status flags, foreign key IDs, user IDs, and timestamps. The tool schema only asks the model for decisions that genuinely require reasoning. Everything else is derived from the authenticated session and business logic.</p>
<aside class="callout tip"><span class="mono">Try this</span><p>Filter your tool return payloads aggressively. Never return raw database objects directly to the model. Return a concise JSON packet containing only the fields the agent needs to verify the outcome and communicate with the user.</p></aside>
<h2 id="how-to-evaluate-tool-schema-boundaries">How to evaluate tool schema boundaries<a class="hlink" href="#how-to-evaluate-tool-schema-boundaries" title="Permanent link">#</a></h2>
<p>When auditing your agent&rsquo;s tool definitions, apply three criteria to decide whether a tool is at the right level of abstraction:</p>
<h3 id="the-atomicity-test">The atomicity test<a class="hlink" href="#the-atomicity-test" title="Permanent link">#</a></h3>
<p>If step B must always happen when step A happens, step A and step B should never be two distinct tools in the agent&rsquo;s catalog. If updating a customer tier must always write a regulatory audit record, build a single tool that takes the tier change and reason, then writes both records in one transaction.</p>
<h3 id="the-argument-minimization-test">The argument minimization test<a class="hlink" href="#the-argument-minimization-test" title="Permanent link">#</a></h3>
<p>Inspect every parameter in your tool schema. If a parameter can be computed deterministically from the database, the current timestamp, or the user&rsquo;s authentication context, remove it from the schema. An agent should never be asked to pass <code>updated_at</code>, <code>modified_by_user_id</code>, or <code>organization_id</code>.</p>
<h3 id="the-evidence-dense-response-test">The evidence-dense response test<a class="hlink" href="#the-evidence-dense-response-test" title="Permanent link">#</a></h3>
<p>When a tool finishes execution, it should return an evidence summary, not an object dump. A response like:
<code>{"status": "applied", "invoice_id": "INV-4021", "amount_waived": 45.00, "new_balance": 310.00}</code> gives the model exact evidence to formulate its response without flooding the context window with raw schema baggage.</p>
<h2 id="a-checklist-for-your-agents-tool-catalog">A checklist for your agent&rsquo;s tool catalog<a class="hlink" href="#a-checklist-for-your-agents-tool-catalog" title="Permanent link">#</a></h2>
<p>Before shipping an agent feature that mutates system state, review your tool registry against these five questions:</p>
<ol>
<li><strong>Are transactions handled by the code or the prompt?</strong> If an agent fails midway through a task, does the system stay in a consistent state without requiring cleanup?</li>
<li><strong>Are inputs constrained to enums and verified types?</strong> If a field accepts only three business states, are they defined as an enum in the schema rather than documented in the system prompt?</li>
<li><strong>Does any tool expose raw database IDs or internal flags that the model must coordinate?</strong> If the model needs to pass an ID from Tool A into Tool B just to mutate state, combine the two tools into a single workflow.</li>
<li><strong>Is destructive action decoupled from ambiguous discovery?</strong> Can the model query state and dry-run potential changes without triggering live side effects?</li>
<li><strong>Does the tool response provide only the evidence needed for the user-facing answer?</strong> Strip out unindexed metadata, nested relationships, and verbose system traces before the result enters the context.</li>
</ol>
<p>If your agent is struggling with consistency, the problem is rarely that the underlying model lacks intelligence. More often, the agent is being asked to do the work of a backend framework. Move deterministic state transitions into your application code, keep business invariants safe, and let the agent focus on high-judgment decisions.</p>]]></content:encoded></item><item><title>Exception queues stall without a named owner</title><link>https://saumya.dabi.live/blog/exceptions-need-a-queue-owner</link><guid isPermaLink="true">https://saumya.dabi.live/blog/exceptions-need-a-queue-owner</guid>
<pubDate>Sat, 29 Aug 2026 02:00:00 +0000</pubDate><category>Fintech &amp; SaaS</category>
<description>Exception queues often collect unresolved work without making responsibility clear. A practical way to assign ownership, deadlines and the next action in a B2B workflow.</description><content:encoded><![CDATA[<p>A workflow can look complete on a dashboard while its hardest cases sit in a queue with nobody clearly responsible. “Needs review” describes a state; it does not tell anyone what to do next. In a high-stakes business process, that gap is where work goes stale.</p>
<h2 id="make-the-queue-answer-four-questions">Make the queue answer four questions<a class="hlink" href="#make-the-queue-answer-four-questions" title="Permanent link">#</a></h2>
<p>For each exception, show four things together: <strong>who owns it, what decision is needed, by when, and what happens next</strong>. If any field is missing, the item is not ready to be treated as routine queue work.</p>
<p>For example, “document mismatch” is a reason, not an action. A more useful item might say: “Operations reviewer — compare the submitted name with the source record — due tomorrow — request clarification if they differ.” The exact owner and deadline depend on the workflow, but the shape is reusable.</p>
<p>Avoid assigning every exception to a generic team inbox. A team can be accountable while no individual notices a new item. If individual assignment is not possible, make the team’s triage rule explicit: who checks the queue, how often, and who takes over when the usual owner is absent.</p>
<h2 id="separate-waiting-from-working">Separate waiting from working<a class="hlink" href="#separate-waiting-from-working" title="Permanent link">#</a></h2>
<p>An exception may be waiting on an internal decision, a customer response, or another system. Those are different states because they imply different next actions. “Waiting on customer” should identify who will follow up and when; otherwise it becomes a polite label for forgotten work.</p>
<p>A small state model is usually enough:</p>
<ul>
<li><strong>Unassigned:</strong> needs triage.</li>
<li><strong>In progress:</strong> an owner is investigating.</li>
<li><strong>Waiting:</strong> blocked on a named dependency, with a review date.</li>
<li><strong>Resolved:</strong> decision recorded and downstream action completed.</li>
</ul>
<p>Do not add states merely to represent every possible cause. Store the cause separately from the work state; “missing field” can be the reason whether the case is unassigned or already being investigated.</p>
<h2 id="design-the-escalation-before-the-dashboard">Design the escalation before the dashboard<a class="hlink" href="#design-the-escalation-before-the-dashboard" title="Permanent link">#</a></h2>
<p>Set a rule for overdue work before adding a red badge. For instance: at the review date, notify the owner; after the next missed review, route the case to the owner’s lead. A notification without a reassignment or explicit decision path only makes the backlog louder.</p>
<p>When designing a queue, test one ordinary case, one ambiguous case, and one case whose owner is unavailable. For each, ask: can someone tell what decision is pending, who must act, and when the workflow will surface a stall?</p>
<p>If the answer is no, fix ownership and next actions before adding more status labels.</p>]]></content:encoded></item><item><title>Don&#x27;t let context switching kill your agent&#x27;s reasoning</title><link>https://saumya.dabi.live/blog/context-switching-in-agent-development</link><guid isPermaLink="true">https://saumya.dabi.live/blog/context-switching-in-agent-development</guid>
<pubDate>Tue, 25 Aug 2026 02:00:00 +0000</pubDate><category>PM Craft</category>
<description>When working with AI agents, context switching isn&#x27;t just about lost time—it&#x27;s about the agent losing the thread of the task. Minimize state jumps to keep reasoning robust.</description><content:encoded><![CDATA[<p>We often talk about context switching as a productivity killer for humans. But when it comes to AI agents, it is a reliability killer. </p>
<p>An agent&rsquo;s reasoning ability is fragile. When you force it to switch between radically different tasks or data domains within the same execution loop, you are not just asking it to do more work—you are increasing the probability that it will lose the thread of its original instruction.</p>
<h2 id="the-cost-of-state-jumps">The cost of state jumps<a class="hlink" href="#the-cost-of-state-jumps" title="Permanent link">#</a></h2>
<p>When an agent is mid-reasoning, it maintains a implicit state of its current task. When you inject a request to switch context—perhaps to perform a quick search, or to summarize a document that is tangentially related—the agent has to re-orient itself.</p>
<p>For simple tasks, this is often fine. But for complex workflows, every state jump introduces a chance for the model to hallucinate or misinterpret previous constraints.</p>
<h2 id="designing-for-continuity">Designing for continuity<a class="hlink" href="#designing-for-continuity" title="Permanent link">#</a></h2>
<p>To build reliable agentic systems, we have to treat agent context as a finite resource:</p>
<ol>
<li><strong>Keep the scope narrow:</strong> If an agent is designed to manage a specific part of a workflow, do not give it the authority to branch into unrelated tasks unless absolutely necessary.</li>
<li><strong>Handoffs over switches:</strong> If a task requires a significant shift in data or perspective, it is almost always better to hand that task off to a specialized sub-agent. This keeps the primary agent&rsquo;s context clean and focused on its core responsibility.</li>
<li><strong>Minimize interruptions:</strong> Instrument your workflows so that the agent completes a logical segment of its task before it receives new information.</li>
</ol>
<p>When we prioritize the continuity of an agent&rsquo;s reasoning process, we reduce the likelihood of compounding errors, leading to systems that are more predictable and far easier to debug.</p>]]></content:encoded></item><item><title>The interface for agentic ambiguity</title><link>https://saumya.dabi.live/blog/the-interface-for-agentic-ambiguity</link><guid isPermaLink="true">https://saumya.dabi.live/blog/the-interface-for-agentic-ambiguity</guid>
<pubDate>Sat, 22 Aug 2026 02:00:00 +0000</pubDate><category>Evals</category>
<description>When AI agents operate in ambiguous territory, the interface needs to explicitly expose the trade-offs, not just the output.</description><content:encoded><![CDATA[<p>When we build AI agents to perform tasks, the tendency is to focus on the output—the final result, the completed form, the drafted email. But when the agent operates in ambiguous territory—where there isn&rsquo;t a single &ldquo;correct&rdquo; answer—that focus is misplaced.</p>
<p>The real challenge isn&rsquo;t just getting the agent to &ldquo;answer&rdquo;; it&rsquo;s ensuring that when the agent is uncertain, the interface provides enough context for the user to understand <em>why</em> and <em>how</em> it reached that decision.</p>
<h3 id="the-trap-of-seamless-automation">The trap of seamless automation<a class="hlink" href="#the-trap-of-seamless-automation" title="Permanent link">#</a></h3>
<p>We often design interfaces to be seamless. We want the user to click &ldquo;Run&rdquo; and get a result. For deterministic tasks, this works. For agentic workflows involving interpretation, inference, or synthesis, a seamless interface hides the agent&rsquo;s logic.</p>
<p>When the agent makes a choice based on incomplete information or ambiguous signals, the user needs to know what those signals were. If the interface hides this, the user is left to guess whether the agent was confident, or if it was just forced to choose the &ldquo;least bad&rdquo; option.</p>
<h3 id="exposing-the-trade-offs">Exposing the trade-offs<a class="hlink" href="#exposing-the-trade-offs" title="Permanent link">#</a></h3>
<p>Instead of presenting the result as absolute fact, design the interface to expose the trade-offs.</p>
<p>If an agent is summarizing a set of conflicting inputs, don&rsquo;t just show the summary. Show the weight it gave each input. If an agent is deciding on a next step in a workflow, don&rsquo;t just execute the step. Show the alternatives it considered and why it discarded them.</p>
<p>This changes the user&rsquo;s role from a passive observer to an active curator. It reduces cognitive load because the user doesn&rsquo;t have to reconstruct the agent&rsquo;s reasoning from scratch; they are presented with the parameters of the decision, allowing them to rapidly validate or correct the agent&rsquo;s logic.</p>
<h3 id="designing-for-transparency">Designing for transparency<a class="hlink" href="#designing-for-transparency" title="Permanent link">#</a></h3>
<p>This requires a fundamental shift in how we think about agent UI:</p>
<ol>
<li><strong>Confidence metrics:</strong> If the model provides a confidence score, surface it. If it doesn&rsquo;t, create a heuristic based on the consistency of the underlying data.</li>
<li><strong>Reasoning trails:</strong> Include a view that breaks down the steps taken, especially for multi-step agents.</li>
<li><strong>Alternative paths:</strong> When a decision involves selecting one path over another, briefly show what would have happened if the other path were chosen.</li>
</ol>
<p>When we treat ambiguity as a feature of the interaction—rather than a flaw in the output—we build systems that are significantly more reliable and much easier to trust.</p>]]></content:encoded></item><item><title>AI product hypotheses as experiments, not roadmap features</title><link>https://saumya.dabi.live/blog/ai-product-hypotheses-as-experiments</link><guid isPermaLink="true">https://saumya.dabi.live/blog/ai-product-hypotheses-as-experiments</guid>
<pubDate>Tue, 18 Aug 2026 02:00:00 +0000</pubDate><category>AI Product</category>
<description>When we treat an AI feature as a fixed requirement, we lose the ability to learn. Framing them as hypotheses allows the team to pivot based on real-world reliability.</description><content:encoded><![CDATA[<p>When I see a roadmap filled with &ldquo;add LLM feature X,&rdquo; I immediately look for the corresponding experiment design. Too often, teams treat an AI implementation as a simple feature request, like adding a button to a form. They specify the input, the output, and the deadline. But in a probabilistic system, you cannot guarantee the output quality on day one.</p>
<p>Framing AI features as experiments rather than fixed requirements changes the nature of the development. It acknowledges that the system might not work as intended at launch, and it shifts the team’s focus from &ldquo;did we build it?&rdquo; to &ldquo;did we learn enough to iterate?&rdquo;</p>
<h2 id="define-the-hypothesis-not-just-the-spec">Define the hypothesis, not just the spec<a class="hlink" href="#define-the-hypothesis-not-just-the-spec" title="Permanent link">#</a></h2>
<p>A standard requirement says &ldquo;the AI will summarize documents.&rdquo; An experiment hypothesis says &ldquo;by adding a summary, we expect users to spend 20% less time reviewing each document, without increasing the rate of missed information.&rdquo;</p>
<p>The difference is the metric. If the AI summarizes but users ignore it because they don&rsquo;t trust it, you have met the requirement but failed the experiment. If you don&rsquo;t know what you are trying to learn, you won&rsquo;t know if you have succeeded until long after launch.</p>
<h2 id="use-a-kill-or-pivot-criteria">Use a &ldquo;kill or pivot&rdquo; criteria<a class="hlink" href="#use-a-kill-or-pivot-criteria" title="Permanent link">#</a></h2>
<p>An experiment needs a stopping rule. If the AI doesn&rsquo;t meet the performance baseline, or if users find the summary unhelpful, what happens next?</p>
<p>If you treat the feature as a fixed requirement, you will likely try to &ldquo;fix&rdquo; the prompt or the model, potentially wasting weeks on a feature that users fundamentally don&rsquo;t need. If it’s an experiment, you have pre-defined conditions for stopping. If the data shows it doesn&rsquo;t solve the problem, you kill it or pivot the approach before the team gets invested in the implementation.</p>
<h2 id="build-the-feedback-loop-into-the-release">Build the feedback loop into the release<a class="hlink" href="#build-the-feedback-loop-into-the-release" title="Permanent link">#</a></h2>
<p>Product managers often ask &ldquo;how do I know if the AI is good?&rdquo; The answer is rarely in the logs. It’s in how users interact with the feature.</p>
<p>Design the release so you can instrument those interactions. Are they copying the summary? Are they regenerating it? Are they editing it before using it? Those signals are the raw data for your hypothesis.</p>
<p>If you cannot instrument the outcome, you are not running an experiment; you are guessing. And in AI, guessing is the most expensive way to build a product.</p>]]></content:encoded></item><item><title>Keep a decision log beside the PRD</title><link>https://saumya.dabi.live/blog/a-prd-needs-a-decision-log</link><guid isPermaLink="true">https://saumya.dabi.live/blog/a-prd-needs-a-decision-log</guid>
<pubDate>Sat, 15 Aug 2026 02:00:00 +0000</pubDate><category>PM Craft</category>
<description>Requirements explain what a team plans to build. A compact decision log preserves why the team chose it, what it rejected, and when to revisit the call.</description><content:encoded><![CDATA[<p>A PRD can be perfectly clear about what to build and still leave the team unable to explain why. That gap appears later, when a constraint changes or someone asks to revisit a trade-off. The document has requirements, but the reasoning that produced them is scattered across meetings, chat, and memory.</p>
<p>I treat a decision log as a small companion to the spec. It is not meeting minutes and it is not a place to defend every choice forever. Its job is to make important choices legible enough that the team can execute them now and reconsider them intelligently later.</p>
<h2 id="capture-choices-not-conversation">Capture choices, not conversation<a class="hlink" href="#capture-choices-not-conversation" title="Permanent link">#</a></h2>
<p>A meeting transcript records what was said. A decision log records what the team will do, and the boundary around that commitment. The distinction matters because discussion often contains tentative ideas that never become decisions.</p>
<p>For example, imagine a team deciding how a document-review tool should handle an unreadable upload. The transcript might include several possible responses: ask for a clearer file, try a second extraction method, or let a person review it. The log should say which behavior ships first, why, and what happens when it fails. It should not preserve every suggestion as though it were still under consideration.</p>
<p>A useful entry can fit in a few lines:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Field</th>
<th>What to write</th>
</tr>
</thead>
<tbody>
<tr>
<td>Decision</td>
<td>The behavior or constraint the team has chosen</td>
</tr>
<tr>
<td>Context</td>
<td>The user need or system condition that makes this choice relevant</td>
</tr>
<tr>
<td>Alternatives</td>
<td>The credible options considered, not every brainstorm</td>
</tr>
<tr>
<td>Rationale</td>
<td>The evidence and trade-off behind the choice</td>
</tr>
<tr>
<td>Owner</td>
<td>The person responsible for following through or reopening it</td>
</tr>
<tr>
<td>Revisit trigger</td>
<td>The observable event that could make the choice wrong</td>
</tr>
</tbody>
</table></div>
<p>The table is a prompt, not paperwork to complete for every tiny detail. Use it for choices that affect user outcomes, architecture, scope, risk, or dependencies. If a decision can be reversed in minutes and has no meaningful knock-on effects, recording it may cost more than it saves.</p>
<h2 id="keep-evidence-separate-from-assumptions">Keep evidence separate from assumptions<a class="hlink" href="#keep-evidence-separate-from-assumptions" title="Permanent link">#</a></h2>
<p>A rationale becomes brittle when opinion is presented as fact. Write down what is known, what is inferred, and what remains uncertain. That lets someone update the decision without having to reject the entire argument.</p>
<p>Suppose a team chooses not to support a certain file format in the first release. The evidence might be that the current parser cannot reliably extract its contents. The assumption might be that few target users need it. The decision could be to show a clear unsupported-format message rather than silently accepting the file. Those are three different statements, and they age at different speeds.</p>
<p>A practical template is:</p>
<blockquote>
<p>Evidence: what we observed or can verify. Assumption: what we believe but have not established. Choice: what we will do given both. Revisit when: what signal would change the balance.</p>
</blockquote>
<p>Avoid vague rationales such as “best UX,” “industry standard,” or “engineering prefers it.” Replace them with the actual trade-off: “We will ask for a replacement file rather than return a partial summary, because a partial result could be mistaken for complete coverage.” That sentence makes the user risk visible and gives a future reviewer something specific to test.</p>
<p>Do not manufacture certainty to make a decision sound strong. If the evidence is thin, state that the choice is a time-boxed hypothesis. A team can still move forward; it just knows what it is betting on.</p>
<h2 id="define-the-reversal-condition-before-launch">Define the reversal condition before launch<a class="hlink" href="#define-the-reversal-condition-before-launch" title="Permanent link">#</a></h2>
<p>“Revisit later” is not a trigger. It is a promise that usually gets crowded out. A useful trigger is concrete enough that someone can notice it without guessing what the original author meant.</p>
<p>For a decision about manual review, a trigger might be: “Reopen this if the supported input types change, or if we observe repeated cases where the current fallback blocks completion.” This avoids inventing a numeric threshold when no baseline exists, while still naming the events that matter.</p>
<p>When a quantitative threshold is appropriate, record its denominator and source. “Review if more than five of the last fifty uploads need manual intervention” is more interpretable than “review if failure rate is high.” But only use a number the team can actually measure. A precise-looking threshold with no instrumentation is decoration.</p>
<p>A reversal condition should also name what is not enough. A single anecdote may prompt investigation without automatically overturning the decision. Conversely, a safety or compliance concern may require immediate review even before a recurring pattern appears. Distinguish the trigger for learning from the trigger for changing course.</p>
<h2 id="connect-decisions-to-the-work">Connect decisions to the work<a class="hlink" href="#connect-decisions-to-the-work" title="Permanent link">#</a></h2>
<p>A log hidden in an old document will not help a team at the moment it makes a change. Link the relevant entry from the requirement, ticket, design, or launch checklist where the choice affects execution. Keep one canonical entry and reference it; copying the rationale into several places creates multiple versions that drift.</p>
<p>A product manager is often well placed to maintain this record, but the reasoning belongs to the people who made the choice. Ask the engineer about system constraints, the designer about interaction consequences, and the relevant domain owner about policy or operational limits. Attribute the decision to the accountable group or owner, not to a fictional consensus.</p>
<p>A review should be quick: scan entries whose triggers have fired, close those that no longer matter, and assign an owner to investigate the uncertain ones. If every item remains permanently “open,” the log has become a museum rather than a working tool.</p>
<h2 id="use-the-log-to-make-change-cheaper">Use the log to make change cheaper<a class="hlink" href="#use-the-log-to-make-change-cheaper" title="Permanent link">#</a></h2>
<p>The goal is not to prevent decisions from changing. It is to make change less expensive and less personal. When someone challenges a choice, the conversation can start with the context and evidence instead of reconstructing who said what in a meeting months ago.</p>
<p>Before calling a PRD ready, ask:</p>
<ul>
<li>Which choices would surprise the team if they changed next week?</li>
<li>For each one, can a new teammate find the rationale and the rejected alternative?</li>
<li>Are facts and assumptions clearly distinguished?</li>
<li>Is there an owner and a recognizable revisit trigger?</li>
<li>Does the linked work point to one current source of truth?</li>
</ul>
<p>If the answer is no, add only the missing decisions. A concise record of five consequential choices is more useful than a transcript of fifty conversations.</p>]]></content:encoded></item><item><title>An agent handoff should carry its evidence</title><link>https://saumya.dabi.live/blog/agent-handoff-evidence</link><guid isPermaLink="true">https://saumya.dabi.live/blog/agent-handoff-evidence</guid>
<pubDate>Tue, 11 Aug 2026 02:00:00 +0000</pubDate><category>Agents</category>
<description>When an AI agent passes work to a person or another agent, a conclusion alone is not enough. A compact evidence packet makes the handoff reviewable and actionable.</description><content:encoded><![CDATA[<p>An agent can finish a task and still leave the next person with more work than before. “Done” is not a useful handoff if the recipient has to reconstruct what happened, what was checked, and what remains uncertain.</p>
<h2 id="make-the-transfer-inspectable">Make the transfer inspectable<a class="hlink" href="#make-the-transfer-inspectable" title="Permanent link">#</a></h2>
<p>Treat the handoff as a small interface contract, not a completion message. Include four fields:</p>
<ul>
<li><strong>Outcome:</strong> what changed or what decision is being proposed.</li>
<li><strong>Evidence:</strong> the specific inputs, tool results, or records supporting it.</li>
<li><strong>Uncertainty:</strong> what could not be confirmed, including missing or conflicting information.</li>
<li><strong>Next action:</strong> who should do what, and whether it is safe to proceed before review.</li>
</ul>
<p>For example, “Vendor record updated” says little. A stronger handoff says: “Updated the billing address using the address in the signed order form; the account profile had a different postcode. I did not change the tax registration field. Please confirm the postcode conflict before the next invoice is issued.” The recipient can verify the source and see the unresolved boundary immediately.</p>
<h2 id="separate-evidence-from-confidence">Separate evidence from confidence<a class="hlink" href="#separate-evidence-from-confidence" title="Permanent link">#</a></h2>
<p>A confidence label is not evidence. “High confidence” does not tell a reviewer which source was used or whether the agent actually checked the relevant field. Keep the evidence concrete: name the source type, quote the relevant value where appropriate, and state what was compared. Avoid copying sensitive content into a handoff when a secure reference or record identifier will do.</p>
<p>This also helps when the work moves between agents. The next agent should receive the observations and constraints, not just a summary that has already compressed away caveats. If the evidence cannot be passed safely, pass a reference and an access instruction instead.</p>
<h2 id="define-when-the-handoff-blocks-action">Define when the handoff blocks action<a class="hlink" href="#define-when-the-handoff-blocks-action" title="Permanent link">#</a></h2>
<p>Not every uncertainty needs a human pause. Make the boundary explicit with a simple rule: proceed only when the evidence source is authoritative, required fields agree, and the action is reversible. Otherwise, route to review and name the unresolved question.</p>
<p>That rule is more useful than a generic “human in the loop” label because it tells the system what event triggers the loop. It also gives the reviewer a bounded task instead of asking them to re-do the whole workflow.</p>
<p>Before shipping an agent handoff, check: Can the recipient identify the proposed outcome, inspect its basis, see what is uncertain, and know whether action is blocked? If any answer is no, the handoff is not finished.</p>]]></content:encoded></item><item><title>The fallback is part of the AI experience</title><link>https://saumya.dabi.live/blog/the-fallback-is-part-of-the-ai-experience</link><guid isPermaLink="true">https://saumya.dabi.live/blog/the-fallback-is-part-of-the-ai-experience</guid>
<pubDate>Sat, 08 Aug 2026 02:00:00 +0000</pubDate><category>AI Product</category>
<description>When an AI answer is uncertain, a blank error state leaves users stranded. Design the fallback as a clear next step, with boundaries and a useful alternative.</description><content:encoded><![CDATA[<p>An AI feature&rsquo;s weakest moment is not always a wrong answer. It is the moment the system cannot answer and leaves the user with nowhere to go.</p>
<p>A generic “Something went wrong” tells a person nothing about whether they should retry, rephrase, provide more context, or switch to another path. The fallback is part of the product interface, not an implementation detail.</p>
<h2 id="give-uncertainty-a-useful-shape">Give uncertainty a useful shape<a class="hlink" href="#give-uncertainty-a-useful-shape" title="Permanent link">#</a></h2>
<p>Start by distinguishing three cases: the system lacks information, the request is ambiguous, or the model&rsquo;s output failed a quality check. They need different next steps.</p>
<ul>
<li>If a source is missing, say which source or detail would help.</li>
<li>If the request has two plausible meanings, show the choices and let the user select one.</li>
<li>If the answer fails a quality threshold, say that the feature could not produce a dependable result and offer the existing non-AI route.</li>
</ul>
<p>Do not ask the user to “try again” when another attempt is unlikely to change the evidence. Do not present an uncertain answer as complete just to avoid an empty state.</p>
<h2 id="keep-the-users-task-moving">Keep the user&rsquo;s task moving<a class="hlink" href="#keep-the-users-task-moving" title="Permanent link">#</a></h2>
<p>A good fallback preserves what the user already entered, explains what happens next, and avoids making them start over. If the system can safely complete part of the task, label that partial result. If it cannot, make the manual path easy to find.</p>
<p>Before launch, test at least these states: no relevant source, conflicting sources, unclear intent, a timed-out request, and a low-confidence result. For each one, specify the message, the available action, and whether the user&rsquo;s input is retained. Treat a repeated timeout differently from a single slow response: after a retry budget is reached, move the person to another path rather than asking them to keep clicking.</p>
<p>The practical rule: whenever the AI path stops, the product should still tell the user what they can do next.</p>]]></content:encoded></item><item><title>Ask the coding assistant what it needs before it codes</title><link>https://saumya.dabi.live/blog/ai-coding-assistant-before-code</link><guid isPermaLink="true">https://saumya.dabi.live/blog/ai-coding-assistant-before-code</guid>
<pubDate>Tue, 04 Aug 2026 02:00:00 +0000</pubDate><category>AI Tooling</category>
<description>A coding assistant asked to implement a feature will often choose an approach before the product question is settled. Use it first to map unknowns, constraints and the cheapest useful test.</description><content:encoded><![CDATA[<p>The fastest way to get code from an AI assistant is to ask it to build something. That is not always the fastest way to learn whether the thing should exist. A polished first draft can make an unsettled product decision feel settled because there is now something concrete to react to.</p>
<p>I get more value when the first request is not “implement this.” It is “tell me what you would need to know before implementing it.”</p>
<h2 id="separate-the-question-from-the-solution">Separate the question from the solution<a class="hlink" href="#separate-the-question-from-the-solution" title="Permanent link">#</a></h2>
<p>A feature request usually bundles an outcome, a possible design and a set of hidden assumptions. For example: “Add a comparison view so a manager can spot unusual changes.” The outcome might be faster review. The proposed design is a comparison view. The assumptions may include that the manager has a stable baseline, can identify what counts as unusual, and has permission to see both records.</p>
<p>Ask the coding assistant to inspect the relevant code and return four things before it edits anything:</p>
<ol>
<li>The current behavior, with file and function references.</li>
<li>The smallest product assumption that could make the proposed feature unnecessary or unsafe.</li>
<li>Two implementation shapes and what each would require.</li>
<li>A cheap test that would change the decision to build.</li>
</ol>
<p>This does not make the assistant a product strategist. It gives the PM a structured way to use the tool&rsquo;s code-reading ability without letting its first implementation become the default answer.</p>
<h2 id="bound-the-context-not-just-the-prompt">Bound the context, not just the prompt<a class="hlink" href="#bound-the-context-not-just-the-prompt" title="Permanent link">#</a></h2>
<p>A long prompt can still produce a weak answer if the tool is pointed at an entire repository with no boundary. The model may spend its context on unrelated files, infer behavior from stale documentation, or edit a shared component when the task is local.</p>
<p>Name the files or folders it may inspect, the files it may change, and the paths it must not touch. If the relevant area is unknown, ask for a read-only map first. Then review the map and widen the boundary deliberately. The instruction “do not modify files” is more reliable than assuming an assistant will know that exploration should remain read-only.</p>
<p>A useful task brief fits in a small template:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Field</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>User outcome</td>
<td>A reviewer can find the changed field quickly</td>
</tr>
<tr>
<td>Known current behavior</td>
<td>The list shows only the latest value</td>
</tr>
<tr>
<td>Allowed context</td>
<td>The list page, its data adapter, and its tests</td>
</tr>
<tr>
<td>Forbidden changes</td>
<td>No API contract or shared navigation changes</td>
</tr>
<tr>
<td>Question to answer</td>
<td>Can a local view expose the old and new values?</td>
</tr>
<tr>
<td>Evidence required</td>
<td>Existing tests plus one focused regression test</td>
</tr>
</tbody>
</table></div>
<p>The examples are hypothetical. The point is to make the boundary observable: if the diff changes a forbidden area, it is easy to reject before debating code quality.</p>
<h2 id="ask-for-alternatives-with-costs">Ask for alternatives with costs<a class="hlink" href="#ask-for-alternatives-with-costs" title="Permanent link">#</a></h2>
<p>When the assistant finds more than one plausible approach, ask it to compare them against the same constraints. “List alternatives” is too loose; it invites a list of implementation patterns with no decision relevance.</p>
<p>Ask for the smallest reversible option, the option that best preserves future flexibility, and the risk each introduces. For the comparison example, a temporary side-by-side view might answer the review question without changing the underlying record model. A persisted history feature could support more workflows but would introduce retention, access and migration decisions.</p>
<p>Do not ask for arbitrary scores unless the inputs mean something. A table with “what this makes easier,” “what it makes harder,” and “what we still need to learn” is often more useful than a synthetic ranking.</p>
<h2 id="make-the-prototype-answer-one-question">Make the prototype answer one question<a class="hlink" href="#make-the-prototype-answer-one-question" title="Permanent link">#</a></h2>
<p>A prototype is valuable when its result can change a decision. Before letting the assistant build it, finish this sentence: “If the prototype shows <strong><em>, we will </em></strong>; if it shows <strong><em>, we will </em></strong>.” If neither branch changes what the team does, the prototype is probably a demo rather than a test.</p>
<p>Keep the prototype narrow enough to inspect. A small script that compares two representative records may be more informative than a fully integrated screen. Use synthetic or approved sample data. If the task depends on real user information, do not paste it into a tool unless the data handling is explicitly permitted.</p>
<p>The assistant can produce the test harness, but the PM still chooses the input cases and the success condition. Ask it to explain which cases are missing and why. Then add the cases that represent a realistic failure: no baseline, a deleted field, a permission boundary, or conflicting values.</p>
<h2 id="treat-the-diff-as-a-claim">Treat the diff as a claim<a class="hlink" href="#treat-the-diff-as-a-claim" title="Permanent link">#</a></h2>
<p>Generated code is a claim that a particular change solves the stated problem. Review the claim against the task, not against how plausible the code looks.</p>
<p>A compact review has four passes:</p>
<ul>
<li><strong>Scope:</strong> Did it touch only allowed files?</li>
<li><strong>Behavior:</strong> Does the change match the user outcome rather than merely the requested UI?</li>
<li><strong>Evidence:</strong> Did the focused test run, and does it fail without the change?</li>
<li><strong>Risk:</strong> Does the change alter data access, persistence, or a shared contract?</li>
</ul>
<p>If a test cannot run, the output is not verified. If a result depends on a manual judgment, record who made it and what they saw. “The assistant said the tests pass” is not a substitute for seeing the test result.</p>
<h2 id="a-better-first-prompt">A better first prompt<a class="hlink" href="#a-better-first-prompt" title="Permanent link">#</a></h2>
<p>Try this before asking a coding assistant to implement a new product idea:</p>
<blockquote>
<p>Read only the listed files and do not edit. Summarize the current behavior with code references. List the assumptions that must be true for this feature to help the user. Give two bounded implementation options, their risks, and one reversible test that could change whether we build it. Stop after the analysis.</p>
</blockquote>
<p>After reviewing that output, decide whether the problem is understood. Then give the assistant a small implementation task with allowed files and a test condition. The tool may still write the code; it just no longer gets to decide what the code is evidence for.</p>
<p>Before the next AI-assisted build, write down the product question, the allowed context, and the result that would change your mind. If any of those is missing, ask for the map before the patch.</p>]]></content:encoded></item><item><title>Why your LLM tests miss edge cases</title><link>https://saumya.dabi.live/blog/why-your-llm-tests-miss-edge-cases</link><guid isPermaLink="true">https://saumya.dabi.live/blog/why-your-llm-tests-miss-edge-cases</guid>
<pubDate>Sat, 01 Aug 2026 02:00:00 +0000</pubDate><category>Evals</category>
<description>Testing LLMs like software breaks when the model finds paths you didn&#x27;t anticipate. A framework for finding the gaps in your eval set.</description><content:encoded><![CDATA[<p>Your eval set has a leak. It&rsquo;s not a bug in the harness or a flaky assertion. It&rsquo;s a systematic gap: you&rsquo;ve written tests the model <em>can</em> pass, which is not the same as tests the model <em>will</em> pass.</p>
<p>When you test software, you map inputs to outputs. You cover branches: the if-statement fires, the loop runs, the edge case at zero doesn&rsquo;t crash. You achieve coverage. The model, though, doesn&rsquo;t execute branches. It computes probability distributions over tokens. A test that passes isn&rsquo;t evidence the model is robust; it&rsquo;s evidence the test was written in a way the model learned to handle.</p>
<h2 id="the-deterministic-illusion">The deterministic illusion<a class="hlink" href="#the-deterministic-illusion" title="Permanent link">#</a></h2>
<p>You write a test like:</p>
<pre><code>Input: &quot;What is 2 + 2?&quot;
Expected: &quot;4&quot;
Result: PASS ✓
</code></pre>
<p>You write fifty more like it. The model passes all fifty. You think you&rsquo;re done.</p>
<p>What you&rsquo;ve actually done: you&rsquo;ve found fifty points in semantic space where the model learned to output the right token. You haven&rsquo;t tested robustness. You&rsquo;ve tested fifty inputs that look like the ones you thought of.</p>
<p>The problem: your eval set covers a small patch of a very large input space. The moment a user writes something you didn&rsquo;t anticipate, a different phrasing, a harder number, a logical twist, you have no evidence either way about what the model will do.</p>
<h2 id="where-edge-cases-hide">Where edge cases hide<a class="hlink" href="#where-edge-cases-hide" title="Permanent link">#</a></h2>
<p>In software, edge cases cluster at boundaries: zero, negative numbers, empty lists, null pointers. You find them by reasoning about the code. In LLMs, edge cases are distributed across semantic space. They&rsquo;re not at boundaries; they&rsquo;re everywhere you didn&rsquo;t write a test.</p>
<p>Consider a feature that summarizes legal documents. Your eval set has:</p>
<ul>
<li>40 summaries of mortgage agreements ✓</li>
<li>30 summaries of employment contracts ✓</li>
<li>20 summaries of NDAs ✓</li>
<li>10 edge cases: very short documents, documents with unusual formatting</li>
</ul>
<p>You test all ninety. The model passes. You ship.</p>
<p>A user gives it a contract written in fragments, with embedded definitions that reference other clauses. The model hasn&rsquo;t seen this structure in your eval set. It fails.</p>
<p>The test you missed wasn&rsquo;t at a boundary. It was in a region of input space your examples didn&rsquo;t cover. You can&rsquo;t reason your way to it. You can&rsquo;t enumerate all possibilities. You have to find it.</p>
<h2 id="two-practical-moves">Two practical moves<a class="hlink" href="#two-practical-moves" title="Permanent link">#</a></h2>
<p><strong>Adversarial sampling.</strong> Have a person—or another LLM—write inputs specifically designed to break your model. Not random inputs. Targeted attacks: ambiguous phrasings, contextual traps, logical puzzles where the model usually fails. Evaluate your model on these adversarial examples. Every failure is a new eval row; every pass is evidence for that region, not the whole space.</p>
<p><strong>Statistical coverage instead of case coverage.</strong> Instead of &ldquo;I&rsquo;ve tested fifty inputs,&rdquo; think: &ldquo;I&rsquo;ve sampled from a distribution of inputs. How much variance does my eval set cover?&rdquo; Take your 50 test cases and measure the semantic diversity: use embeddings, cluster them, plot them. Are you sampling from across the space, or just hitting the same region repeatedly? If you see three tight clusters in embedding space, you&rsquo;re testing three easy cases over and over.</p>
<h2 id="the-pattern-to-watch-for">The pattern to watch for<a class="hlink" href="#the-pattern-to-watch-for" title="Permanent link">#</a></h2>
<p>If your eval set is written by the team building the feature, it will lean toward cases the team thought of and cases the prompt was already tuned against. Ask someone outside the team, a user, a support lead, another PM, to write test cases without seeing your prompt. Compare their eval set to yours. The tests they write that your model fails are the gaps you can&rsquo;t see from the inside.</p>
<h2 id="the-real-rule">The real rule<a class="hlink" href="#the-real-rule" title="Permanent link">#</a></h2>
<p>A passing eval set is not evidence of robustness. It&rsquo;s a report of what your tests could measure. Robustness is what happens when the model sees inputs outside your test&rsquo;s semantic cluster.</p>
<p>Build an eval set that moves. Rotate in fresh adversarial examples. Sample from outside your comfort zone. The edge cases you find by searching are edge cases you can fix.</p>]]></content:encoded></item><item><title>Who owns the next step in a credit workflow?</title><link>https://saumya.dabi.live/blog/fintech-workflow-owner</link><guid isPermaLink="true">https://saumya.dabi.live/blog/fintech-workflow-owner</guid>
<pubDate>Tue, 28 Jul 2026 02:00:00 +0000</pubDate><category>Fintech &amp; SaaS</category>
<description>A loan application can look complete while the work is stuck between people. Model each state with an owner, an expected next action and an exception path before optimizing for speed.</description><content:encoded><![CDATA[<p>A status label can make a lending workflow look orderly while nobody knows who should act next. “Pending” may mean the applicant owes a document, an operations team is checking it, a decision-maker has not reviewed it, or an external dependency has not responded. Those are different states wearing the same word.</p>
<p>The product problem is not merely to make the application form shorter. It is to make responsibility and the next move visible across the people and systems that carry the case.</p>
<h2 id="model-work-not-just-records">Model work, not just records<a class="hlink" href="#model-work-not-just-records" title="Permanent link">#</a></h2>
<p>A loan application is a record, but the workflow is a series of actions: information is supplied, checked, resolved, reviewed, and eventually closed or continued. The states should describe what is happening to the work, not just how much information is present in the form.</p>
<p>For every state, write down:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Question</th>
<th>Example answer</th>
</tr>
</thead>
<tbody>
<tr>
<td>Who owns the next action?</td>
<td>Applicant, operations reviewer, or decision owner</td>
</tr>
<tr>
<td>What triggers it?</td>
<td>A missing field, a submitted document, or a completed check</td>
</tr>
<tr>
<td>What evidence is required?</td>
<td>The specific information needed to move forward</td>
</tr>
<tr>
<td>What happens if it cannot proceed?</td>
<td>A named exception route and a visible reason</td>
</tr>
</tbody>
</table></div>
<p>If any cell is blank, the status is likely to become a parking lot. “Waiting for review” is not an operational state if the system cannot identify the queue, owner, or next event.</p>
<h2 id="separate-missing-information-from-review">Separate missing information from review<a class="hlink" href="#separate-missing-information-from-review" title="Permanent link">#</a></h2>
<p>A request for a document and an internal verification are not the same kind of wait. The first needs a clear ask to the applicant; the second needs a work queue and an owner. Combining them creates unhelpful notifications and makes it difficult to know whether the customer or the institution has the next move.</p>
<p>Use distinct states such as “information requested,” “information received,” and “under review,” but only if the people operating the workflow can act on those distinctions. Each state should have a transition rule and an exit condition. If the product cannot tell whether a document is sufficient, it should not automatically label the case “complete.”</p>
<p>Show the reason for the wait in language the relevant person can use. “More information required” is weaker than “Please provide the page that shows the requested repayment date.” The exact wording depends on the process; the rule is that the request must name the missing evidence and what will happen after it arrives.</p>
<h2 id="design-the-exception-path-before-the-happy-path-is-polished">Design the exception path before the happy path is polished<a class="hlink" href="#design-the-exception-path-before-the-happy-path-is-polished" title="Permanent link">#</a></h2>
<p>A workflow designed only for a complete, legible application will push exceptions into email, spreadsheets, and memory. That is where ownership becomes ambiguous and the product loses its record of why a decision was delayed.</p>
<p>List the exceptions that alter the next action: unreadable information, conflicting values, duplicate submissions, a deadline that has passed, or a case that needs a human review. For each, specify who receives it, what they can decide, and what evidence they must leave behind.</p>
<p>Do not turn every exception into a free-text comment. Free text can capture nuance, but it does not reliably route the work. Pair it with a small set of structured reasons and a clear escalation owner. If the situation does not fit the list, capture the explanation without pretending the case is in a known state.</p>
<h2 id="measure-the-handoffs">Measure the handoffs<a class="hlink" href="#measure-the-handoffs" title="Permanent link">#</a></h2>
<p>A single end-to-end duration can hide where the workflow is stuck. Measure the time between meaningful handoffs: request to submission, submission to review, review to decision, and decision to communication. State the start and end event for each interval so that a dashboard does not compare incompatible clocks.</p>
<p>Then segment by case type or route only when those distinctions are supported by the data and useful to an operating decision. A long wait caused by missing information may need better instructions. A long internal review may need queue ownership or policy clarification. Averages can obscure a small group waiting far longer; inspect the distribution and the oldest open cases as well.</p>
<p>Do not optimize elapsed time by hiding necessary checks. In a financial workflow, speed is valuable only if the product preserves the evidence and review required for a sound decision. Make the speed measure sit beside the quality and exception measures, not in place of them.</p>
<h2 id="make-the-next-step-legible-to-every-participant">Make the next step legible to every participant<a class="hlink" href="#make-the-next-step-legible-to-every-participant" title="Permanent link">#</a></h2>
<p>Applicants need to know whether to act, what to provide, and whether their submission was received. Operators need a queue they can work and a record of what is outstanding. Decision-makers need the evidence and the unresolved questions, not a green badge that implies certainty.</p>
<p>That does not mean every participant sees every field. Permissions and data minimization still matter. Design the workflow so each role sees enough to perform its action and no more than the role requires.</p>
<p>A handoff also needs a time expectation. “Assigned” does not tell an operator or applicant when the next update should arrive. Define the expected review window, what message appears if that window is missed, and who can unblock the queue. Do not promise a decision time the team cannot control; distinguish an internal target from an external dependency. This makes delays visible without implying that every case should follow the same path.</p>
<p>A useful prototype can be a state-transition table before it is a new screen. Put one ordinary case and two exceptions through it. For each transition, check that the owner, trigger, permission, notification and audit record are explicit. If the next move cannot be answered from the product, fix the workflow model before polishing the dashboard.</p>]]></content:encoded></item><item><title>Decide when your AI agent should stop</title><link>https://saumya.dabi.live/blog/every-agent-needs-a-stopping-rule</link><guid isPermaLink="true">https://saumya.dabi.live/blog/every-agent-needs-a-stopping-rule</guid>
<pubDate>Sat, 25 Jul 2026 02:00:00 +0000</pubDate><category>Agents</category>
<description>An agent spec needs more than a goal and a set of tools. Define what counts as done, when to stop, and what evidence earns another action before it touches user data.</description><content:encoded><![CDATA[<p>An agent can have a clear goal, the right tools, and still behave badly. It keeps searching after it has enough evidence, retries a failed action with a different guess, or quietly stops halfway through a task. The missing part of the spec is often not another capability. It is a rule for when the system is done.</p>
<p>A stopping rule sounds like an implementation detail. It is a product decision: it defines how much work the system may do, what it is allowed to infer, and when a person takes over.</p>
<h2 id="a-goal-is-not-a-completion-contract">A goal is not a completion contract<a class="hlink" href="#a-goal-is-not-a-completion-contract" title="Permanent link">#</a></h2>
<p>“Find the renewal date and prepare a reminder” looks specific until an agent has to execute it. Which folder should it search? What if two documents contain different dates? Is drafting the reminder enough, or should it send one? If it cannot find the date, should it search every connected drive or ask the user?</p>
<p>A goal describes a destination. A completion contract describes the evidence that the destination was reached and the boundary beyond which the agent must not continue.</p>
<p>For that example, a first contract could say:</p>
<ul>
<li>Search only the user’s selected contract folder.</li>
<li>Return the date with a link to the source passage.</li>
<li>Draft a reminder, but do not send it.</li>
<li>If the sources disagree or no date is found, stop and show what was checked.</li>
</ul>
<p>That is less magical than “handle renewals for me.” It is also testable. The user can tell what happened, and the team can tell whether the agent followed the intended path.</p>
<h2 id="define-done-in-observable-terms">Define “done” in observable terms<a class="hlink" href="#define-done-in-observable-terms" title="Permanent link">#</a></h2>
<p>An agent should not decide it is done because its last message sounds complete. Done should correspond to a state a person or system can verify.</p>
<p>For a read-only research task, the finish line might require a result and a source. For a record update, it might require a successful response from the system and a read-back showing the new value. For a draft, it might mean the draft exists in the expected place and remains unsent.</p>
<p>Write the completion condition as a small checklist:</p>
<div class="tbl"><table>
<thead>
<tr>
<th>Task type</th>
<th>Evidence required to call it done</th>
</tr>
</thead>
<tbody>
<tr>
<td>Find information</td>
<td>Answer plus a source the user can open</td>
</tr>
<tr>
<td>Prepare a draft</td>
<td>Draft exists in the agreed location; no send action occurred</td>
</tr>
<tr>
<td>Change a record</td>
<td>Update succeeded and a follow-up read confirms the value</td>
</tr>
<tr>
<td>Multi-step task</td>
<td>Every required step is verified, or remaining steps are listed</td>
</tr>
</tbody>
</table></div>
<p>The distinction between “attempted” and “completed” matters. A tool returning without an error may only mean the request was accepted. If the outcome matters, read it back. If it cannot be verified, say so instead of converting an attempt into a success claim.</p>
<h2 id="put-a-budget-on-actions">Put a budget on actions<a class="hlink" href="#put-a-budget-on-actions" title="Permanent link">#</a></h2>
<p>Token limits and timeouts protect infrastructure. They do not tell the product how much autonomy is appropriate. A user-facing agent also needs an action budget: a limit on searches, retries, mutations, or elapsed time for one request.</p>
<p>For a first version of the read-only renewal lookup, I might start with five document searches, one follow-up search if a source is incomplete, and a two-minute deadline. Those are hypotheses to test against real tasks, not universal numbers. The important part is that the agent has a defined point at which it must stop and report what it found.</p>
<p>Separate the budget by action type. Reading one more document is not equivalent to sending a message or changing a record. A useful policy might allow several read-only lookups, permit one retry after a transient error, and require explicit approval before any irreversible action. Never let a retry silently broaden permissions or turn a draft into a sent message.</p>
<p>Then measure where the budget is spent. If successful tasks regularly need one more search, adjust the search cap. If retries repeat the same failure, change the recovery path rather than raising the cap. More autonomy should follow evidence that the extra action improves the result without creating a worse failure mode.</p>
<h2 id="make-stopping-conditions-explicit">Make stopping conditions explicit<a class="hlink" href="#make-stopping-conditions-explicit" title="Permanent link">#</a></h2>
<p>“Stop when confused” is too vague to implement or evaluate. Name the conditions that end the run:</p>
<ul>
<li><strong>Missing information:</strong> a required field is absent from the permitted sources.</li>
<li><strong>Conflicting evidence:</strong> two sources disagree and no precedence rule resolves them.</li>
<li><strong>Permission boundary:</strong> the next step needs access or approval the agent does not have.</li>
<li><strong>Repeated failure:</strong> the same tool action fails again after its allowed retry.</li>
<li><strong>Budget exhausted:</strong> the remaining task cannot be completed within the action or time limit.</li>
</ul>
<p>Each condition needs a user-facing result. Missing information should name the missing field. Conflicting evidence should show both sources. A permission boundary should ask for the specific approval needed. Budget exhaustion should summarize completed and unfinished steps.</p>
<p>This turns stopping into a designed path instead of an error state. It also gives the team a useful set of evaluation cases: one for each reason the agent should stop, plus one where it should continue.</p>
<h2 id="make-partial-progress-legible">Make partial progress legible<a class="hlink" href="#make-partial-progress-legible" title="Permanent link">#</a></h2>
<p>A task is not always all-or-nothing. If an agent finds the date and prepares the reminder but cannot save the draft, it should not report “done” or return a generic failure. It should say which parts are complete, which are not, and why it stopped.</p>
<p>I would make the final status one of three things:</p>
<ol>
<li><strong>Complete:</strong> all required outcomes were verified.</li>
<li><strong>Partial:</strong> some outcomes were verified; unfinished steps are named.</li>
<li><strong>Blocked:</strong> no safe next action is available without new information or approval.</li>
</ol>
<p>Keep that status separate from the natural-language summary. The interface can show a concise sentence, but the underlying result should preserve the status, completed actions, evidence, and stop reason. That makes the experience clearer for users and the behavior easier to audit later.</p>
<h2 id="the-stopping-rule-review">The stopping-rule review<a class="hlink" href="#the-stopping-rule-review" title="Permanent link">#</a></h2>
<p>Before shipping an agent, take one normal request and walk it through these questions:</p>
<ol>
<li>What exact evidence proves the task is complete?</li>
<li>How many searches, retries, and state-changing actions are allowed?</li>
<li>Which conditions require stopping or asking the user?</li>
<li>Can the agent distinguish a successful attempt from a verified outcome?</li>
<li>If it stops halfway through, can the user see what remains?</li>
</ol>
<p>If those answers are missing, the agent does not yet have a product contract. Add the stopping rule before adding another tool.</p>]]></content:encoded></item></channel></rss>