<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Tuszynski</title>
    <description>The latest articles on DEV Community by Michael Tuszynski (@michaeltuszynski).</description>
    <link>https://dev.to/michaeltuszynski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1447774%2Fa99eea93-7845-4764-9fce-b1755bcfa456.png</url>
      <title>DEV Community: Michael Tuszynski</title>
      <link>https://dev.to/michaeltuszynski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michaeltuszynski"/>
    <language>en</language>
    <item>
      <title>An Ontology Your AI Generated Is a Guess With a Schema</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Wed, 28 Oct 2026 18:04:01 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/an-ontology-your-ai-generated-is-a-guess-with-a-schema-22d5</link>
      <guid>https://dev.to/michaeltuszynski/an-ontology-your-ai-generated-is-a-guess-with-a-schema-22d5</guid>
      <description>&lt;p&gt;An agent asked for net revenue by sales rep before the 2025 territory change can do one of two things. A model writing raw SQL finds a reps table, an invoices table and a join key, and returns a number. A semantic layer with no definition for pre-change territories returns an error. The number looks better in a demo. The error is the one you want behind a board deck.&lt;/p&gt;

&lt;p&gt;That difference is most of what remains of the case for the enterprise ontology. It holds only if a named person decided the definitions on a known date. The 2026 wave of products that generate those definitions automatically puts the guess back in, now carrying the word "ontology." This post argues for budgeting the agreement, not the graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  The word got popular after its best argument got weaker
&lt;/h2&gt;

&lt;p&gt;The original pitch for ontologies in AI was accuracy, and it arrived with a strong number. In 2023, &lt;a href="https://arxiv.org/abs/2311.07509" rel="noopener noreferrer"&gt;Sequeda and colleagues at data.world&lt;/a&gt;, then a seller of a knowledge-graph data catalog, tested GPT-4 zero-shot on an insurance-domain enterprise schema. Over the raw SQL schema it averaged 16.7% execution accuracy. Over a knowledge graph built from an ontology plus mappings, it reached 54.2%, a little more than 3x. On high-schema-complexity questions, SQL accuracy was 0%. The paper's own conclusion recommends buying a data catalog with a knowledge-graph architecture, so treat those figures as a vendor's benchmark too.&lt;/p&gt;

&lt;p&gt;The models have improved since. In April, dbt Labs &lt;a href="https://docs.getdbt.com/blog/semantic-layer-vs-text-to-sql-2026" rel="noopener noreferrer"&gt;reran the benchmark&lt;/a&gt; on the same ACME Insurance dataset: 11 questions, each run 20 times. In dbt's runs, text-to-SQL went from 32.7% with GPT-4 in November 2023 to 64.5% with Claude Sonnet 4.6. The semantic layer scored 72.7%. After an LLM added three dbt models, text-to-SQL hit 90.0% and the semantic layer 98.2%.&lt;/p&gt;

&lt;p&gt;Both configurations show the same 8.2-point gap. As a ratio, that's about 1.13x and then 1.09x. Don't set those beside the 2023 3x as one trend line: that figure compared a knowledge graph with raw SQL in data.world's setup, while dbt compared its semantic layer with text-to-SQL in its own. What dbt's rerun does show is a single-digit gap between a defined layer and a current model. Discount even that. dbt sells the semantic layer, and 11 questions on one schema is a small sample. To get text-to-SQL working, dbt loaded the entire schema into context, which it says isn't practical for larger datasets. On a real warehouse the gap could widen again. The evidence is suggestive, not conclusive. Evidence this thin can't carry a platform purchase on accuracy alone. Forrester, for one, has adopted the language anyway. In an analyst blog post it &lt;a href="https://www.forrester.com/blogs/the-next-evolution-of-ai-will-rely-on-context-layers/" rel="noopener noreferrer"&gt;now calls the context layer&lt;/a&gt; "the next evolution of semantic layers and knowledge graphs," built "through graph-based ontologies and linked context."&lt;/p&gt;

&lt;h2&gt;
  
  
  What survived is how each approach fails
&lt;/h2&gt;

&lt;p&gt;The dbt post says the important part itself: "With text-to-SQL, failure looks like a plausible but incorrect answer. With the Semantic Layer, failure looks like an error message." And more bluntly: "Text-to-SQL will cheerfully give you a wrong number."&lt;/p&gt;

&lt;p&gt;At 90% accuracy, the remaining misses arrive formatted exactly like the hits. They come with two decimal places, a chart and a confident sentence. The case here is that a number that looks right invites fewer checks than an error does. The failure produces output that passes for success, so nothing prompts a check. An error message gets noticed the first time it appears.&lt;/p&gt;

&lt;p&gt;So the durable value of a defined layer is its boundary. Inside the definitions it answers. Outside them it refuses.&lt;/p&gt;

&lt;h2&gt;
  
  
  A refusal borrows its credibility from whoever wrote the definitions
&lt;/h2&gt;

&lt;p&gt;The boundary cuts both ways. A layer that refuses out-of-scope questions answers in-scope ones with full confidence. If the definition inside the boundary is wrong, the layer returns a wrong number with the authority of a system known to say "can't answer." If users learn that the layer refuses when it doesn't know, they'll reasonably read every answer it does give as checked. That plausibly makes a wrong definition more dangerous there than in text-to-SQL, where users have less reason to drop their skepticism.&lt;/p&gt;

&lt;p&gt;That makes the definitions the load-bearing part, and the 2026 products are automating exactly that part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new tools generate the definitions they used to require
&lt;/h2&gt;

&lt;p&gt;The vendors deserve a fair reading. Microsoft's &lt;a href="https://learn.microsoft.com/en-us/fabric/iq/ontology/overview" rel="noopener noreferrer"&gt;Fabric IQ ontology&lt;/a&gt;, in preview, can generate an ontology from Power BI semantic models, and "changes follow a proposal-first model so users can review them before applying them." A rule there is "a natural-language statement of business logic linked to ontology concepts." Snowflake's &lt;a href="https://docs.snowflake.com/en/user-guide/views-semantic/autopilot" rel="noopener noreferrer"&gt;Semantic View Autopilot&lt;/a&gt; uses query history accessible to the creating role "to generate both relationships and verified query suggestions," which you can review and apply. Databricks' &lt;a href="https://docs.databricks.com/aws/en/genie/genie-ontology" rel="noopener noreferrer"&gt;Genie Ontology&lt;/a&gt;, also in preview, gives each snippet "an authority score based on where it was generated from, how often it is used, and how fresh it is," then "ranks the most relevant snippets, resolves conflicts, and answers."&lt;/p&gt;

&lt;p&gt;Review buttons exist. The risk is in what the review checks. In Databricks' score, usage frequency and freshness measure popularity, and source measures provenance. None of the three records who agreed to a definition. Snowflake's suggestions start from queries people already ran. The most-used definition of "active customer" can be the one finance stopped accepting last quarter, because old dashboards still run it every hour. A suggestion mined from query history encodes what people ran, wrong queries included.&lt;/p&gt;

&lt;p&gt;Read "resolves conflicts" twice. Two competing definitions of a term amount to a business decision, and here a ranking function settles it at question time. &lt;a href="https://www.topquadrant.com/blog/ontology-market-guide" rel="noopener noreferrer"&gt;TopQuadrant&lt;/a&gt;, which sells formally governed ontologies and so has a stake in this point, puts it most sharply: an inferred definition "can tell you how a term is currently used. It cannot tell you what anyone agreed."&lt;/p&gt;

&lt;p&gt;Voltaire wrote the logic down in 1759. In &lt;a href="https://www.gutenberg.org/ebooks/19942" rel="noopener noreferrer"&gt;&lt;em&gt;Candide&lt;/em&gt;&lt;/a&gt;, Pangloss proves "that things cannot be otherwise than as they are": the nose was "formed to bear spectacles," and so "we have spectacles." An ontology mined from usage reasons the same way. The definition exists and the dashboards run it, so it must be the right one. Rank by popularity and freshness, and whatever wins is, by construction, the best of all possible definitions. Nobody had to agree to it for it to win.&lt;/p&gt;

&lt;h2&gt;
  
  
  The objections fail on their own mechanisms
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Models will close the last 8 points too."&lt;/strong&gt; Maybe, on accuracy. But take two valid definitions of active customer: a paid invoice in the last 90 days, or a login in the last 30. Both are computable from the schema, and both are defensible. No amount of schema reading tells a model which one the organization adopted. That's a decision. It's recorded somewhere or nowhere, and a better model doesn't change which.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Inferred definitions beat nothing."&lt;/strong&gt; For ad hoc exploration, yes. dbt's own recommendation is "Text-to-SQL for ad hoc analyses and smaller datasets." Exploration has a human in the loop: an analyst sees an odd number, changes a filter and reruns, so a wrong definition costs one query. A KPI or a board deck runs on a schedule without anyone reading the SQL, so the same wrong definition repeats in every report until someone notices the trend is off. An automated action skips even that. Those three need a signature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Palantir-style ontologies are about actions."&lt;/strong&gt; Correct. &lt;a href="https://www.topquadrant.com/blog/ontology-market-guide" rel="noopener noreferrer"&gt;TopQuadrant's guide&lt;/a&gt; describes Palantir's model as object, link and action types: verbs as well as nouns, and who is permitted to trigger them. Action permissions check who may trigger "approve refund." They don't check what the refund rule's nouns mean. A rule keyed to "active customer" approves the wrong refunds if that definition is wrong, however tight the permission. So signed definitions sit underneath action permissions. One doesn't replace the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The definition of record fits on one card
&lt;/h2&gt;

&lt;p&gt;Write one card per term an agent is allowed to use. The values below are illustrative.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;net_revenue&lt;/span&gt;
&lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;sum(invoice_amount - credit_amount)&lt;/span&gt;
  &lt;span class="s"&gt;where invoice_status = 'paid'&lt;/span&gt;
  &lt;span class="s"&gt;and invoice_date between :start and :end&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;one named person&amp;gt;, Controller&lt;/span&gt;   &lt;span class="c1"&gt;# never "Finance"&lt;/span&gt;
&lt;span class="na"&gt;decided&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-10-05&lt;/span&gt;
&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authored&lt;/span&gt;                    &lt;span class="c1"&gt;# generated | authored&lt;/span&gt;
&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Q3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2026&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(2026-07-01&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2026-09-30)"&lt;/span&gt;
  &lt;span class="na"&gt;must_return&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18420000&lt;/span&gt;
&lt;span class="na"&gt;must_refuse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sales&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2025&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;territory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;change"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2023-01-01&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(pre-migration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;billing)"&lt;/span&gt;
&lt;span class="na"&gt;review_trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;schema change to invoices or credits&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pricing or billing-terms change&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule that makes the card work: a generated definition stays &lt;code&gt;source: generated, owner: none&lt;/code&gt; and is excluded from agent answers until someone signs it. Generation drafts the card. A person turns it into a record.&lt;/p&gt;

&lt;p&gt;Any ontology product should pass four checks before purchase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can it show who approved each definition, and when?&lt;/li&gt;
&lt;li&gt;Does each definition carry a test question with a known answer?&lt;/li&gt;
&lt;li&gt;Does the agent refuse outside the defined scope, and is each refusal logged?&lt;/li&gt;
&lt;li&gt;Can the definitions be exported if you leave the platform?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cards cost meetings, and some teams shouldn't pay
&lt;/h2&gt;

&lt;p&gt;Every signed card costs an owner's time. As an illustrative figure, 40 terms at 30 minutes each comes to 20 hours of a senior person's calendar before any re-signs. A small team where one analyst &lt;em&gt;is&lt;/em&gt; the agreement doesn't need cards. That person's head is the ontology of record. A domain whose definitions change weekly will drown in re-signs. Those teams should keep agents in exploration mode and keep their output out of KPIs entirely.&lt;/p&gt;

&lt;p&gt;The ontology worth budgeting for is the agreement, not the graph. When an executive asks for net revenue by sales rep before the 2025 territory change, the right answer is the first line under &lt;code&gt;must_refuse&lt;/code&gt;, and the controller's name next to it.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>semanticlayer</category>
      <category>datagovernance</category>
      <category>ontology</category>
    </item>
    <item>
      <title>Your $200K FDE Quarter Ends. Nobody Is Budgeted to Own the Agent in Quarter Two.</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:31:35 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/your-200k-fde-quarter-ends-nobody-is-budgeted-to-own-the-agent-in-quarter-two-12g4</link>
      <guid>https://dev.to/michaeltuszynski/your-200k-fde-quarter-ends-nobody-is-budgeted-to-own-the-agent-in-quarter-two-12g4</guid>
      <description>&lt;p&gt;Gartner &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-09-29-gartner-predicts-70-percent-of-enterprises-will-abandon-agentic-ai-built-by-vendor-forward-deployed-engineering-by-2028" rel="noopener noreferrer"&gt;forecasts that by 2028, 70% of enterprises will abandon agentic AI built by vendor forward-deployed engineering&lt;/a&gt;, "trapped by soaring costs and unable to evolve it on their own." Gartner publishes it as a forecast. This post argues the mechanism behind it shows up in the budget. Gartner's own fee estimate is quoted per quarter per use case, which reads as a build price that runs until handoff. Nothing in the estimate as reported covers the quarter after it, so unless the contract says otherwise, nobody is named to own the agent or funded to keep it working, and the first forced change lands on a team that never built it.&lt;/p&gt;

&lt;p&gt;Call that period quarter two: the first quarter the agent runs without the people who wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $200K quarter is the floor, and it's priced as a build
&lt;/h2&gt;

&lt;p&gt;Gartner estimates, &lt;a href="https://www.ciodive.com/news/microsoft-25b-embed-engineers/824392/" rel="noopener noreferrer"&gt;reported by CIO Dive&lt;/a&gt;, suggest FDE consulting fees before platform and integration costs could range between $200,000 and $400,000 quarterly per use case. So $200K is the bottom of the range, and it covers consulting fees alone. Platform and integration costs sit outside it.&lt;/p&gt;

&lt;p&gt;In the engagement this post has in mind, a fee priced that way points at one milestone: the engineers ship, the acceptance demo passes, the handoff deck goes out, and then they leave.&lt;/p&gt;

&lt;p&gt;The Register's &lt;a href="https://www.theregister.com/ai-and-ml/2026/09/30/7-in-10-enterprises-expected-to-abandon-vendor-built-agentic-ai-by-2028/5300021" rel="noopener noreferrer"&gt;summary of the report&lt;/a&gt; names the failure: engagements fail "when customers do not acquire the knowledge and control needed to maintain and develop the resulting systems after the vendor leaves." Gartner's own &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-09-29-gartner-predicts-70-percent-of-enterprises-will-abandon-agentic-ai-built-by-vendor-forward-deployed-engineering-by-2028" rel="noopener noreferrer"&gt;Phase 1 advice&lt;/a&gt; says to "identify an executive sponsor who is accountable for business outcomes, not simply the implementation budget." An implementation budget ends at handoff. A business outcome doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first forced change already has a date
&lt;/h2&gt;

&lt;p&gt;The vendor leaving doesn't freeze the agent's dependencies. Anthropic's &lt;a href="https://platform.claude.com/docs/en/about-claude/model-deprecations" rel="noopener noreferrer"&gt;model deprecations page&lt;/a&gt; commits to "at least 60 days' notice before model retirement for publicly released models." That's a reasonable, published commitment. It's also a calendar someone has to read.&lt;/p&gt;

&lt;p&gt;Take an illustrative engagement: two quarters, April through September 2026, with the agent built on &lt;code&gt;claude-sonnet-4-5-20250929&lt;/code&gt;. Per the same &lt;a href="https://platform.claude.com/docs/en/about-claude/model-deprecations" rel="noopener noreferrer"&gt;deprecations table&lt;/a&gt;, that model was deprecated on September 30, 2026, with a tentative retirement of November 30, 2026. Sixty-one days. The recommended replacement is &lt;code&gt;claude-sonnet-5-5&lt;/code&gt;. Requests to a retired model fail.&lt;/p&gt;

&lt;p&gt;So the deprecation notice arrives in handoff week, and the retirement falls inside quarter two. The work is ordinary: swap the model ID, review prompts tuned against the old model's behavior, re-run the acceptance evals, compare outputs, redeploy. Nothing exotic. All of it needs credentials, the eval suite and a person whose job it is. Here is what that entry looks like when someone owns it (illustrative):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;model_calendar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-5-20250929&lt;/span&gt;
    &lt;span class="na"&gt;deprecated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-09-30&lt;/span&gt;
    &lt;span class="na"&gt;retires&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-11-30&lt;/span&gt;        &lt;span class="c1"&gt;# tentative, per provider page&lt;/span&gt;
    &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-5-5&lt;/span&gt;
    &lt;span class="na"&gt;migration_owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;named&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;person,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;team&amp;gt;"&lt;/span&gt;
    &lt;span class="na"&gt;evals_rerun_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-11-09&lt;/span&gt;
    &lt;span class="na"&gt;last_reviewed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-10-01&lt;/span&gt;  &lt;span class="c1"&gt;# reviewed monthly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A schema change in a source system or an eval score that drifts follows the same pattern, usually with less warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  In illustrative math, the ownership line costs a fraction of one engagement quarter
&lt;/h2&gt;

&lt;p&gt;Keyhole Software, a consultancy that sells builds, &lt;a href="https://keyholesoftware.com/agentic-ai-development-cost-2026/" rel="noopener noreferrer"&gt;estimates&lt;/a&gt; that "annual run and maintenance costs then add 15% to 25% of the build price every year." It also says the maintenance budget "belongs in the original statement of work as a percentage, not a change order filed after the first incident." That range describes Keyhole's own build prices, not Gartner's FDE consulting fees. Applying the percentage to a $400K fee-only build, as below, is this post's illustrative transfer, not a sourced figure.&lt;/p&gt;

&lt;p&gt;Illustrative arithmetic, at the bottom of Gartner's fee range:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build, two quarters at $200K, fees only: $400,000&lt;/li&gt;
&lt;li&gt;Run cost at 15% to 25% a year: $60,000 to $100,000&lt;/li&gt;
&lt;li&gt;Per quarter: $15,000 to $25,000&lt;/li&gt;
&lt;li&gt;As a share of one $200K engagement quarter: 7.5% to 12.5%&lt;/li&gt;
&lt;li&gt;Cost of abandoning instead: the $400,000, plus the platform and integration spend Gartner's range leaves out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On these numbers the ownership line is the cheap part. It's also a part the fee estimate, as reported, says nothing about. And it has a cost the dollar figure hides: the owner is an engineer whose hours come out of some other roadmap, and the sponsor has to defend a budget line for a system that already "shipped."&lt;/p&gt;

&lt;h2&gt;
  
  
  The ownership sheet gets signed before the SOW
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.mpt.solutions/fde-pods-have-a-strategist-but-no-grader/" rel="noopener noreferrer"&gt;grader post&lt;/a&gt; priced checking the work during the build. The sheet below covers who owns it after the vendor goes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;What gets written down&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;One named internal person&lt;/td&gt;
&lt;td&gt;Not a team, and not the vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Budget&lt;/td&gt;
&lt;td&gt;Run cost as a percent of build&lt;/td&gt;
&lt;td&gt;In the SOW, not a change order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model calendar&lt;/td&gt;
&lt;td&gt;Every model ID the agent calls, with its published retirement date&lt;/td&gt;
&lt;td&gt;Reviewed monthly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evals&lt;/td&gt;
&lt;td&gt;The acceptance suite, handed over&lt;/td&gt;
&lt;td&gt;Runnable by the owner without the vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-call&lt;/td&gt;
&lt;td&gt;Who gets paged&lt;/td&gt;
&lt;td&gt;Plus what they're allowed to change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kill criteria&lt;/td&gt;
&lt;td&gt;The condition under which the sponsor turns it off&lt;/td&gt;
&lt;td&gt;Signed by the sponsor (the &lt;a href="https://www.mpt.solutions/gartner-40-percent-agentic-ai-canceled-zombie-projects/" rel="noopener noreferrer"&gt;kill-sheet post&lt;/a&gt; has a five-field version with a named cancel authority)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The week the vendor leaves, five things should be true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The owner can redeploy the agent without vendor credentials.&lt;/li&gt;
&lt;li&gt;The owner has run the full eval suite once, alone.&lt;/li&gt;
&lt;li&gt;The model calendar lists every model ID in production.&lt;/li&gt;
&lt;li&gt;The first migration has a date on it.&lt;/li&gt;
&lt;li&gt;The sponsor has signed the kill criteria.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of the five is false, the exit plan isn't finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the vendor buys more of the same problem
&lt;/h2&gt;

&lt;p&gt;The obvious objection: keep the vendor on for run and support. Gartner's &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-09-29-gartner-predicts-70-percent-of-enterprises-will-abandon-agentic-ai-built-by-vendor-forward-deployed-engineering-by-2028" rel="noopener noreferrer"&gt;Phase 3&lt;/a&gt;, titled "Exit and Prove Independence," rejects that in plain words: "Execute the exit plan established at the start rather than extending the engagement because internal teams are not ready." The mechanism sits in the forecast's own phrasing. An extension at FDE rates is the "soaring costs" half. And while the vendor carries the pager, the client's team has less reason to learn the system, which feeds the "unable to evolve it" half. Extension compounds the gap it was meant to cover.&lt;/p&gt;

&lt;p&gt;The second objection: the vendor will fold the custom work into its product, so there's nothing to own. Gartner &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-09-29-gartner-predicts-70-percent-of-enterprises-will-abandon-agentic-ai-built-by-vendor-forward-deployed-engineering-by-2028" rel="noopener noreferrer"&gt;predicts&lt;/a&gt; that through 2028, less than 20% of FDE engagements will turn recurring customer needs into capabilities in the vendor's core product. If that forecast holds, more than 80% of engagements won't turn recurring needs into product capabilities, and whatever they built still has to be kept working by someone outside the vendor's product team. Who holds the IP depends on the contract. Either way, the system keeps running on the client's side, and someone there has to keep it working.&lt;/p&gt;

&lt;p&gt;The third: agents are cheap to keep running. The token bill may well be small. The scheduled work isn't optional. A 60-day retirement notice, an eval re-run, an upstream API that changes its response shape: each arrives on someone else's calendar. Cheap inference with no owner still breaks on retirement day, tentatively November 30.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pilot built to die needs only the last row
&lt;/h2&gt;

&lt;p&gt;The sheet doesn't transfer everywhere. A pilot designed to be killed at handoff needs no quarter-two line. It needs kill criteria and a date, and nothing else on the sheet applies.&lt;/p&gt;

&lt;p&gt;A client with no internal engineering can't hold the owner seat at all. Naming a project manager as owner of a system they can't redeploy is theater. The honest answer there is a priced managed service with an exit clause, chosen on purpose. What fails is the version that gets drifted into, one renewal at a time, at FDE rates.&lt;/p&gt;

&lt;p&gt;For everyone else, the unowned quarter is the mechanism this post argues sits behind Gartner's 70% forecast, and the fix is one line signed before the engagement starts. For the illustrative summer build, the first entry on that sheet is already known: &lt;code&gt;claude-sonnet-4-5-20250929&lt;/code&gt;, tentatively retires November 30, 2026, owner: ________.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>enterpriseai</category>
      <category>aigovernance</category>
      <category>aiengineering</category>
    </item>
    <item>
      <title>The Lint Ran Every Week. The Dead-Link Count Never Went Down.</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:23:48 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/the-lint-ran-every-week-the-dead-link-count-never-went-down-22m1</link>
      <guid>https://dev.to/michaeltuszynski/the-lint-ran-every-week-the-dead-link-count-never-went-down-22m1</guid>
      <description>&lt;p&gt;In August I wrote that &lt;a href="https://www.mpt.solutions/karpathys-llm-wiki-works-heres-what-broke-when-i-ran-it-at-real-scale/" rel="noopener noreferrer"&gt;Karpathy's LLM wiki works&lt;/a&gt;, and that a weekly lint pass "recovers most of" the dead links and orphan pages a wiki piles up. That was wrong. The lint ran every week after that, and it recovered nothing.&lt;/p&gt;

&lt;p&gt;Here is the log. My vault runs a scheduled checker every Sunday that counts pages, dead wikilinks and orphans. The last column strips out matches that were never links, which I'll get to below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Week&lt;/th&gt;
&lt;th&gt;Pages scanned&lt;/th&gt;
&lt;th&gt;Dead wikilinks (raw)&lt;/th&gt;
&lt;th&gt;Orphan pages&lt;/th&gt;
&lt;th&gt;Actually broken&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aug 2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 9&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 16&lt;/td&gt;
&lt;td&gt;261&lt;/td&gt;
&lt;td&gt;116&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 23&lt;/td&gt;
&lt;td&gt;264&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 30&lt;/td&gt;
&lt;td&gt;264&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 6&lt;/td&gt;
&lt;td&gt;267&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 13&lt;/td&gt;
&lt;td&gt;283&lt;/td&gt;
&lt;td&gt;153&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;136&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 20&lt;/td&gt;
&lt;td&gt;293&lt;/td&gt;
&lt;td&gt;153&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;139&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 27&lt;/td&gt;
&lt;td&gt;303&lt;/td&gt;
&lt;td&gt;155&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;141&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 4&lt;/td&gt;
&lt;td&gt;317&lt;/td&gt;
&lt;td&gt;169&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 11&lt;/td&gt;
&lt;td&gt;327&lt;/td&gt;
&lt;td&gt;170&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 18&lt;/td&gt;
&lt;td&gt;334&lt;/td&gt;
&lt;td&gt;170&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first two rows are a separate embarrassment. The checker was pointed at a folder I had retired in a migration, so it scanned zero pages and reported a clean wiki. The post claiming lint works went out on August 13, four days after the last zero-page run and three days before the first real one.&lt;/p&gt;

&lt;p&gt;From August 16, when it scanned the real vault, to October 18, pages grew 28% and actually broken links grew 34%. That's roughly 0.42 per page at the start and 0.44 at the end: the rate held steady. What never happened is the thing lint is supposed to cause. Across ten weekly runs, the broken-link count never went down once, and orphans ended at 124, up from 117. Detection ran on schedule every week. Nobody repaired anything.&lt;/p&gt;

&lt;p&gt;So the missing line item in Karpathy's pattern isn't the lint. Lint is cheap. It's the repair, and the repair turns out to be mostly decisions a script can't make.&lt;/p&gt;

&lt;h2&gt;
  
  
  A repair script could fix none of 185 on its own
&lt;/h2&gt;

&lt;p&gt;In September I added a repair tool. It finds every &lt;code&gt;[[link]]&lt;/code&gt; whose target note doesn't exist and tries to match it to a real note: first case-insensitive, then fuzzy. It rewrites only the safe cases, where the link differs from a real filename by capitalization or spacing.&lt;/p&gt;

&lt;p&gt;I ran it today in dry-run mode. It took 2.7 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vault-repair: 185 dead wikilinks — 0 confident fixes, 4 fuzzy suggestions, 181 unresolved
  ~ 200-knowledge/context/recipe-context.md: [[recipes/classic-meatloaf]] ≈ [[classic-meatloaf]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero safe fixes. Four suggestions, all the same mistake: a recipe linked by folder path instead of note name. The other 181 have no candidate at all.&lt;/p&gt;

&lt;p&gt;(The weekly checker said 170 on Sunday; the repair script says 185 today. Different tools on different days, and I haven't diffed the two lists, so I can't tell you yet which explains the gap.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Most dead links are names the agent was told to write
&lt;/h2&gt;

&lt;p&gt;I sorted all 185 by what they point at.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the link is&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A name or topic with no note behind it, mostly people, companies and services (41 of them in archived daily logs)&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A CONFIRM placeholder my draft pipeline leaves in old blog drafts&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Example syntax inside a third-party README I saved&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A folder-path link: 4 to recipes that exist, 3 to index pages that don't&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The big row is the interesting one. My operating file tells the agent to wikilink every person, company and service the first time it comes up, so a later page about the same entity connects back. That rule works. It's also a dead-link generator. Every new name gets brackets the moment it's written, and nothing creates the card behind it.&lt;/p&gt;

&lt;p&gt;Schwab is linked six times. Plaid three. Most of the rest are names that came up once or twice in a daily log, each wrapped in brackets and pointing at nothing.&lt;/p&gt;

&lt;p&gt;Karpathy's &lt;a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f" rel="noopener noreferrer"&gt;original gist&lt;/a&gt; names this exact finding. His lint step looks for "important concepts mentioned but lacking their own page." Finding them was never the hard part. Each one is a question only the wiki's owner can answer: does this name deserve a card, or should the brackets go? A name mentioned once in a call note probably doesn't need a page. An institution that holds your money does.&lt;/p&gt;

&lt;p&gt;The bottom three rows are noise, and they say something too. The repair script's link pattern matches anything inside double brackets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;LINK_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So my content pipeline's own review markers count as broken links, and so do the &lt;code&gt;[[wikilinks]]&lt;/code&gt; examples in the READMEs of &lt;a href="https://github.com/nvk/llm-wiki#readme" rel="noopener noreferrer"&gt;nvk/llm-wiki&lt;/a&gt; and &lt;a href="https://github.com/Ar9av/obsidian-wiki#readme" rel="noopener noreferrer"&gt;Ar9av/obsidian-wiki&lt;/a&gt; that I saved for reference. A dead-link count is a count of bracket pairs, not of broken knowledge. 24 of the 185 were never links in the first place, and the weekly checker counts them too. On August 16 it counted 7 of those. By October 18 it counted 24, because my draft pipeline had started leaving CONFIRM markers in saved drafts. That's why the raw column in the table above grew faster than the real one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-created stubs move the number, not the problem
&lt;/h2&gt;

&lt;p&gt;The obvious fix: have the agent create a stub card whenever it writes a new link. The dead-link count goes to zero overnight.&lt;/p&gt;

&lt;p&gt;That trades one lint finding for another. A stub with a title and no content is an orphan in everything but name. Nothing useful links into it, and nothing in it is worth reading. I already carry 124 orphans. Stubs would add up to 154 more and make the checker look healthier while the wiki gets no more useful.&lt;/p&gt;

&lt;p&gt;It also defeats the point of the card. An entity card is worth having because it's the one place a fact about that entity lives. A stub promises that place exists and then holds nothing. The next session that reads it learns the name and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this stops mattering
&lt;/h2&gt;

&lt;p&gt;A small wiki in one domain probably won't hit this. My guess, and it's only a guess, is that it stays manageable while there are few enough names to card each one as it arrives. Mine broke down once it spanned several domains and the daily logs mentioned people faster than anyone curated them.&lt;/p&gt;

&lt;p&gt;A team with someone whose job is curating the wiki won't hit it either. That person is the missing line item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget the decisions, not the lint
&lt;/h2&gt;

&lt;p&gt;If you're rolling Karpathy's pattern out to a team, the lint is the part you can automate and forget. Count the other thing: how many new names enter the wiki each week, and who decides whether each one gets a card.&lt;/p&gt;

&lt;p&gt;My count today is 154 open decisions, built up over ten weekly lint runs nobody acted on. The script fixed none and suggested 4. The rest need an owner, and the weekly report is only useful if someone reads it on Monday.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>knowledgemanagement</category>
      <category>llmwiki</category>
      <category>aiengineering</category>
    </item>
    <item>
      <title>Every FDE Pod Has a Strategist. None Has a Grader.</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:31:07 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/every-fde-pod-has-a-strategist-none-has-a-grader-1g3f</link>
      <guid>https://dev.to/michaeltuszynski/every-fde-pod-has-a-strategist-none-has-a-grader-1g3f</guid>
      <description>&lt;p&gt;Gartner forecasts that &lt;a href="https://www.theregister.com/ai-and-ml/2026/09/30/7-in-10-enterprises-expected-to-abandon-vendor-built-agentic-ai-by-2028/5300021" rel="noopener noreferrer"&gt;70% of enterprises will abandon agentic AI built by vendor forward-deployed engineers by 2028&lt;/a&gt;, in a September 29 release reported the next day by The Register and Channel Dive. The firm calls the pattern "FDE washing." Its analyst &lt;a href="https://www.channeldive.com/news/can-forward-deployed-engineers-fdes-fix-ai-gartner/831816/" rel="noopener noreferrer"&gt;counts about 2,000 active FDEs against roughly four times that demand&lt;/a&gt;, and Gartner estimates that &lt;a href="https://www.channeldive.com/news/can-forward-deployed-engineers-fdes-fix-ai-gartner/831816/" rel="noopener noreferrer"&gt;FDE consulting fees alone could top $200,000 per quarter per use case&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Gartner's stated mechanism is dependency and exit: clients end up unable to run or leave what the vendor built. How the pod is designed is not part of its analysis. This post argues the dependency has a structural cause visible in the published FDE pods. The pods that name their roles each have a strategist, and none has a grader. No seat checks the work against criteria the client agreed to, held by someone who had no hand in building it. That link is this post's argument. Gartner doesn't make it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pod is already the standard delivery model
&lt;/h2&gt;

&lt;p&gt;The lone engineer embedded at a client is mostly a recruiting image. The published delivery unit is a pod, and the pods look alike.&lt;/p&gt;

&lt;p&gt;Palantir pairs the Delta (the FDE) with an Echo (a deployment strategist) and Devs on the product side. One alumnus describes FDEs &lt;a href="https://nabeelqu.substack.com/p/reflections-on-palantir" rel="noopener noreferrer"&gt;going on site at the customer three to four days a week&lt;/a&gt;. Deloitte's lead-FDE postings describe &lt;a href="https://apply.deloitte.com/en_US/careers/JobDetail/Lead-Anthropic-Forward-Deployed-Engineer-GPS/353534" rel="noopener noreferrer"&gt;pods of two to five onshore-anchored, offshore-supported engineers&lt;/a&gt;. Thoughtworks' principal-FDE posting describes a pod of one product strategist, one experience designer and two forward-deployed engineers. AWS's billion-dollar FDE organization deploys &lt;a href="https://thenextweb.com/news/aws-1bn-forward-deployed-engineers-enterprise-ai" rel="noopener noreferrer"&gt;pods of five or six people, embedded inside a single customer at a time&lt;/a&gt;. Anthropic's own postings pair FDEs with a technical deployment lead who won't write production code and owns the roadmap the engineers execute against. The shape repeats: one or two engineers, a lead who owns scope and the executive relationship, sometimes a designer, and offshore or platform support behind them. Postings change, so the specifics will drift. The shape hasn't.&lt;/p&gt;

&lt;p&gt;The supply side is about to scale this template. Anthropic's &lt;a href="https://www.anthropic.com/news/claude-frontier-academy" rel="noopener noreferrer"&gt;Claude Frontier Academy&lt;/a&gt; commits $100M to train 10,000 "Frontier Deployed Engineers" by the end of 2027 at partners including Accenture, Deloitte, McKinsey and Morgan Stanley. Accenture's statement says those FDEs will work "inside client workflows as part of multidisciplinary teams." If the partners follow their own postings, those teams will look like the ones above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every seat in the pod helped build the thing
&lt;/h2&gt;

&lt;p&gt;Read those pod descriptions for a quality seat and you won't find one. None of them names a QA seat, an acceptance seat or a grading seat. The closest is a mention of "quality gates" in Deloitte's posting.&lt;/p&gt;

&lt;p&gt;The strategist seat is already standard. Palantir calls it the Echo. Other firms call it a deployment lead, a product strategist or a PM. Pods don't need anyone to invent it. And every seat in every pod is a builder. Engineers write the code. The strategist sequences the roadmap. The designer shapes the workflow. Platform and offshore support keep it running. Each person present has a stake in the result passing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification gets dropped because nobody is waiting on it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.mpt.solutions/forward-deployed-engineer-four-different-things/" rel="noopener noreferrer"&gt;earlier post on the FDE title&lt;/a&gt; broke the role into separate jobs. Here's the version that matters for pods. The role has four jobs: build the system, redesign the process around it, run the client relationship, and verify the result. A pod splits the first three across seats. The fourth goes unassigned.&lt;/p&gt;

&lt;p&gt;It gets dropped inside a pod for the same reason a lone engineer drops it. No one is waiting on the grade. Shipping has a demo date, and the executive relationship has a steering meeting. A verification report has no audience until something breaks. Everyone in the room also wants the work to pass, including the vendor's lead, whose quarterly fee renews on the impression that it did.&lt;/p&gt;

&lt;p&gt;Follow that to the client's side. Without an independent grade against agreed criteria, the client can't tell what it bought. Without a sponsor who has authority to stop the work (the one seat &lt;a href="https://www.mpt.solutions/gartner-40-percent-agentic-ai-canceled-zombie-projects/" rel="noopener noreferrer"&gt;the kill sheet from the earlier post&lt;/a&gt; insists on), the client can't leave without losing whatever value is in there. It can't separate the working parts from the broken parts, so it keeps paying the only people who know. That is dependency, and that's what Gartner is forecasting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing seat grades and does nothing else
&lt;/h2&gt;

&lt;p&gt;Call it the acceptance owner. The definition is narrow on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Co-writes testable acceptance criteria with the client sponsor before work starts&lt;/li&gt;
&lt;li&gt;Grades the outcome against those criteria after every change&lt;/li&gt;
&lt;li&gt;Publishes the grade to the sponsor, not to the pod first&lt;/li&gt;
&lt;li&gt;Had no hand in building the work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The seat can sit on either side of the contract. A mature client's own QA function can hold it. A third party can. A vendor employee from outside the delivery org can, if the client accepts that. What disqualifies someone is having built the work. Which side signs their paycheck matters less.&lt;/p&gt;

&lt;p&gt;Here's the cadence relative to the pod:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Week&lt;/th&gt;
&lt;th&gt;Pod&lt;/th&gt;
&lt;th&gt;Acceptance owner&lt;/th&gt;
&lt;th&gt;Sponsor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Scopes the use case&lt;/td&gt;
&lt;td&gt;Co-writes criteria with sponsor; each one testable pass/fail&lt;/td&gt;
&lt;td&gt;Signs criteria and stop conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-5&lt;/td&gt;
&lt;td&gt;Builds, demos weekly&lt;/td&gt;
&lt;td&gt;Re-runs the criteria after each merged change; logs pass/fail&lt;/td&gt;
&lt;td&gt;Reads the grade before the demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Mid-sprint review&lt;/td&gt;
&lt;td&gt;Publishes trend: criteria passing, failing, untested&lt;/td&gt;
&lt;td&gt;Decides continue, re-scope or stop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7-12&lt;/td&gt;
&lt;td&gt;Builds, hardens&lt;/td&gt;
&lt;td&gt;Same loop; adds regression on previously passed criteria&lt;/td&gt;
&lt;td&gt;Reads the grade weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Quarter close, renewal&lt;/td&gt;
&lt;td&gt;Final grade against week-0 criteria, unchanged unless sponsor re-signed&lt;/td&gt;
&lt;td&gt;Renews or exits on the grade&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each seat also has one thing it must not do. The engineer doesn't grade their own work. The strategist can write down what done means but doesn't own the grade. The sponsor never hands the stop decision to someone else. The acceptance owner's rule is the least obvious one: don't suggest fixes. The moment the grader proposes the fix, the grader has helped build it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Owning the criteria doesn't make the grade independent
&lt;/h2&gt;

&lt;p&gt;The strongest objection is that the pod already has this. Palantir's Echo, or a deployment lead who owns the roadmap, writes down what done means.&lt;/p&gt;

&lt;p&gt;That person owns the criteria. They also own the scope, the sequencing and the executive relationship, and they answer for whether the engagement renews. When a criterion half-passes in week 11, the person who wrote the roadmap is also the person who explains to the sponsor why it's close enough. The objection fails on that mechanism. Authorship and grading sit in the same chair, and that chair is the one with the most riding on renewal.&lt;/p&gt;

&lt;p&gt;The second objection: the client's own QA team can do this. Often true. When it can, that team &lt;em&gt;is&lt;/em&gt; the seat, provided it didn't help build the system. The test is simple. If the client's QA engineers pair with the FDEs during the build, they've crossed over, and someone else has to grade.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fractional grader costs about 2.5% to 7.5% of a $200K quarter
&lt;/h2&gt;

&lt;p&gt;The acceptance owner doesn't need to be full time. The work is a criteria workshop up front, a re-run after each change, and a weekly report. That fits in roughly 0.1 to 0.2 of a seat.&lt;/p&gt;

&lt;p&gt;Illustrative pricing, using Gartner's $200,000-a-quarter estimate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pod fee: $200,000 per quarter for one use case&lt;/li&gt;
&lt;li&gt;Pod of four seats (two engineers, strategist, designer): $50,000 per seat per quarter&lt;/li&gt;
&lt;li&gt;Acceptance owner at 0.1 seat: $5,000 per quarter, or 2.5% of the fee&lt;/li&gt;
&lt;li&gt;At 0.15 seat: $7,500 per quarter, or 3.75%&lt;/li&gt;
&lt;li&gt;At 0.2 seat: $10,000 per quarter, or 5%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An experienced independent grader may bill above the pod's average seat. Double the rate and 0.15 of a seat costs $15,000 a quarter, or 7.5%.&lt;/p&gt;

&lt;p&gt;Now price it against abandonment. A use case run for four quarters at $200,000 costs $800,000. The grader at 0.15 seat costs $30,000 for the same year. If an independent grade ends a failing engagement just one quarter early, it saves $200,000. That covers the seat at $7,500 a quarter for about 26 quarters, more than six years.&lt;/p&gt;

&lt;p&gt;The cost of this advice goes beyond the invoice. A grader will fail work the pod considers done. Demos get awkward. Renewals get harder for the vendor. The pod loses some of its speed, because "shipped" now has to mean "passed."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the seat doesn't fit
&lt;/h2&gt;

&lt;p&gt;An 8-week proof of concept often can't carry any extra seat, fractional or not. The fix there is cheaper: write the criteria at week 0 and have the sponsor grade at week 8 personally.&lt;/p&gt;

&lt;p&gt;Clients who won't name a sponsor break the model entirely. A grade with no one empowered to act on it is a report nobody reads.&lt;/p&gt;

&lt;p&gt;The onshore-anchored, offshore-supported pod is the hardest case. On paper it's five engineers. In practice one engineer can end up alone at the client, with the rest of the pod a time zone away. That engineer is the whole on-site build team and the only witness to whether it works. A pod like that needs a grader most and is least likely to have budget for one.&lt;/p&gt;

&lt;p&gt;Go back to the criterion that half-passes in week 11. With an acceptance owner, it doesn't get explained in a steering meeting. It shows up in the weekly grade as a fail, with the week-0 wording next to it, and it shows up again in week 12. At week 13 the sponsor holds a sheet with every signed criterion marked pass or fail. That sheet is the seat every published pod is missing. If that line is still red, the sponsor decides whether the next $200,000 buys a fix or funds the exit.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>forwarddeployedengineers</category>
      <category>aigovernance</category>
      <category>enterpriseai</category>
    </item>
    <item>
      <title>30% More Code, Zero More Shipped Work: Where the Capacity Actually Went</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Mon, 12 Oct 2026 16:29:30 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/30-more-code-zero-more-shipped-work-where-the-capacity-actually-went-3nkg</link>
      <guid>https://dev.to/michaeltuszynski/30-more-code-zero-more-shipped-work-where-the-capacity-actually-went-3nkg</guid>
      <description>&lt;p&gt;A Harvard study of 718 firms found that coding agents produced 30% more lines of code and no detectable increase in finished work. Most coverage stops there and reads the zero as a verdict on the tools. The paper's own review data points somewhere else. My reading of that data, and it's my inference rather than the paper's stated finding, is that the extra output was real and the review stage absorbed it. That's an org-design failure, and you can look for it in your own pull request history this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The extra code piled up at the review stage
&lt;/h2&gt;

&lt;p&gt;Fiona Chen and James Stratton built &lt;a href="https://fion.ac/jellyfish.pdf" rel="noopener noreferrer"&gt;the study&lt;/a&gt; on Jellyfish data covering 718 firms and about 300 million work events from January 2021 to March 2026. Jellyfish sells engineering analytics. A finding that work stalls somewhere measurable isn't against its commercial interest, so weigh the source with that in mind. The analysis itself comes from academic economists, and the headline result is not the one an AI vendor would pick.&lt;/p&gt;

&lt;p&gt;After teams adopted coding agents, the activity numbers went up. Lines of code rose 30%. Commits rose 20%. Pull requests rose 23%.&lt;/p&gt;

&lt;p&gt;Jira issue and epic resolution showed no change the authors could distinguish from zero. Their estimate rules out an output gain above 12%.&lt;/p&gt;

&lt;p&gt;The review numbers explain the gap. In &lt;a href="https://fion.ac/jellyfish.pdf" rel="noopener noreferrer"&gt;the same paper&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR review time rose 49%.&lt;/li&gt;
&lt;li&gt;The share of PRs with changes requested nearly doubled.&lt;/li&gt;
&lt;li&gt;Comments per PR rose 35%.&lt;/li&gt;
&lt;li&gt;The share of workers doing review rose 14%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put the two lists side by side. More code went in at the top. Review time, the changes-requested rate and comments per PR all rose, even with 14% more people doing review. Resolved tickets came out the bottom flat. The paper reports these as separate effects and doesn't claim one caused the other. My inference from the pattern: the capacity didn't vanish. It turned into review load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review load grew faster than PR count
&lt;/h2&gt;

&lt;p&gt;The paper estimates each effect separately, so stacking them is my illustration, not a result from the paper. But stacking them shows why a 23% bump in PRs can stall a team.&lt;/p&gt;

&lt;p&gt;Take an illustrative 20-person team with 6 regular reviewers, 200 merged PRs a month, a 10-hour median review time, 4 comments per PR, and 15% of PRs sent back with changes requested. Apply the paper's effects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          before     after      change
PRs / month                  200       246       +23%
Review time (hours)         10.0      14.9       +49%
PR-hours awaiting review   2,000     3,665       +83%   (1.23 x 1.49)
Comments / PR                4.0       5.4       +35%
Total comments               800     1,328       +66%   (1.23 x 1.35)
Reviewers                    6.0      6.84       +14%
Comments per reviewer        133       194       +46%   (1.66 / 1.14)
Changes-requested rate       15%       30%       ~2x
PRs needing a second pass     30        74
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PR count rose 23%. PR-hours spent awaiting review rose 83%. That's elapsed time in the queue, not reviewer effort, but it's time finished work sits unshipped. Even with 14% more people reviewing, each reviewer handles roughly 46% more comment traffic. Every changes-requested PR goes back through the queue, and the illustrative team goes from 30 second passes a month to 74.&lt;/p&gt;

&lt;p&gt;That's the mechanism as I read it. Agents raised the rate at which work arrives at review. Nobody raised the rate at which review clears it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headcount didn't move, and that's the CFO's problem
&lt;/h2&gt;

&lt;p&gt;The paper's headcount results are where the budget conversation should start. Total headcount showed a precise zero effect, ruling out a decline above 2.9%. Engineering headcount showed a noisier zero, ruling out a decline above 13.8%.&lt;/p&gt;

&lt;p&gt;A CFO who funds agent licenses expecting to shrink the engineering line didn't get that in this data. My argument, which the paper doesn't test directly: what the money bought was a review backlog. Buying more seats pushes more work into a queue that's already behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure where your capacity went from PR data
&lt;/h2&gt;

&lt;p&gt;You don't need Jellyfish to check whether your team looks like the paper. The GitHub API has what you need. This script pulls merged PRs, their reviews and their timelines, and prints three numbers per month: time to first review, changes-requested rate, and reviewer share. Time to first review starts at the last ready-for-review event (or PR creation, if the PR never sat in draft) and stops at the first submitted review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt;

&lt;span class="n"&gt;REPO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. your-org/your-repo
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--paginate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--slurp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;review_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/issues/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/timeline?per_page=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
             &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ready_for_review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
                              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pulls?state=closed&amp;amp;per_page=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;merged_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;merged_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pulls/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/reviews?per_page=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;review_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;submitted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submitted_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reviews&lt;/span&gt;
                       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submitted_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submitted_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHANGES_REQUESTED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;people&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hours&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;   n/a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  prs=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_review_h=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;changes_requested=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewer_share=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few caveats. It makes two API calls per PR, so run it on one repo at a time or you'll hit rate limits. PRs merged with no review drop out of the timing median. Reviewer share here means reviewers divided by everyone active in the repo that month, not total headcount. And time to first review is my proxy. It isn't the paper's PR review time metric, which comes from Jellyfish's own definitions, so don't compare your hours to anything in the paper. Compare your percentage change across the rollout to the paper's 49%.&lt;/p&gt;

&lt;p&gt;Draw a line at your agent rollout month and read the pattern. If PR count and changes-requested rate both climbed while reviewer share stayed flat, you have the paper's queue. If reviewer share rose and time to first review still climbed, you added reviewers without shrinking the work each one sees. Pair this with the 30-day rework measure from &lt;a href="https://www.mpt.solutions/ai-coding-dashboard-counts-the-wrong-thing/" rel="noopener noreferrer"&gt;the earlier post on AI coding dashboards&lt;/a&gt;, because every fix below trades review time against escaped defects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix review capacity and merge policy before buying more seats
&lt;/h2&gt;

&lt;p&gt;Start by making review planned work. A reviewer rotation on the sprint board, with hours attached, stops review from being the thing people squeeze in after their "real" tickets. Those hours come out of feature work, and the roadmap will show it. That's the honest price of clearing the queue.&lt;/p&gt;

&lt;p&gt;Merge policy is the second lever. Tier the approval rules: changes in low-risk paths that pass tests merge on one approval, while auth, payments and data migrations keep two. You'll get more escaped defects in the low-risk tier. That's why the rework number has to sit next to the speed number on the same dashboard.&lt;/p&gt;

&lt;p&gt;Then shrink the unit of review. Cap diff size and tell your agents to split work into reviewable pieces. My argument here, not a measured result: a 400-line agent PR gets skimmed in a way that four 100-line PRs don't. The cost is more review events, each with its own context switch, plus tooling for stacked PRs.&lt;/p&gt;

&lt;p&gt;This doesn't transfer everywhere. On a four-person startup where review is a glance, the constraint is probably upstream: unclear tickets, slow product decisions. Agents there produce code faster against specs nobody agreed on, and no review fix helps. Jira resolution is also an imperfect output measure. Teams that adopt agents may start writing bigger issues, which would hide gains in the ticket count. The review numbers don't depend on how tickets are sized, though, and they moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Newer agents raise the arrival rate, not the review rate
&lt;/h2&gt;

&lt;p&gt;The strongest objection: the data ends in March 2026, and today's agents are better. Maybe the stall was a property of weaker tools.&lt;/p&gt;

&lt;p&gt;Look at where the stall happened. A better agent writes more code, and more of it is plausible. That raises the arrival rate at review. It does nothing for review throughput unless the agent's output needs fewer changes or the review itself gets automated.&lt;/p&gt;

&lt;p&gt;That can work, and the script above will tell you. If changes-requested rate falls after a model upgrade and 30-day rework doesn't rise, the newer agent is clearing the queue. If PR count jumps again and review time follows, you got the March 2026 result with a faster input.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hidden 12% gain still leaves most of the extra volume in the queue
&lt;/h2&gt;

&lt;p&gt;The second objection is statistical. "No detectable change" only rules out gains above 12%. A real gain of 5% or 10% could be sitting inside the confidence interval.&lt;/p&gt;

&lt;p&gt;Concede it. Then do the arithmetic. PRs rose 23%. At the 12% ceiling, PRs per resolved issue still rose by roughly 10% (1.23 / 1.12 = 1.098). The 49% jump in review time is measured directly, not inferred from a null. A modest hidden gain is the best case for this argument: the tools produced something, less of it reached done than went in, and the review stage holds the difference. A gain that small also caps the headcount math. It can't pay for the 13.8% engineering cut the paper already ruled out.&lt;/p&gt;

&lt;p&gt;So the zero isn't a verdict on the tools. It's what happens when output rises and review capacity doesn't. Go back to the illustrative 20-person team: agents gave it 46 extra PRs a month, and those PRs arrived carrying 1,665 extra PR-hours waiting in review and 44 extra second passes.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>codereview</category>
      <category>developerproductivity</category>
      <category>engineeringmanagement</category>
    </item>
    <item>
      <title>Gartner Says Over 40% of Agentic AI Projects Will Be Canceled. The Expensive Ones Are the Ones That Aren't.</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Sun, 11 Oct 2026 02:04:52 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/gartner-says-over-40-of-agentic-ai-projects-will-be-canceled-the-expensive-ones-are-the-ones-that-1l18</link>
      <guid>https://dev.to/michaeltuszynski/gartner-says-over-40-of-agentic-ai-projects-will-be-canceled-the-expensive-ones-are-the-ones-that-1l18</guid>
      <description>&lt;p&gt;A support-ticket triage agent goes live on a Monday with a 90-day pilot budget and a vague goal: "reduce agent handle time." On day 90 the numbers are mixed. Handle time is down on some queues and up on others, and three reviewers spend part of each week fixing the agent's routing mistakes. Nobody calls it a failure, because nobody wrote down what failure would look like. So the project keeps its funding, and it is still running a year later.&lt;/p&gt;

&lt;p&gt;That project won't appear in Gartner's cancellation count. It's the expensive outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cancellation is the cheap way for a pilot to end
&lt;/h2&gt;

&lt;p&gt;Gartner &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027" rel="noopener noreferrer"&gt;predicts over 40% of agentic AI projects will be canceled by the end of 2027&lt;/a&gt;, citing escalating costs, unclear business value, and inadequate risk controls. &lt;a href="https://www.reuters.com/business/over-40-agentic-ai-projects-will-be-scrapped-by-2027-gartner-says-2025-06-25/" rel="noopener noreferrer"&gt;Reuters ran it&lt;/a&gt; as a failure headline, and so did nearly everyone else.&lt;/p&gt;

&lt;p&gt;I read it the other way. A canceled project stops spending. The team moves to the next bet and the inference bill stops. If the cancellation comes at the end of a bounded pilot, the organization paid a known amount to learn something true.&lt;/p&gt;

&lt;p&gt;The projects that should worry a budget owner sit in the other 60%. Some of them are working. Some of them are zombies: pilots that never cleared a value bar and never got killed. They keep paying for inference, integration upkeep and human supervision, and they hold onto engineers who could be building something that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 40% is a forecast, and it only counts the failures someone admitted to
&lt;/h2&gt;

&lt;p&gt;Two cautions before anyone puts this number in a board deck.&lt;/p&gt;

&lt;p&gt;First, it's an analyst prediction about 2027, not a measurement. The survey data in the same press release comes from a &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027" rel="noopener noreferrer"&gt;January 2025 poll of 3,412 Gartner webinar attendees&lt;/a&gt;. In that poll, 19% reported significant agentic AI investment and 31% were waiting to see. People who sign up for a Gartner webinar on agents aren't a random sample of enterprises. Treat the 40% as an informed guess about direction.&lt;/p&gt;

&lt;p&gt;Second, the forecast counts cancellations, and cancellation requires a decision. A zombie never triggers one. As &lt;a href="https://www.forbes.com/sites/robertszczerba/2026/07/07/why-40-of-agentic-ai-projects-may-be-canceled-by-2027/" rel="noopener noreferrer"&gt;Forbes' read of the Gartner report&lt;/a&gt; puts it, the causes are management failures, not model failures. A management failure that keeps a project alive is the same failure as one that kills it late. It's just harder to see.&lt;/p&gt;

&lt;p&gt;So the 40% tells you nothing about how many of the surviving 60% deserve to survive. The one tool that answers that is a document written before the pilot starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The kill threshold has to exist before the first prompt ships
&lt;/h2&gt;

&lt;p&gt;My earlier post, "Most Agent Projects Die. The Survivors Share a Shape," covered why survivors survive. This one is about the artifact the survivors' sponsors write and the zombies' sponsors skip: a kill sheet, signed before the pilot starts.&lt;/p&gt;

&lt;p&gt;It has five fields. Here it is for the triage agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# kill-sheet.yaml: signed before pilot start, not edited after&lt;/span&gt;
&lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support-triage-agent&lt;/span&gt;
&lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;median_time_to_correct_queue&lt;/span&gt;   &lt;span class="c1"&gt;# one metric, not a dashboard&lt;/span&gt;
  &lt;span class="na"&gt;baseline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4.2h&lt;/span&gt;                        &lt;span class="c1"&gt;# measured over the 30 days before launch&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.5h&lt;/span&gt;                          &lt;span class="c1"&gt;# what "worked" means&lt;/span&gt;
  &lt;span class="na"&gt;measured_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-01&lt;/span&gt;               &lt;span class="c1"&gt;# day 90, a fixed date&lt;/span&gt;
&lt;span class="na"&gt;monthly_run_cost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;inference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3800&lt;/span&gt;
  &lt;span class="na"&gt;integration_upkeep&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;              &lt;span class="c1"&gt;# 0.25 engineer, loaded&lt;/span&gt;
  &lt;span class="na"&gt;human_review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6750&lt;/span&gt;                    &lt;span class="c1"&gt;# 3 reviewers x 30h x $75&lt;/span&gt;
  &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14550&lt;/span&gt;
&lt;span class="na"&gt;cost_ceiling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12000&lt;/span&gt;                     &lt;span class="c1"&gt;# above this, value has to be proven&lt;/span&gt;
&lt;span class="na"&gt;cancel_authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VP Support Operations&lt;/span&gt; &lt;span class="c1"&gt;# a name, not a committee&lt;/span&gt;
&lt;span class="na"&gt;extension_used&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format matters less than when it gets written. Every field in it is easy to fill in on day zero and very hard to fill in honestly on day 90, once people have spent a quarter on the project and the sponsor has told leadership it's going well.&lt;/p&gt;

&lt;p&gt;Some notes on the fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One metric.&lt;/strong&gt; If you give an agent five metrics, it will move one of them, and that one becomes the story. Choose the metric the business would pay for, and measure the baseline before launch. A baseline reconstructed after the fact always comes out flattering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The date is fixed.&lt;/strong&gt; "End of Q2" turns into "once the new model ships" turns into "after the reorg." Put a calendar date on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human review is part of run cost.&lt;/strong&gt; It's usually the largest line, and usually the one left out. In the sheet above, review time is 46% of monthly cost. Inference is 26%. If your sheet shows only the token bill, it understates run cost by a factor of three or four.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ceiling sits below current run cost.&lt;/strong&gt; That's deliberate. A pilot that costs more than the ceiling has to clear the metric target to continue. It doesn't get to argue it's "almost there."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cancel authority is one named person.&lt;/strong&gt; If killing the project needs a committee, it won't happen, because no committee member wants to own the decision.&lt;/p&gt;

&lt;p&gt;Expect the cancel-authority line to draw the most pushback. It's the only field that puts a person's name next to a possible failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  A zombie costs six times what an honest cancellation does
&lt;/h2&gt;

&lt;p&gt;When the triage agent misses its target on day 90, there are two outcomes to price.&lt;/p&gt;

&lt;p&gt;Cancel on day 90 and the cost is three months of run cost, $43,650. That's the tuition.&lt;/p&gt;

&lt;p&gt;Now say it stays alive for nine more months on "let's give it another quarter," and then another. The zombie bill has two parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zombie_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monthly_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;months_past_kill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;team_monthly_loaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;monthly_run&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;months_past_kill&lt;/span&gt;
    &lt;span class="c1"&gt;# Opportunity cost, floor estimate: the loaded cost of engineers who could
&lt;/span&gt;    &lt;span class="c1"&gt;# have been on the next-best project. Their real cost is that project's
&lt;/span&gt;    &lt;span class="c1"&gt;# value, which is usually higher and harder to defend in a spreadsheet.
&lt;/span&gt;    &lt;span class="n"&gt;opportunity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team_monthly_loaded&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;months_past_kill&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opportunity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;zombie_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14_550&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# {'run': 130950, 'opportunity': 144000, 'total': 274950}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The opportunity line assumes one full-time engineer's worth of attention ($16,000 a month loaded), split across the two people still tuning prompts and triaging complaints. It doesn't overlap with the 0.25 engineer already counted under integration upkeep. And it's a floor. The true cost of those engineers is whatever they would have shipped instead.&lt;/p&gt;

&lt;p&gt;That comes to $274,950 against $43,650, about 6.3 times the cost of the honest answer. Every month of delay adds $30,550. On top of that, the zombie keeps three reviewers in the review habit, and it makes the next agent proposal harder to fund, because leadership now associates "agents" with a project that never ended.&lt;/p&gt;

&lt;p&gt;None of these numbers are exotic. They're the triage agent's own costs from the sheet above, multiplied by the time nobody decided anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Late bloomers get one extension, written down in advance
&lt;/h2&gt;

&lt;p&gt;The strongest objection is real. Some value takes time to show up. A triage agent may get much better after six months of corrections feed its routing rules, or once the support team stops working around it. A strict day-90 date kills that project before it matures, and it rewards pilots that look good early over those that compound.&lt;/p&gt;

&lt;p&gt;That argument holds only when there's a mechanism behind the slow value, and a mechanism can be written down. So the kill sheet gets an extension rule, agreed at signing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One extension, no more than 60 days. A second extension is a cancellation.&lt;/li&gt;
&lt;li&gt;Before the extension starts, the sponsor names the mechanism ("routing accuracy improves as correction data accumulates") and a leading indicator that can be measured now ("misroute rate falls from 18% to 12% by day 150").&lt;/li&gt;
&lt;li&gt;The cost ceiling doesn't change. If the extension needs more money, it's a new project and gets a new sheet.&lt;/li&gt;
&lt;li&gt;The same named person signs the extension and owns the decision at its end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This answers the late-bloomer case on its own terms. A real late bloomer can say why it's slow and what will move first. A zombie can only say it needs more time. The rule lets the first through and stops the second, and "we're seeing promising signals" doesn't count as a mechanism.&lt;/p&gt;

&lt;p&gt;The cost of this discipline is that some projects that would have made it will die. A kill sheet will occasionally cancel a winner. That trade is fine. A false cancellation costs one pilot's tuition plus a restart. A false reprieve costs $30,550 a month with no end date.&lt;/p&gt;

&lt;p&gt;Go back to the triage agent on day 90. With the sheet, the VP of Support Operations reads one number, median time to the correct queue: 3.6 hours against a target of 2.5. She either signs one 60-day extension tied to a misroute rate or ends it. Without the sheet, the same 3.6 hours gets called "a 14% improvement" in a status deck, and nine months later the project has cost $274,950.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>aistrategy</category>
      <category>enterpriseai</category>
      <category>projectmanagement</category>
    </item>
    <item>
      <title>An AI Agent With a Crypto Wallet Is a Hot Wallet Anyone Can Talk To</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Tue, 06 Oct 2026 04:18:31 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/an-ai-agent-with-a-crypto-wallet-is-a-hot-wallet-anyone-can-talk-to-41kk</link>
      <guid>https://dev.to/michaeltuszynski/an-ai-agent-with-a-crypto-wallet-is-a-hot-wallet-anyone-can-talk-to-41kk</guid>
      <description>&lt;p&gt;An attacker sent Grok a free NFT, posted a message in Morse code, and walked away with &lt;a href="https://cointelegraph.com/magazine/grok-bankr-wallet-free-nft-prompt-injection" rel="noopener noreferrer"&gt;roughly 3 billion DRB tokens, about $174,000&lt;/a&gt;. Nobody stole a private key and no smart contract had a bug. One AI agent translated some dots and dashes in public. A second AI agent read that translation as an order and paid it.&lt;/p&gt;

&lt;p&gt;That incident from May 2026 is the best case against giving agents custody of crypto. An agent wallet is a hot wallet with a chat interface, and anyone who can get text in front of the agent can talk to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack turned a translation into a transfer
&lt;/h2&gt;

&lt;p&gt;The sequence is short, and every step matters.&lt;/p&gt;

&lt;p&gt;Grok had a wallet through Bankr, a bot that executes on-chain actions for users who tag it on X. That wallet was read-only. The attacker &lt;a href="https://cointelegraph.com/magazine/grok-bankr-wallet-free-nft-prompt-injection" rel="noopener noreferrer"&gt;airdropped a free Bankr Club NFT&lt;/a&gt; to it. Holding the NFT upgraded the wallet's permissions from read-only to transfer rights. So an inbound token, which anyone can send to any address, changed what the wallet was allowed to do.&lt;/p&gt;

&lt;p&gt;Next the attacker posted Morse code and asked Grok to translate it and tag Bankrbot. Grok did what it was asked. The decoded text told Bankrbot to send about 3B DRB to the attacker's address. Bankrbot saw a tagged instruction coming from an account with transfer rights, and &lt;a href="https://cointelegraph.com/magazine/grok-bankr-wallet-free-nft-prompt-injection" rel="noopener noreferrer"&gt;it executed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two design decisions failed here. Permissions could be granted by something the wallet received, not just by something its owner configured. And one agent's output (a translation, which is about as harmless as text gets) went straight into another agent's command parser. Neither is a model problem, and a smarter Grok would not have fixed either one. Grok translated correctly. That was the attack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The money came back because the attacker chose to return it
&lt;/h2&gt;

&lt;p&gt;The funds were returned, and coverage has treated that as a happy ending. It's the most alarming part of the story. The recovery came from the attacker's goodwill. No system clawed it back.&lt;/p&gt;

&lt;p&gt;On-chain transfers are final. You get no chargeback, no issuing bank to call, and no dispute window. Once Bankrbot signed the transaction, the only party who could undo it was the person holding the $174K. Allium's explainer on agent wallets describes &lt;a href="https://www.allium.so/blog/agent-wallet-how-ai-agents-hold-and-spend-onchain/" rel="noopener noreferrer"&gt;agents that hold and spend onchain&lt;/a&gt; as an emerging pattern. The pattern inherits that finality whether or not anyone designed for it.&lt;/p&gt;

&lt;p&gt;A human who gets phished on a bank wire has a few hours and a fraud department. An agent that gets prompt-injected on a public chain has however long a block takes to confirm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring fires after the money is gone
&lt;/h2&gt;

&lt;p&gt;Compliance and transaction-monitoring vendors were quick to cite the incident as a reason to buy detection: watch agent wallets, score anomalous transfers, alert on suspicious patterns. That's the strongest counterargument to my position. It says agent custody is fine once you have good enough eyes on it.&lt;/p&gt;

&lt;p&gt;Look at what monitoring would actually have done here. Bankrbot's transfer was one transaction, signed by an authorized wallet and sent to a valid address. A good anomaly model flags it as unusual, maybe within seconds of confirmation. The flag lands on a transfer that has already settled, on rails with no reversal path. You get an excellent incident report and a very short list of remediation options, most of which come down to asking the attacker nicely.&lt;/p&gt;

&lt;p&gt;Detection pays off when something downstream can act on it, like a card network holding settlement or a bank freezing an account. On a public chain nothing downstream can act. Every control that matters has to run before the signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capability limits have to be enforced in code, before signing
&lt;/h2&gt;

&lt;p&gt;Here's the control set I'd require before an agent touches anything that moves value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No standing transfer rights.&lt;/strong&gt; The default is read-only. Spending authority is granted per task and expires.&lt;/li&gt;
&lt;li&gt;Permissions come from the operator's configuration and nowhere else. No inbound token, NFT, message, or airdrop can change what the wallet may do.&lt;/li&gt;
&lt;li&gt;Destinations come from an allowlist. An address the operator hasn't approved can't receive funds, however the request is worded.&lt;/li&gt;
&lt;li&gt;Per-transaction and daily caps, enforced outside the model.&lt;/li&gt;
&lt;li&gt;Human approval above a threshold, set low enough that it actually triggers.&lt;/li&gt;
&lt;li&gt;Text an agent emits never gets parsed as a command by another agent. Translations, summaries, and replies are data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last rule is the one that would have stopped the Grok/Bankr chain on its own. Here's roughly what the gate looks like as code that sits between any agent and the signer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;operator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent_output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inbound_message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransferRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Origin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// set by the transport layer, never by the model&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Loaded from deploy-time config. No code path reads wallet holdings&lt;/span&gt;
&lt;span class="c1"&gt;// to decide permissions, so an airdropped NFT changes nothing.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xTreasury...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xVendorA...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;perTxCapUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dailyCapUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;humanApprovalAboveUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransferRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;spentTodayUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needs_human&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;operator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountUsd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;perTxCapUsd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spentTodayUsd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountUsd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dailyCapUsd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountUsd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;humanApprovalAboveUsd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needs_human&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the attack through it. The instruction to Bankrbot came from Grok's public reply, so &lt;code&gt;origin&lt;/code&gt; is &lt;code&gt;agent_output&lt;/code&gt;, and the first line returns &lt;code&gt;deny&lt;/code&gt;. Even if someone mislabeled the origin, the attacker's address isn't on the allowlist. Even if it were, $174K is roughly 700 times the per-transaction cap. The attack has to get past four independent checks, and none of them depend on the model reading the request correctly.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;origin&lt;/code&gt; field carries the most weight, and it's also the easiest to get wrong. It has to be stamped by whatever received the message. If the model fills it in, the attacker can fill it in too. In the agent pipelines I run, no tool that moves money or deletes data accepts arguments parsed from another model's free-text output.&lt;/p&gt;

&lt;p&gt;This gate has real costs. Allowlists make agents worse at the open-ended "pay whoever" tasks that make crypto agents appealing in demos. Human approval adds latency. Caps cut into throughput. That's the point. Those costs are the price of putting a limit on what a stranger's message can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Card rails can say no and take it back
&lt;/h2&gt;

&lt;p&gt;In June 2026 Mastercard announced Agent Pay for Machines. Going by Mastercard's own description, it authenticates the agent, enforces spending limits, and guarantees settlement on a network that already has dispute rails. That's Mastercard describing its own product, and nobody has shown publicly how those disputes play out when the buyer is a bot. Treat it as a design statement until someone reproduces it in production.&lt;/p&gt;

&lt;p&gt;Still, the design statement points the right way. Card networks were built on the assumption that some transactions will be wrong. Authorization can decline a purchase before it goes through, and chargebacks can reverse it afterward. The Grok/Bankr chain had neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart accounts get you limits but still no reversals
&lt;/h2&gt;

&lt;p&gt;The strongest technical pushback is that crypto can enforce limits too. Smart-contract wallets support session keys with spending caps, destination restrictions, and expiry, all enforced on-chain. That's true. A well-built smart account can run most of the gate above at the contract level, which is better than a policy check sitting in an off-chain service.&lt;/p&gt;

&lt;p&gt;But it only covers the "say no" half. A session key capped at $250 limits the damage, and the $250 is still gone. For agents that have to transact on-chain, such as protocol treasuries, on-chain market makers, or anything with no card-network equivalent, smart-account limits are the minimum, and the caps should be set on the assumption that every dollar under them is already lost. For everyone else, which means agents buying API credits, SaaS seats, or cloud capacity, there's no reason to choose a rail without reversals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cap is the most an attacker can take
&lt;/h2&gt;

&lt;p&gt;If an agent has to move money, put it on rails that can refuse a payment and reverse one. If it has to move crypto, assume the cap is what you'll lose, because a stranger with a free NFT and a Morse code chart can try to take it.&lt;/p&gt;

&lt;p&gt;That Morse code message was worth about $174,000, and the only control that worked was the attacker deciding to send the money back.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>promptinjection</category>
      <category>cryptosecurity</category>
      <category>aisecurity</category>
    </item>
    <item>
      <title>Your AI Coding Dashboard Counts the Wrong Thing</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Mon, 05 Oct 2026 19:23:59 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/your-ai-coding-dashboard-counts-the-wrong-thing-1mo9</link>
      <guid>https://dev.to/michaeltuszynski/your-ai-coding-dashboard-counts-the-wrong-thing-1mo9</guid>
      <description>&lt;p&gt;Open the vendor dashboard for any AI coding tool and you'll see acceptance rate near the top. It's the number managers quote in staff meetings. GitHub's own research puts it at &lt;a href="https://arxiv.org/abs/2306.15033" rel="noopener noreferrer"&gt;roughly 30% of suggestions accepted&lt;/a&gt;, and the &lt;a href="https://docs.github.com/en/rest/copilot/copilot-metrics" rel="noopener noreferrer"&gt;Copilot metrics API&lt;/a&gt; gives you suggestion and acceptance counts per editor, language, and day. So a team sees 30%, the line goes up and to the right, and the renewal gets signed.&lt;/p&gt;

&lt;p&gt;That number measures a keystroke. Finance is paying for software that stays shipped. The output measure that belongs on the dashboard is the one no vendor shows by default: &lt;strong&gt;of the lines your team accepted, how many were reverted or rewritten within 30 days?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptance rate was validated against how developers felt
&lt;/h2&gt;

&lt;p&gt;Acceptance rate didn't become the headline metric by accident. The &lt;a href="https://cacm.acm.org/research/measuring-github-copilots-impact-on-productivity/" rel="noopener noreferrer"&gt;GitHub study in Communications of the ACM&lt;/a&gt; tested several usage signals and found acceptance rate was the best predictor of &lt;em&gt;perceived&lt;/em&gt; productivity. Perceived is the important word there. Developers filled out surveys, and the survey scores tracked how often they hit Tab.&lt;/p&gt;

&lt;p&gt;That's a real finding. It's also a finding about sentiment. It never claims the accepted code survived review, passed the next sprint, or stayed out of the incident channel. And GitHub sells the tool. Treat the 30% as a vendor number about engagement, not about output.&lt;/p&gt;

&lt;p&gt;The newer tooling has the same blind spot one layer up. Dash0's AI Coding Insights lets you &lt;a href="https://www.dash0.com/blog/introducing-ai-coding-insights-stop-guessing-what-your-coding-agents-deliver" rel="noopener noreferrer"&gt;sort every Claude Code session by cost or error count&lt;/a&gt; and read the full transcript. That's useful for debugging an agent. But cost and errors-per-session both describe what went &lt;em&gt;into&lt;/em&gt; the work. Neither tells you what happened to the code after the session closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rework is where AI-assisted code shows its real cost
&lt;/h2&gt;

&lt;p&gt;The independent evidence points the same direction. GitClear analyzed 153 million changed lines and &lt;a href="https://www.gitclear.com/coding_on_copilot_data_shows_ais_downward_pressure_on_code_quality" rel="noopener noreferrer"&gt;projected code churn, meaning lines reverted or updated within two weeks of being written, to double in 2024 against its 2021 pre-AI baseline&lt;/a&gt;. GitClear sells code analytics, so discount it too. But the 2024 DORA report, which has no product to sell here, found that &lt;a href="https://dora.dev/research/2024/dora-report/" rel="noopener noreferrer"&gt;a 25% increase in AI adoption was associated with an estimated 7.2% drop in delivery stability&lt;/a&gt;. And METR's randomized trial found experienced open-source developers were &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;19% slower with AI tools while believing they were 20% faster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Put those next to the CACM result and you get the core problem. The metric vendors chose tracks how developers feel. The independent studies that measured outcomes found instability, rework, and a perception gap. Acceptance rate sits on the wrong side of that gap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.mpt.solutions/your-ai-coding-pilot-cost-80k-and-shipped-9-faster-heres-the-line-item-finance-m/" rel="noopener noreferrer"&gt;The $80K AI coding pilot post&lt;/a&gt; priced the senior review hours on the cost side. This is the matching hole on the output side. A suggestion that gets accepted on Tuesday and rewritten on the following Monday shows up on the dashboard as a win. Then it shows up again as new work.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can measure 30-day rework from git history you already have
&lt;/h2&gt;

&lt;p&gt;You don't need a vendor for this. Git already records who wrote every line and when it changed. The method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take every non-merge commit that is at least 30 days old.&lt;/li&gt;
&lt;li&gt;Tag it AI-assisted or not. Claude Code adds a &lt;code&gt;Co-Authored-By: Claude&lt;/code&gt; trailer to its commits by default, so the tag comes free.&lt;/li&gt;
&lt;li&gt;Count the lines each commit added.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git blame&lt;/code&gt; at the repo state 30 days later and count how many of those lines are still attributed to the original commit.&lt;/li&gt;
&lt;li&gt;Rework rate = 1 − (kept ÷ added), computed separately for each cohort.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;30-day rework: share of added lines gone or rewritten 30 days later.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;

&lt;span class="n"&gt;DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;git&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;added_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;git&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;show&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--numstat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--format=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;added&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# skip binaries
&lt;/span&gt;            &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;kept_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;git&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blame&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--line-porcelain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;   &lt;span class="c1"&gt;# file deleted or moved out of reach: counts as reworked
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rev_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;git&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;show&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--format=%ct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;git&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rev-list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--before=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;DAY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;120 days ago&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;git&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--no-merges&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--since=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--until=30 days ago&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--format=%H%x09%B%x00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;totals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
        &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cohort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;co-authored-by: claude&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rev_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;added_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;added&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
            &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;kept_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cohort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;added&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cohort&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;added&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; added, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kept&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;added&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; reworked by day 30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-w -M -C&lt;/code&gt; flags tell blame to ignore whitespace and follow lines that moved within or between files. Without them, a refactor that only reformats or relocates code gets counted as rework. The 30-day cutoff on &lt;code&gt;--until&lt;/code&gt; matters too. A commit from last week hasn't had its 30 days yet, and counting it would make every cohort look stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The objection: some rework is just healthy iteration
&lt;/h2&gt;

&lt;p&gt;Here's the best case against this metric. Code gets rewritten because teams learn. A spike gets thrown away on purpose. A first pass at an API gets reshaped once the second caller shows up. If you punish 30-day rework, you punish exploration, and engineers will learn to stop committing early.&lt;/p&gt;

&lt;p&gt;That objection has a specific mechanism, and the two-cohort design deals with it. Healthy iteration happens to human-written code too, in the same repo, under the same review culture, against the same product churn. Reading a single rework rate on its own tells you nothing. The signal is the &lt;em&gt;difference&lt;/em&gt; between the AI cohort and the human cohort over the same window. If both sit at 15%, AI isn't adding rework and the acceptance-rate story holds. If the AI cohort sits at 30% against a human 15%, AI-assisted code is being rewritten at twice the human rate, and the vendor dashboard counted every one of those lines once, as a win.&lt;/p&gt;

&lt;p&gt;Don't turn it into a per-engineer scorecard, either. It's a cohort measure for a renewal decision. Once it's used to grade people, people start gaming the trailers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this measurement breaks
&lt;/h2&gt;

&lt;p&gt;The script has real limits, and you should know them before you put the output in front of a CFO.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Squash merges erase the attribution.&lt;/strong&gt; If your team squash-merges PRs, the trailer survives only when it lands in the squash message. Many merge tools drop it. Check a few merged PRs by hand before trusting the cohort split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inline completions leave no trailer.&lt;/strong&gt; Copilot-style Tab completions get mixed into human commits without any marker. For those tools, compare whole-repo rework over the 90 days before rollout against the 90 days after. It's noisier, and it's still more honest than acceptance rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated files distort the count.&lt;/strong&gt; Lockfiles, snapshots, and codegen output get rewritten by machines on every run. Exclude them with a pathspec, or the cohort with more dependency bumps will look worse for reasons that have nothing to do with AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thirty days is a choice.&lt;/strong&gt; GitClear uses two weeks. Thirty days catches the rewrite that comes when the second caller shows up, which two weeks often misses. Pick a window, write it down, and don't change it between quarters.&lt;/p&gt;

&lt;p&gt;None of these limits rescues acceptance rate. Every one of them is a reason to measure carefully. None is a reason to go back to counting keystrokes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number to bring to the renewal meeting
&lt;/h2&gt;

&lt;p&gt;Go back to the dashboard. It says 30% of suggestions were accepted. That figure was validated against how developers felt, and the vendor selling the seats reports it.&lt;/p&gt;

&lt;p&gt;Ask the question finance should be asking: of that 30%, what fraction was still in main on day 30, and how does that compare to the code your engineers wrote without help? It comes down to two cohorts and one ratio, &lt;code&gt;kept ÷ added&lt;/code&gt;, and you can compute it from your own &lt;code&gt;git log&lt;/code&gt; this afternoon.&lt;/p&gt;

</description>
      <category>aiengineering</category>
      <category>developerproductivity</category>
      <category>engineeringmetrics</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Forward Deployed Engineer Names Four Different Things. One of Them Is a Software Product.</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Wed, 30 Sep 2026 21:50:36 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/forward-deployed-engineer-names-four-different-things-one-of-them-is-a-software-product-2li9</link>
      <guid>https://dev.to/michaeltuszynski/forward-deployed-engineer-names-four-different-things-one-of-them-is-a-software-product-2li9</guid>
      <description>&lt;h1&gt;
  
  
  Forward Deployed Engineer Names Four Different Things. One of Them Is a Software Product.
&lt;/h1&gt;

&lt;p&gt;The same three words now label a job at one frontier lab, a different job at the same lab, a billion-dollar organization at AWS, and a piece of software you can turn on inside Palantir Foundry.&lt;/p&gt;

&lt;p&gt;That last one deserves a second read. Palantir invented the role, and Palantir now ships &lt;a href="https://www.palantir.com/docs/foundry/ai-fde/overview" rel="noopener noreferrer"&gt;AI FDE&lt;/a&gt;, which the docs introduce as "the AI-powered forward deployed engineer," an "interactive agent that operates Foundry for you through conversational commands."&lt;/p&gt;

&lt;p&gt;So when two people say "we need an FDE on this," there is no longer any guarantee they are discussing the same category of thing, let alone the same scope of work. That's a scoping problem, and scoping problems are expensive for whoever is holding the statement of work when they surface.&lt;/p&gt;

&lt;p&gt;Eight years in presales left me with one reflex above all others: get the boundary of the work written down before anybody starts building. This title needs that treatment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The role began as an answer to an information problem
&lt;/h2&gt;

&lt;p&gt;Palantir popularized the term and had it in use for staff &lt;a href="https://en.wikipedia.org/wiki/Forward_Deployed_Engineer" rel="noopener noreferrer"&gt;by 2009&lt;/a&gt;. The phrasing is borrowed from the military's forward-basing language. What it solved was a problem about information rather than cost.&lt;/p&gt;

&lt;p&gt;You cannot write a specification for software that has to run in an environment you have never seen. Undocumented schemas. Compliance rules nobody wrote down. A workflow that lives in the heads of six people in one building. An architect who visits, produces a design, and flies home hasn't transferred knowledge — they've transferred a guess, and the guess fails in integration.&lt;/p&gt;

&lt;p&gt;The fix was to stop splitting the person who learns the environment from the person who writes the code. Same engineer, both jobs, on site, accountable through production.&lt;/p&gt;

&lt;p&gt;That original definition has four parts worth separating, because they behave very differently under automation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learn an environment that isn't documented.&lt;/li&gt;
&lt;li&gt;Decide what to build in it.&lt;/li&gt;
&lt;li&gt;Build it against the real data.&lt;/li&gt;
&lt;li&gt;Own it in production, or hand it over with the knowledge attached.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What the software version actually does is step three
&lt;/h2&gt;

&lt;p&gt;Read Palantir's capability list rather than the product name. AI FDE handles data integration and pipeline modification, data connection management, ontology editing, function writing and testing, read-only exploration, governance and access auditing, and React application development.&lt;/p&gt;

&lt;p&gt;Every item on that list is platform operation. Each one presupposes two things: that somebody has already decided what should be built, and that the target is Foundry. On day one of a real deployment, neither is true.&lt;/p&gt;

&lt;p&gt;To Palantir's credit, the documentation claims nothing more than that. There is no line on that page about replacing human FDEs — the product is scoped as a way to operate the platform faster, which is exactly what it appears to be. The overreach isn't in the docs. It's in reading the product name as a description of the role.&lt;/p&gt;

&lt;p&gt;Which makes step three the honest answer to "what can AI do here." Building against real data is getting dramatically faster. Steps one, two and four are close to untouched, because none of them are typing problems. Nobody has automated walking into a business you don't understand and working out which of the things people are complaining about is the one worth building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two job titles at one company already disagree
&lt;/h2&gt;

&lt;p&gt;Inside OpenAI the split between step three and the rest is visible in the org chart.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://openai.com/careers/forward-deployed-software-engineer-sf-san-francisco/" rel="noopener noreferrer"&gt;Forward Deployed Software Engineer&lt;/a&gt; posting wants 7+ years of full-stack work and describes coding "side-by-side" with customer teams "on their infrastructure," designing abstractions that later engagements reuse. That's steps three and four, with the original accountability intact.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://openai.com/careers/forward-deployed-engineer-(fde)-sf-san-francisco/" rel="noopener noreferrer"&gt;Forward Deployed Engineer&lt;/a&gt; posting owns "discovery, technical scoping, system design, build, and production rollout," plus sequencing, tradeoffs between scope and speed, playbook authoring, and field feedback that "changes product and model roadmaps." It contributes code "when progress or clarity depends on it." Conditional, by design.&lt;/p&gt;

&lt;p&gt;Both are reasonable jobs. They are not the same job, and the difference is which of the four steps the person is accountable for. A team that hires the second expecting the first will be surprised, and so will the customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The category got funded faster than it got defined
&lt;/h2&gt;

&lt;p&gt;Anthropic and OpenAI stood up forward-deployed groups in May. On June 30, AWS committed &lt;a href="https://www.aboutamazon.com/news/aws/aws-1-billion-forward-deployed-ai-engineers" rel="noopener noreferrer"&gt;$1 billion&lt;/a&gt; to a Forward Deployed Engineering organization it describes as "agentic-first," compressing "timelines from months to days," and designed so "customers are self-sufficient when a deployment ends." Two days later Microsoft announced Frontier Co. with $2.5 billion and 6,000 embedded engineers.&lt;/p&gt;

&lt;p&gt;Treat the months-to-days figure as a vendor ceiling rather than a planning number; no independent measurement of it exists yet. But the pattern is clear enough without it: billions of dollars of organizational design arrived in about eight weeks, and shared vocabulary takes longer than that.&lt;/p&gt;

&lt;p&gt;The market's own uncertainty shows up in what it pays. Two frontier labs are hiring for this title in the same city right now, and their posted base ranges start $95K apart — $185K at one, $280K at the other. That's not an argument about whether the role is valued. It's a straightforward signal that the scope behind the words hasn't converged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The strongest objection is that this is genuinely new work
&lt;/h2&gt;

&lt;p&gt;a16z's Tom Hollands &lt;a href="https://a16z.com/forward-deployed-job-titles/" rel="noopener noreferrer"&gt;argued in January&lt;/a&gt; that forward-deployed titles describe work that would have been unrecognizable five years ago, and he's right about the mechanism. An AI system's capabilities only resolve once you build against the customer's real data, which breaks the design-it-then-hand-it-off sequence and collapses steps two and three into one person. His test for whether a new title is doing real work: does it describe something that didn't exist before?&lt;/p&gt;

&lt;p&gt;Apply that test to the mechanism and it passes. Apply it to the vocabulary and the picture changes, because the same three words now cover step three alone, all four steps, an organizational strategy, and a Foundry feature. New work deserves a new name. It also deserves a definition, and the second part hasn't happened. Worth noting that a16z funds many of the companies staffing these teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scope belongs in writing before the kickoff
&lt;/h2&gt;

&lt;p&gt;Not a candidate checklist — a scoping checklist, equally useful from either side of the table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The artifact.&lt;/strong&gt; What gets shipped, named outright: an MCP server, a data pipeline, an application running in the customer's stack. "Scopes of work and project plans" is a real deliverable too, but it's a different one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The requirement owner.&lt;/strong&gt; Who decides what gets built. If that's the customer, this is step three and can move fast. If it's the deployed engineer, steps one and two are in scope and the timeline is a different shape entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The production owner at exit.&lt;/strong&gt; The original role's defining trait was that whoever mapped the problem answers the page six months later. "Self-sufficient when the deployment ends" is a legitimate model, and a different commitment. Both are fine. Assuming one and getting the other is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The platform boundary.&lt;/strong&gt; Whose stack, with what access, and which parts an agent can operate directly. This is where the software version of the title is genuinely useful, and where its limits are hard edges rather than preferences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where this argument runs out: a two-year-old title is supposed to be unsettled, and dispersion this early proves nothing about anyone's intentions. Posted ranges are base only, and equity is undisclosed, so the $95K figure describes disclosure practice as much as scope. And a single vocabulary may never arrive — "DevOps" never fully converged either, and teams still shipped, because the good ones wrote down what they meant locally.&lt;/p&gt;

&lt;p&gt;That last part is the actual ask. Not an industry standard. Just a shared sentence, written before the kickoff, naming which of the four steps the forward deployed engineer owns. The role was invented because a handoff document couldn't carry the knowledge. It would be a strange outcome if the title itself became the thing nobody could hand off.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>enterpriseai</category>
      <category>platformengineering</category>
    </item>
    <item>
      <title>Your Agent Is Paying Opus Prices for Yes/No Questions</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Mon, 28 Sep 2026 02:47:35 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/your-agent-is-paying-opus-prices-for-yesno-questions-48c1</link>
      <guid>https://dev.to/michaeltuszynski/your-agent-is-paying-opus-prices-for-yesno-questions-48c1</guid>
      <description>&lt;p&gt;An agent loop makes dozens of small calls that aren't reasoning at all. Is this ticket about billing. Is this search result relevant. Did the tool call succeed. Should this story be a blog post. Most agent harnesses send every one of those to the same frontier model that writes the code, with a prompt that ends in "return JSON."&lt;/p&gt;

&lt;p&gt;I measured what that costs. The same six-question classification, run 150 times per model: a System One model answered in 163 ms at $53 per million calls. Claude Opus 5 took 2.7 seconds and $11,183. That's roughly 17x the latency and 200x the price for a yes/no question.&lt;/p&gt;

&lt;p&gt;But the savings only survive if the loop around the model owns the thresholds. My data says that part is harder than the model swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  A System One model answers in types, not text
&lt;/h2&gt;

&lt;p&gt;The name comes from Kahneman's split between fast, intuitive System 1 and slow, deliberate System 2. &lt;a href="https://docs.typesafe.ai/concepts/system-one" rel="noopener noreferrer"&gt;TypeSafe's Jev&lt;/a&gt; is the first model sold under that label. It takes a state (text or JSON) and a set of typed questions, and it returns three kinds of answer: a &lt;code&gt;choice&lt;/code&gt; from options you list, a &lt;code&gt;score&lt;/code&gt; against a rubric you write, and a &lt;code&gt;noul&lt;/code&gt;, the probability that a statement is true.&lt;/p&gt;

&lt;p&gt;It doesn't write text. It doesn't call tools. TypeSafe's own docs say it plainly: Jev is &lt;a href="https://docs.typesafe.ai/introduction/coding-agents" rel="noopener noreferrer"&gt;not a drop-in replacement&lt;/a&gt; for the LLM behind Claude Code or Cursor. You keep the LLM for the work that needs one and call Jev for the judgments.&lt;/p&gt;

&lt;p&gt;That's the same argument I made in April about &lt;a href="https://www.mpt.solutions/the-model-doesnt-matter-the-harness-does/" rel="noopener noreferrer"&gt;where agent performance comes from&lt;/a&gt;: the loop matters more than the weights. A System One model is a new part to put inside the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code computes, System One judges, the LLM generates
&lt;/h2&gt;

&lt;p&gt;Three layers, and a question belongs to exactly one of them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Owns&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;Anything with an exact answer&lt;/td&gt;
&lt;td&gt;counts, date math, thresholds, combining scores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System One&lt;/td&gt;
&lt;td&gt;Bounded judgments with a closed answer set&lt;/td&gt;
&lt;td&gt;route this ticket, is this vendor marketing, how durable is this story&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Generation and multi-step reasoning&lt;/td&gt;
&lt;td&gt;write the draft, call tools, plan a fix&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TypeSafe publishes where Jev breaks, which is more than most vendors do. The &lt;a href="https://docs.typesafe.ai/model-jaggedness/jev-1.13" rel="noopener noreferrer"&gt;jaggedness page for jev-1.13&lt;/a&gt; lists arithmetic, date comparison, double negatives, oversized state and adversarial text. Every one of those is a boundary in the table above. Push arithmetic down into code. Push anything open-ended up to the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: scoring blog topics
&lt;/h2&gt;

&lt;p&gt;The content engine behind this blog pulls about 40 stories a day from TechMeme, Reddit and Firecrawl, and a regex scorer decides which ones are worth writing about. In September I wrote a Jev version of that scorer. One request asks six questions about each story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;substance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Does this contain something concrete to react to — a claim, an outcome, a number, an incident, a decision someone made — as opposed to asking the reader for help or opinions?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// criteria: { true: "...", false: "..." }&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The weights live in code, not in the prompt, so retuning them re-ranks the backlog without spending a token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substance_floor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substance_floor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vendor_fluff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vendor_fluff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first shadow run, read-only next to the regex scorer, promoted three r/devops help threads to long-form. "Do you really test your backup" scored 2.9 of 3 on beat fit and 0.83 on operator angle. Jev was right about both. A backup question from a practitioner is squarely on beat. It's also not a blog post, because it asks instead of reports. The judgment was fine. My weighting was wrong, and the fix was a &lt;code&gt;substance&lt;/code&gt; question applied as a multiplier rather than another additive term. The Jev scorer lives on a branch and only runs in shadow when I invoke it; the regex scorer is what ships today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, and where the savings leak
&lt;/h2&gt;

&lt;p&gt;I ran the six questions against 50 recent stories, three times each, on Jev, Claude Haiku 4.5 (no extended thinking) and Claude Opus 5 at low effort. Same wording, same criteria, Claude held to the same schema with structured outputs. Total spend: $1.93.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Jev 1.13&lt;/th&gt;
&lt;th&gt;Haiku 4.5&lt;/th&gt;
&lt;th&gt;Opus 5 (low)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median latency&lt;/td&gt;
&lt;td&gt;163 ms&lt;/td&gt;
&lt;td&gt;1,156 ms&lt;/td&gt;
&lt;td&gt;2,740 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per 1M calls&lt;/td&gt;
&lt;td&gt;$53&lt;/td&gt;
&lt;td&gt;$1,610&lt;/td&gt;
&lt;td&gt;$11,183&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tier changed across its own 3 runs&lt;/td&gt;
&lt;td&gt;2 of 50&lt;/td&gt;
&lt;td&gt;20 of 50&lt;/td&gt;
&lt;td&gt;5 of 50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Prices come from &lt;a href="https://docs.typesafe.ai/models" rel="noopener noreferrer"&gt;TypeSafe's models page&lt;/a&gt; ($0.042 per million input tokens, output free) and &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Anthropic's pricing page&lt;/a&gt;. The Haiku column surprised me. It's the default "cheap classifier," and it was the least consistent model in the test, flipping its own tier on 20 stories out of 50. Prompt caching won't rescue it here either: this prompt is about 1,300 tokens and Haiku 4.5 won't &lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;cache anything under 4,096&lt;/a&gt;. Opus 5 caches from 512 tokens, which brings it to roughly $3,000 per million calls if every call hits the cache. Still about 57x Jev.&lt;/p&gt;

&lt;p&gt;Jev agreed with Opus on 274 of the 300 individual answers. It also landed 26 of 50 stories in a different tier.&lt;/p&gt;

&lt;p&gt;Both are true because Jev scores the whole set higher (median composite 1.73 against Opus at 1.25) while ranking the stories in roughly the same order. My tier cutoffs sit where the old scorer's numbers used to cluster. Move to a model with a different baseline and half the backlog changes tier. Haiku and Opus disagreed on tier for 20 of 50 stories too, so this isn't a Jev defect. &lt;strong&gt;Tier cutoffs belong to the model they were tuned on. A model swap is a change to the agent harness, not a config flag.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The obvious fix is confidence routing: let Jev answer, and send low-confidence cases to Opus. Jev returns a confidence with every &lt;code&gt;score&lt;/code&gt; and &lt;code&gt;choice&lt;/code&gt;. The naive rule, "escalate the story if any answer is below 0.8," sent 48 of 50 stories to Opus, because a four-level rubric rarely puts 80% on a single level. Durability's median confidence was 0.65.&lt;/p&gt;

&lt;p&gt;TypeSafe warns against carrying one threshold across question types, and the data agrees. A per-type rule (escalate when the choice confidence is below 0.8 or any noul lands between 0.3 and 0.7) sent 24 of 50 stories up. That costs $5,421 per million, 2.1x cheaper than Opus alone, and it catches 17 of the 26 answers where the two models disagree. Real, but nowhere near 200x. On a workload where half the stories are genuinely ambiguous, escalation eats most of the savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks
&lt;/h2&gt;

&lt;p&gt;Durability was the weakest question. It caused 12 of the 26 disagreements, and on 8 stories Jev split its probability between two levels at least two steps apart. The score it returns is an expected value, so a story that's either a one-week event or a durable shift comes back as "somewhere in the middle." TypeSafe's docs say not to treat that number as a precise magnitude. My composite divides it by three and adds it in, which is exactly what they say not to do.&lt;/p&gt;

&lt;p&gt;There's no answer key in these numbers. Agreement with Opus is not accuracy, and 50 stories from one domain is a small sample. State is also data Jev doesn't treat as hostile by default, which matters for an agent that reads web pages. And the rate limits on the models page are "adjusting dynamically," which is a sentence to read twice before routing production traffic through one vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to change on Monday
&lt;/h2&gt;

&lt;p&gt;Grep your agent harness for model calls whose output is a label, a score or a boolean. Those are System One questions wearing an LLM's price tag. Shadow them against a System One model for a week, read-only, and log both answers.&lt;/p&gt;

&lt;p&gt;Then do the part the benchmark says is hard. Retune your cutoffs on the new model before you switch, set confidence thresholds per question type, and escalate only where the answer can change the decision. The model is the cheap part now. The 26 misfiled stories came from my loop, not from Jev.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>agentharnessdesign</category>
      <category>aiengineering</category>
      <category>platformengineering</category>
    </item>
    <item>
      <title>262 Nights, Zero Bytes, Exit 0: The Failure Mode of Unattended Agent Runs</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Thu, 24 Sep 2026 18:33:36 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/262-nights-zero-bytes-exit-0-the-failure-mode-of-unattended-agent-runs-1b3g</link>
      <guid>https://dev.to/michaeltuszynski/262-nights-zero-bytes-exit-0-the-failure-mode-of-unattended-agent-runs-1b3g</guid>
      <description>&lt;h1&gt;
  
  
  262 Nights, Zero Bytes, Exit 0: The Failure Mode of Unattended Agent Runs
&lt;/h1&gt;

&lt;p&gt;A backup job I run synced an empty directory every night for 262 consecutive nights. Every run exited 0. Every run wrote &lt;code&gt;Photo library sync complete&lt;/code&gt; into the log. The off-site copy held zero objects for nine months while the real library sat at 17 GB and 17,658 files on a disk nothing was copying.&lt;/p&gt;

&lt;p&gt;The script's source path pointed somewhere that did not exist. Docker's bind mount then created that path, empty, at the start of every run. The job manufactured the evidence that its own configuration was valid, found nothing there, and synced the nothing it found. A guard like &lt;code&gt;[ -d "$source" ]&lt;/code&gt; would have passed all 262 times.&lt;/p&gt;

&lt;p&gt;I found it by counting objects in the bucket by hand, on an unrelated errand. No monitor caught it, because there was nothing for a monitor to catch. The job ran on schedule. It logged. It exited clean.&lt;/p&gt;

&lt;p&gt;That job is what I think about now when a vendor shows me an agent that works unsupervised for two days.&lt;/p&gt;

&lt;h2&gt;
  
  
  An exit code answers a different question than the one you asked
&lt;/h2&gt;

&lt;p&gt;AWS now sells what it calls frontier agents: autonomous systems that, in its own words, &lt;a href="https://aws.amazon.com/blogs/machine-learning/aws-launches-frontier-agents-for-security-testing-and-cloud-operations/" rel="noopener noreferrer"&gt;"run persistently for hours or days without constant human oversight"&lt;/a&gt;. The Security Agent bills &lt;a href="https://aws.amazon.com/security-agent/pricing/" rel="noopener noreferrer"&gt;$50.00 per task-hour&lt;/a&gt;, and AWS puts an average evaluation at 24 task-hours, so roughly $1,200 a run. I wrote about that &lt;a href="https://www.mpt.solutions/aws-frontier-agents-what-50-hour-pen-testing-and-30-hour-sre-means-for-platform-teams/" rel="noopener noreferrer"&gt;pricing model in April&lt;/a&gt; and I still think the unit economics are sound.&lt;/p&gt;

&lt;p&gt;The economics are not the problem. Supervision is.&lt;/p&gt;

&lt;p&gt;When a run ends, what comes back is a status: it finished, or it didn't. One bit. That bit reports whether the process reached its final line, which is a different question from whether the work happened. My backup reached its final line 262 times.&lt;/p&gt;

&lt;p&gt;For a five-minute job you paper over the gap by looking. You read the output, you spot-check the result, your own attention is the instrument. Stretch the same job to 24 unattended hours and that instrument is gone. What's left is the bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Success decays exponentially with run length
&lt;/h2&gt;

&lt;p&gt;A 24-hour run isn't a stretched-out 1-hour run. The failure math works against you, and it compounds.&lt;/p&gt;

&lt;p&gt;Toby Ord's analysis of agent benchmark data, &lt;a href="https://arxiv.org/abs/2505.05115" rel="noopener noreferrer"&gt;&lt;em&gt;Is there a half-life for the success rates of AI agents?&lt;/em&gt;&lt;/a&gt;, fits the results to a constant rate of failure per minute of work. That produces exponential decay: success probability falls off a cliff as tasks get longer, and every agent has a characteristic half-life. Failures accumulate across subtasks, and one bad subtask sinks the run.&lt;/p&gt;

&lt;p&gt;METR measures the same curve empirically. Its &lt;a href="https://metr.org/time-horizons/" rel="noopener noreferrer"&gt;time-horizons work&lt;/a&gt; reports the task length at which frontier models succeed 50% of the time, and that 50% threshold is chosen because it's the easiest point to estimate robustly. At the horizon being advertised, the coin is fair.&lt;/p&gt;

&lt;p&gt;Then read the caveat on METR's own page. "Measurements above 16 hrs are unreliable with our current task suite."&lt;/p&gt;

&lt;p&gt;The independent yardstick runs out at 16 hours. The vendor's advertised operating range is hours or days. Past that line there is no public, non-vendor measurement of how often these runs actually succeed. You are not choosing between a measured 94% and a measured 60%. You're choosing between a vendor number and no number.&lt;/p&gt;

&lt;h2&gt;
  
  
  My own agent measurements lied in the same direction
&lt;/h2&gt;

&lt;p&gt;This isn't a hypothetical I imported from a paper. My own eval tooling has handed me clean results that were nothing of the kind.&lt;/p&gt;

&lt;p&gt;A model evaluation I ran logged a 900-second timeout and a 766-second near-timeout for two coding tasks. The CSV was indistinguishable from a model that cannot do the work. A second agent had been pushing large prompts at the same single-slot inference server the whole time. Re-run on a quiet box, the same model and the same tasks passed in 57 and 83 seconds.&lt;/p&gt;

&lt;p&gt;A separate eval launched into a detached tmux session returned two FAIL rows in under a second, with zero-byte logs. The binary wasn't on the PATH that a daemon-spawned shell inherits. That output is indistinguishable from a model that errors instantly.&lt;/p&gt;

&lt;p&gt;Both runs produced a result file. Both exited. Neither measured what it claimed to measure, and in both cases the artifact looked exactly like a real finding, which is the part that costs you. A crash you notice. A plausible wrong answer goes into the spreadsheet.&lt;/p&gt;

&lt;p&gt;The same pattern shows up wherever I stopped watching. A weekly link checker reported &lt;code&gt;Scanned 0 wiki pages / Dead wikilinks: 0&lt;/code&gt; for six weeks, green every time, because it pointed at a directory a migration had retired. Repointed, the same code immediately found 261 pages and 116 dead links. A watchdog probing an embedding server's &lt;code&gt;/api/version&lt;/code&gt; endpoint stayed green for two days while every actual embed call returned a 500 and the index took in nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 94% accuracy score is computed over the runs somebody checked
&lt;/h2&gt;

&lt;p&gt;The strongest objection is that this is all anecdote against data. AWS reports &lt;a href="https://aws.amazon.com/blogs/machine-learning/aws-launches-frontier-agents-for-security-testing-and-cloud-operations/" rel="noopener noreferrer"&gt;up to 75% lower MTTR, 80% faster investigations, and 94% root cause accuracy&lt;/a&gt; from preview customers, and one university cut incident resolution from two hours to 28 minutes. Those are outcomes, not vibes.&lt;/p&gt;

&lt;p&gt;Look at how that number gets built. A root-cause accuracy figure is computed over runs whose root cause somebody established independently. To score a run, you need ground truth, which means a human went and checked. The denominator is runs that were verified.&lt;/p&gt;

&lt;p&gt;The failure class I'm describing never enters that denominator. A run that reports success, produces a clean artifact, and is wrong is scored as correct unless somebody goes looking, and the whole premise of an unattended agent is that nobody does. My backup would have scored 262 for 262 on any accuracy metric derived from its own output.&lt;/p&gt;

&lt;p&gt;These are also first-party numbers from preview customers selected by the vendor. That's not disqualifying. It does mean they're a ceiling rather than an expectation, and they answer "how often is a checked run right," not "how often does an unchecked run lie."&lt;/p&gt;

&lt;h2&gt;
  
  
  Assert the artifact, not the exit status
&lt;/h2&gt;

&lt;p&gt;The fix isn't more monitoring. It's changing what gets asserted. The backup is the shortest example I have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before: the job's own bind mount created $SRC empty, so this always passed.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing source"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
rclone &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# After: refuse an empty source, and log what actually moved.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-print&lt;/span&gt; &lt;span class="nt"&gt;-quit&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"REFUSING: &lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt; is empty — not syncing a deletion"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
rclone &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
rclone size &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# object count and bytes, into the log, every single run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refuse a degenerate input instead of processing it. &lt;code&gt;rclone sync&lt;/code&gt; mirrors deletions, so a wrong path doesn't merely fail to back things up, it erases the copy you had. Emit a number that a zero-work run cannot produce. And push the heartbeat only when a leg moved a non-empty source, so a silent no-op goes stale and pages somebody.&lt;/p&gt;

&lt;p&gt;For an agent run the shape is identical. Don't ask whether the run finished. Ask what it changed: how many findings, how many files touched, how many tests that were red are now green, how many tokens against how many accepted diffs. Then set a floor. A 24-hour pen test that reports zero findings and zero requests issued has told you about your instrumentation, not your security posture.&lt;/p&gt;

&lt;p&gt;The cheap version of this test: can this check ever come back red? If a run that did nothing would produce output identical to a run that worked, you don't have a check. You have a log line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this doesn't apply
&lt;/h2&gt;

&lt;p&gt;Exploratory work has no assertable artifact. When you turn an agent loose on "look at this system and tell me what's odd," there's no count to floor and no diff to measure, and you're back to reading the transcript. Outcome assertions don't help there, and pretending otherwise gives you a number that means nothing.&lt;/p&gt;

&lt;p&gt;The threshold is unattended duration, not autonomy. A 20-minute agent run you watch needs none of this. An hour-long cron job nobody has opened since 2025 needs all of it. Agents make it urgent because the unattended part is the product, sold at durations past where anyone outside the vendor has measured the success rate.&lt;/p&gt;

&lt;p&gt;262 nights is the number I'd put in front of anyone signing off on a Friday-to-Monday agent run. Not because a backup script is an agent, but because it's the floor: a deterministic shell script, no model in the loop, nothing nondeterministic about it, wrong every night for nine months while reporting success. Add nondeterminism and a 50% time horizon on top of that and the exit code is worth less, not more.&lt;/p&gt;

&lt;p&gt;Decide what a successful run must produce, in a number, before you let anything run for a day. Otherwise you'll do what I did, and find out by hand, on an errand, in month nine.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>platformengineering</category>
      <category>aireliability</category>
      <category>devops</category>
    </item>
    <item>
      <title>6 Agent Licenses, 1 Runtime: You Are Paying Per Seat for the Layer That Is Not Scarce</title>
      <dc:creator>Michael Tuszynski</dc:creator>
      <pubDate>Mon, 07 Sep 2026 20:21:20 +0000</pubDate>
      <link>https://dev.to/michaeltuszynski/6-agent-licenses-1-runtime-you-are-paying-per-seat-for-the-layer-that-is-not-scarce-mnh</link>
      <guid>https://dev.to/michaeltuszynski/6-agent-licenses-1-runtime-you-are-paying-per-seat-for-the-layer-that-is-not-scarce-mnh</guid>
      <description>&lt;p&gt;Six licenses. One runtime. That's the shape of most agent quotes on the table right now, and the invoice is inverted: the six licenses are the big line, and the runtime, the thing that actually consumes scarce compute, is either a rounding error or a separate purchase order nobody brought to the renewal meeting.&lt;/p&gt;

&lt;p&gt;Microsoft published the split more clearly than anyone else did. The E7 bundle lists at &lt;a href="https://www.microsoft.com/en-us/microsoft-365/enterprise/microsoft-365-plans-and-pricing" rel="noopener noreferrer"&gt;$99 per user per month&lt;/a&gt;. To be fair to that number, it buys the whole suite, Office and Teams and the security stack, with agent capability folded in; it is the cleanest public example of the split, not a pure agent-seat price. SAMexpert's breakdown of the license puts the split plainly: that layer grants agent capability, &lt;a href="https://samexpert.com/microsoft-365-e7-licensing-guide/" rel="noopener noreferrer"&gt;it does not run them&lt;/a&gt;. Execution bills separately, in Agent Compute Units, through a pre-purchase plan sold in blocks. Two layers, two meters, one signature.&lt;/p&gt;

&lt;p&gt;Treat that as the reference architecture for every agent quote you get this year, whoever the vendor is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seat layer and the runtime layer are priced by different physics
&lt;/h2&gt;

&lt;p&gt;The interface layer is software. Marginal cost of a seventh seat: approximately zero. The vendor's cost doesn't move when you add a user who opens the panel twice a week.&lt;/p&gt;

&lt;p&gt;The runtime layer is electricity, silicon, and a queue. Marginal cost of the ten-thousandth agent run is real, and it's the only part of the stack where the vendor is buying something scarce from someone else.&lt;/p&gt;

&lt;p&gt;So when a quote grows by multiplying the free-to-produce layer and holds the expensive layer flat or unbundled, that's not a pricing accident. That's the whole design. You're being metered on the thing that isn't scarce.&lt;/p&gt;

&lt;p&gt;Run the six seats: 6 × $99 = &lt;strong&gt;$594/month&lt;/strong&gt;, &lt;strong&gt;$7,128/year&lt;/strong&gt;, before a single agent executes anything. Now ask how many of those six drove an agent in the last 30 days. If it's two, one platform engineer and one person in support automation, the real cost per active agent user is &lt;strong&gt;$297/month&lt;/strong&gt;, and the other four seats bought a login.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Per seat" hides decisions your contract already made
&lt;/h2&gt;

&lt;p&gt;The phrase does a lot of undisclosed work. Per-seat licensing is &lt;a href="https://softwarepricing.com/blog/per-seat-licensing-variations/" rel="noopener noreferrer"&gt;an umbrella covering separate decisions&lt;/a&gt; about what the unit attaches to, what time window counts it, and which type of user holds it. Vendors pick one combination, print two words, and let you assume you know which one you bought.&lt;/p&gt;

&lt;p&gt;Two of those decisions bite agent deals in particular. Named-user counts never go down mid-term; concurrent counts can, so ask which one is in your paper. It's usually in a definitions exhibit, not the order form. And the service identity your agent runs under may be counted as a seat even though no human ever touches it. That one line can double a bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The runtime is getting cheaper faster than the seat is
&lt;/h2&gt;

&lt;p&gt;The direction of travel is the part procurement keeps missing. Inference prices have fallen with every model generation, and a vendor's runtime rate card follows them down inside a contract term. Per-seat list prices move once a year, and not downward.&lt;/p&gt;

&lt;p&gt;If a seat price was originally set to recover runtime cost, and runtime cost drops 60% over a term, the seat price should move. It won't unless you make it a renewal condition. Put a benchmark clause in: seat price adjusts with the vendor's own published runtime rate card, or you get the delta as runtime credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the seat is actually buying, and why that argument doesn't survive contact
&lt;/h2&gt;

&lt;p&gt;The strongest counterargument is real, so take it seriously: the seat isn't paying for compute at all. It pays for identity and SSO, audit logging, data-loss prevention, admin policy, connectors into your existing data, and a support contract with a throat to choke. Those are per-human costs. Charging per human is honest.&lt;/p&gt;

&lt;p&gt;Fine. Then price them as such, and watch the argument come apart on its own mechanism.&lt;/p&gt;

&lt;p&gt;Governance costs scale with directory objects and policy scope, not with who happens to use an agent. If audit and DLP are what the seat buys, they should survive the removal of the agent entitlement. So ask for that quote: the seat, priced without agent capability. Most vendors won't produce it, because the number would be embarrassing next to the one with the agent checkbox on.&lt;/p&gt;

&lt;p&gt;The gap between those two quotes isn't governance. It's a right to consume, sold as a subscription. And a right to consume, when the consumption is metered separately anyway, is a toll booth on a road you already paid to drive.&lt;/p&gt;

&lt;p&gt;The vendor's own bundle proves this. E7's seat layer explicitly doesn't execute agents. The execution meter is a different SKU with a different plan. Every argument that the seat covers runtime is refuted by the price page it came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The worksheet to take into the renewal
&lt;/h2&gt;

&lt;p&gt;Split the quote before you sign it. It fits in your head. Do it on paper anyway, before you sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// renewal-worksheet.ts — split the bill by layer before signing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;seatPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// $/user/month — interface layer, ~$0 marginal cost to vendor&lt;/span&gt;
  &lt;span class="na"&gt;activeAgentUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// drove an agent at least once in last 30 days&lt;/span&gt;
  &lt;span class="na"&gt;runtimeSpendMonthly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// metered execution, separate PO&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seatBill&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seatPrice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// 594&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;perActive&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seatBill&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeAgentUsers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// 297&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtimeShare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtimeSpendMonthly&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
                     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seatBill&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtimeSpendMonthly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// 0.00&lt;/span&gt;

&lt;span class="c1"&gt;// If runtimeShare &amp;lt; 0.25, you are buying interface, not capability.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is the test. When less than a quarter of your total agent spend is execution, the vendor has moved the money to the layer where scarcity doesn't apply, and your growth path is buying more logins rather than more work.&lt;/p&gt;

&lt;p&gt;In the agent pipeline I run there is no seat at all. The interface is a terminal and a queue directory, and every dollar is metered execution: drafting this post cost $1.11 in model calls, and a second operator would have added nothing to the bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this argument breaks
&lt;/h2&gt;

&lt;p&gt;Three places, honestly.&lt;/p&gt;

&lt;p&gt;Small teams under ten people often can't get consumption pricing at all. Vendors gate metered plans behind enterprise minimums, and a flat seat price is the cheaper administrative outcome even when the unit economics are ugly. Take the seats and stop optimizing.&lt;/p&gt;

&lt;p&gt;Regulated buyers who need contractual data residency, retention guarantees, and a named DPA are buying something the runtime layer doesn't include. That's a legitimate per-tenant cost that a per-seat model approximates badly but does cover.&lt;/p&gt;

&lt;p&gt;And if your agent usage is genuinely uniform, every seat driving the runtime every day, the two models converge and the seat is just a prepaid meter with less paperwork. Usage is rarely that flat. The common shape is a few heavy operators and a long tail of people who opened the panel twice.&lt;/p&gt;

&lt;p&gt;None of that changes the ask. Get the runtime quoted as its own line, in its own units, with its own rate card. Then look at the two numbers side by side.&lt;/p&gt;

&lt;p&gt;Six seats at $99 is $7,128 a year. Ask your vendor what the runtime under those six seats cost last quarter. If they can't tell you, they're not selling you compute. And if they can, you finally know which of the two numbers to negotiate.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>enterprisesoftware</category>
      <category>cloudcomputing</category>
      <category>aistrategy</category>
    </item>
  </channel>
</rss>
