Industries

Services

Community

We’re in the cheap-code era. Does your business survive it?

Portrait of Waldemar Krumrick, Co-Founder at Darwoft

Waldemar Krumrick

Wednesday, September 9, 2026

16 minutes

Be careful assuming it does, without doing the work.

We are riding out a storm that, at first, we didn’t know where it would throw us. AI hit every industry so hard and so fast that, for a while, nobody had a map. We knew — we admitted it to one another — that a process was opening up so disruptive that it is being said its impact on the workforce and on how work gets done will be greater than the industrial revolution’s. You don’t have to sign that analogy to admit what showed up in the meetings: the craft moved.

In software the symptoms were clear. Agile as we had ritualized it — the small story, the sprint, “we’ll see it in the code” — came up short against an agent that implements in an afternoon what used to be the quarter. The industry looked again at a principle it had abandoned, not because the principle was false, but because the demand for ever more immediate results had pushed it into the realm of the utopian: sitting down to specify, for real, before you build. With agentic development, that principle came back into the realm of the possible. It is called, once again, spec-driven development. It is not the four-hundred-page document Agile was written to bury. It is a living spec: you write it, the agent closes the loop in hours, and you correct. What changed is not the value of thinking first; it is the cost of finding out you thought wrong.

The workforce shifted toward defining what needs to be done and how it should be implemented. The stage of writing the code started to look like the change you get back from a purchase. Producing code is cheap now: it is one of the claims you hear most in planning, in retrospectives, and even at commercial milestones. That, and that we now produce “faster.”

Why do we think that, in spec-driven work? Because the circuit looks like this. An agent reads a spec, writes the code, the tests pass, UAT processes find no bugs, and the process ships in a fraction of the time it would have cost five years ago. The code, in fact, was cheap to produce. What we want to discuss — with real-life cases — is something else: is cheap to produce the same as cheap to operate?

The cost did not disappear: it moved. It shifts upstream, into earlier scope — into engineering definitions — and it comes back with force in storage, transfer, parsing, tokens, timeouts, and in every stage of a flow that, when volume grows, pays the same bill again. If you are not careful, that combination can cost even more than the approach that came before. UAT processes are not designed to see that. They look at whether the product does what was asked, not at storage, parsing, or tokens. And specs, however spec-driven the method, cannot enumerate every variable in a system; pretending otherwise is utopian. That is why auditing agentic implementations — even the ones that came out “perfect” on the first pass — is still part of the job: not as distrust of the agent, but as the only way to find costs that still have no symptom.

A million way to die in the West.
I mean… 3 ways to die doing agentic development

#1 Forgetting that storage gets billed too

It is highly likely that, whatever your role, if you have been on development teams and you have a couple of years in this, you have faced a request that sounds innocent: given a document, extract and analyze the text, search for certain concepts, and show the user the document with the findings painted on top, like a highlighter over the phrase. Text is not enough. You also have to extract the geometry: the position of each word on the page, its width, its height, its x and y, and how confident the OCR is that it read it. In this kind of process the output, if you look at the artifact and not the screen, is mostly coordinates. Text is what you read; coordinates are what you store, replicate, and parse again.

In the case at hand, the work was done from a spec, the implementation was extremely fast, UAT processes found no bugs, and the process started running. In the months that followed, volume was expected to grow from tens of documents to thousands a day. Not a dollar had been lost yet. The money would leave later, with no alarm, in every JSON nobody was going to open to count decimals.

Nobody had written in the spec how many digits a coordinate should have.

A large, many-page document produced JSON of around 70 MB uncompressed, which can hold around a hundred thousand blocks. The size provoked curiosity. Why so much? A look at the file revealed fragments like this:

{
  "word": "deadline",
  "x": 0.17280931770801544,
  "y": 0.38410277343750012,
  "width": 0.05688273906707764,
  "height": 0.012441406250000003,
  "confidence": 99.96014404296875
}
{
  "word": "deadline",
  "x": 0.17280931770801544,
  "y": 0.38410277343750012,
  "width": 0.05688273906707764,
  "height": 0.012441406250000003,
  "confidence": 99.96014404296875
}
{
  "word": "deadline",
  "x": 0.17280931770801544,
  "y": 0.38410277343750012,
  "width": 0.05688273906707764,
  "height": 0.012441406250000003,
  "confidence": 99.96014404296875
}
{
  "word": "deadline",
  "x": 0.17280931770801544,
  "y": 0.38410277343750012,
  "width": 0.05688273906707764,
  "height": 0.012441406250000003,
  "confidence": 99.96014404296875
}

That is how cloud OCR APIs — AWS, Azure, Google Cloud; the pattern is the same — serialize a word and its geometry: fractions between 0 and 1, plus a confidence score. What jumped out was not the word. It was the number of decimals: 12 decimals, on a box that on screen is a yellow smudge.

Do we need that many decimals? If they do not really contribute to what the business understands as an acceptable result, and the file’s weight really does vary significantly with those decimals, we are looking at a meaningful chance to improve. Does the highlight on the document move if we drop three of those decimals? What if we drop eight? When will the user’s eye start to notice that the highlighter is “off” because we trimmed too far?

You don’t answer that by ear. You answer it with an A/B on the same block.

Table 1. A representative WORD block, as the API serializes it and as it looks after rounding geometry to 4 decimals and confidence to 2.


Field

Before (full float)

After

Left

0.17280931770801544

0.1728

Width

0.05688273906707764

0.0569

Confidence

99.96014404296875

99.96

Table 2. Quantization error at 4 decimals (normalized 0–1 coordinates). The worst shift observed on the reference document was 0.00005 of page fraction: 0.22 px at 4,500 px wide.


Viewport

Max error (4 decimals)

1,920 px (page at 100%)

0.19 px

3,000 px (high-resolution render)

0.30 px

4,500 px (zoom ~150%)

0.45 px

Below a screen pixel, the box does not move. That is exactly the PoC: the same sector of the document, the same highlight, before and after.


FIGURE 1 · A/B PoC — highlight fidelityBEFORE · 12 DECIMALSAccording to the field report,an active nest was observed inthe north sector, second visit.Left 0.17280931770801544Width 0.05688273906707764AFTER · 4 DECIMALSAccording to the field report,an active nest was observed inthe north sector, second visit.Left 0.1728Width 0.0569The box does not move. Max error measured: < 1 px at 150% zoom.

Figure 1. A/B PoC: the yellow box lands on the same phrase with 12 decimals and with 4 decimals.

Table 3. The same reference document.


Metric

Before

After (4 / 2 decimals)

Delta

Uncompressed JSON

~70 MB

~45 MB

−35%

Gzip (level 6)

20.45 MB

10.71 MB

−47.6%

JSON parse (best of 3)

965 ms

734 ms

−24%

OCR blocks

~100,000

same

Visually identical; operationally, another bill. Four decimals on a 0–1 fraction are, at Full HD width, two tenths of a pixel. Twelve decimals are a fantasy of precision the eye cannot tell apart and the business did not ask for. And the trim did not weigh once: every downstream stage — publishing a slimmer geometry contract, importing boxes, aligning evidence, debugging locally — downloaded and parsed the same file again. The cost was not in OCR: it was in the fan-out. Once the bloated artifact was written, the rest of the system inherited it.

In general, it is easy to find that the system was tested and shipped against the most visible criteria — in this case, that the findings are correctly identified on the original document — and it is likely you will find some gray areas as to the long-term impact of that operation.

What you pay if the measured profile reaches 100 million

The exercise is deliberately extreme, and that is why it helps: a hundred million documents, all of the large profile that was measured, a single gzipped copy of the OCR JSON per document, capacity only (no PUT, GET, egress, or replication). Decimal arithmetic (1 GB = 1,000 MB). List prices, U.S. East, first tier, no volume discount or contract: S3 Standard at $0.023/GB-month and Azure Blob Hot LRS at $0.0184/GB-month. In real regime the high S3 tier drops a little; we don’t use that here, so as not to dress the numbers.

Table 4. Storage footprint at 100 million documents (gzip).



Before

After

No longer stored

Per document

20.45 MB

10.71 MB

9.74 MB

Total

2.05 PB (2,045,000 GB)

1.07 PB (1,071,000 GB)

0.97 PB

Table 5. Monthly capacity, storage only.



Before

After

Savings / month

Savings / year

Amazon S3 Standard ($0.023/GB-month)

~$47,000

~$25,000

~$22,000

~$269,000

Azure Blob Hot LRS ($0.0184/GB-month)

~$38,000

~$20,000

~$18,000

~$215,000

That is one copy, one month, without counting that every downstream stage downloads the file again. Parsing, in a single pass over those 100 million, saves ~231 ms per document: on the order of 6,400 CPU-hours (about 267 machine-days).

The lesson is not the exact figure on the invoice, which tomorrow changes with region and list price. It is the order of magnitude: eight digits nobody sees, almost a petabyte and a quarter of a million dollars a year, in the scenario where the process “already works” and UAT processes already signed off.

Could it have been written in the spec? Yes. “Serialize geometry to N decimals” is one line, and it wasn’t there. And here it pays to keep your feet on the ground and tell what happens in real life. Every Monday-morning quarterback knows which team won on Sunday. It would be easy, now, to conclude that the error was in how the spec was defined and that there is no point continuing the discussion: you just have to put the effort into making that process… perfect. Fine: in the next spec we write for geometry extraction we will specify decimal precision. That does not attack the real problem. Putting yourself in that position is thinking we can enumerate every error a system might throw, and foresee each of those situations. The error, in that case, is vanity. Even allowing that in the next spec we will define the number of decimals, you have to understand that the error will change, that the room for improvement will be somewhere else. This case is not the closed diagnosis: it is a concrete example, a symptom of a possible disease. Specs will never be perfect. In our example, an agent, or a human team accelerated by one, will implement what the API returns, because “maximum fidelity” looks correct and because nobody asked it to trim. UAT processes will sign off because the product looks right. What remains, then, is not the promise of a spec with no holes: it is critical audit, monitoring, hypothesizing about what was implemented from angles that were not contemplated at the start. That is not an extra. It is a necessity. It is a must.

Do not let the reader think this is an argument against specs. It is an argument against the idea that spec plus agent plus green UAT processes equal a cheap system. The code was cheap; the impact, the operational side, what keeps the system alive, was not.

#2 Picking the wrong representative sample

Same scene as #1: the document, the text, the geometry, the highlighter on the phrase. Here you add the finding. The model has already returned N findings; what remains is relating each one to a position of origin in the document.

The algorithm that did that mapping was, at the micro level, a walk. It took every block the document had and, for each one, asked whether it was on the right page — the finding’s page — to know whether to keep comparing. In pseudocode, we could represent it like this:

for finding in findings:
    lines = [b for b in blocks if b.page == finding.page]
    # blocks is the whole document
for finding in findings:
    lines = [b for b in blocks if b.page == finding.page]
    # blocks is the whole document
for finding in findings:
    lines = [b for b in blocks if b.page == finding.page]
    # blocks is the whole document
for finding in findings:
    lines = [b for b in blocks if b.page == finding.page]
    # blocks is the whole document

On a document of two, three, four pages — even ten — that loop performs so well, to human perception, that nobody flags a potential problem. The viewer paints. UAT processes sign off. The clock never enters the room.

The problem showed up when it was measured on large documents. A profile of about three hundred pages and about two thousand findings was taking on the order of six minutes. A delay like that ought to provoke curiosity.

What if we build a per-page hash of blocks and optimize the algorithm that way? Do execution times come down?

You don’t answer that by ear. You answer it with an A/B that does not touch the match: same findings, same boxes. Only how many times the same work is redone. The sample was not a single document: 50 files, averaging about 300 pages and about 2,000 findings each — the kind of document the process would see in production, not the ten-page file that turns the local environment green.

We could consider a fix of this kind: we build the per-page index of blocks and, once inside the loop, only look it up:

lines_by_page = {}
for b in blocks:  # once
    lines_by_page.setdefault(b.page, []).append(b)

for finding in findings:  # ~2,000
    lines = lines_by_page[finding.page]
lines_by_page = {}
for b in blocks:  # once
    lines_by_page.setdefault(b.page, []).append(b)

for finding in findings:  # ~2,000
    lines = lines_by_page[finding.page]
lines_by_page = {}
for b in blocks:  # once
    lines_by_page.setdefault(b.page, []).append(b)

for finding in findings:  # ~2,000
    lines = lines_by_page[finding.page]
lines_by_page = {}
for b in blocks:  # once
    lines_by_page.setdefault(b.page, []).append(b)

for finding in findings:  # ~2,000
    lines = lines_by_page[finding.page]

None of that changes what is matched or how a match is decided. In the sample, finding counts matched before and after.

Table 6. Benchmark: algorithm runtime, typical per document (representative sample: ~300 pages, ~2,000 findings).



Before

After

Improvement

Runtime

386.9 s

292.0 s

~24.5%

Almost a minute and a half less per document. The initial implementation was written by an agent; it passed a developer’s review. Without a representative sample — the profile the process will see in regime — the algorithm that is correct at the macro level slips through: UAT processes sign off on counts, the clock never enters the room. Choosing the test cases is the key.

Could the index have been written in the spec? It is one line. It almost never is, because it is micro: algorithm implementation, not architecture. What can — and, today more than ever, must — be part of the spec-driven definition is the sample contract: what profile you test on, a clock budget, a pass/fail that is not only “same counts.” Spec-driven without that contract describes the product and leaves the algorithm to the luck of the first code that compiles, measured on the small document. UAT processes will sign off. The minutes will show up when the batch has fifty three-hundred-page documents.

Picking the wrong sample was easy: the local document passed, the highlight landed. The code was cheap. The ninety-five seconds per document, when the batch looks like production, were not.

#3 Not looking for the right cache patterns

A different business. They had put a model to work validating whether certain concepts were supported by evidence on given pages of a document. The spec asked for a yes or a no per concept, with a citation. What shipped matched that. UAT processes signed off on verdicts. Nobody asked how many times the same page excerpt traveled to the model.

To see it, one page is enough. Two hundred and fifty characters:

The contractor delivered the north section on April 18, eight days after the agreed date. The record notes that the concrete did not reach 25 MPa on the first test and the check was rescheduled to 28 days, without yet applying the penalty clause.

Two concepts to check: delay in delivery and failure to meet concrete strength. The old prompt did not look at them together. It built one call per concept and, in each one, pasted the entire text again.

Call 1

Page text:
[the 250 characters above]

Is there evidence of “delay in delivery”?
Answer yes or no and cite the excerpt

Page text:
[the 250 characters above]

Is there evidence of “delay in delivery”?
Answer yes or no and cite the excerpt

Page text:
[the 250 characters above]

Is there evidence of “delay in delivery”?
Answer yes or no and cite the excerpt

Page text:
[the 250 characters above]

Is there evidence of “delay in delivery”?
Answer yes or no and cite the excerpt

Call 2

Page text:
[the same 250 characters]

Is there evidence of “failure to meet concrete strength”?
Answer yes or no and cite the excerpt

Page text:
[the same 250 characters]

Is there evidence of “failure to meet concrete strength”?
Answer yes or no and cite the excerpt

Page text:
[the same 250 characters]

Is there evidence of “failure to meet concrete strength”?
Answer yes or no and cite the excerpt

Page text:
[the same 250 characters]

Is there evidence of “failure to meet concrete strength”?
Answer yes or no and cite the excerpt

Two round-trips. A little over 90 percent of the prompt was identical. In production it was not 250 characters or two concepts: it was thousands of characters of page and, in the profile that was measured, about 25 concepts per document. The loop was the same.

The natural reaction is to use prompt cache. The second call declares: you’ve already seen the long prefix, bill the hit. The page text is reused cheaper — about 90 percent less on those tokens. It is still 25 round-trips. Still 25 JSON payloads. Still, on every call, the closing instructions that do not fit in the cache. Prompt cache attacks the price of repeating the excerpt. The right pattern attacks the fact of repeating it.

That is converting the call. The excerpt goes once. The list of concepts, too. The model iterates.

Single call

Page text:
[the same excerpt]

Given these concepts, iterate them one by one
and say whether you find evidence of each:
1. delay in delivery
2. failure to meet concrete strength


Answer one verdict per concept, with a citation

Page text:
[the same excerpt]

Given these concepts, iterate them one by one
and say whether you find evidence of each:
1. delay in delivery
2. failure to meet concrete strength


Answer one verdict per concept, with a citation

Page text:
[the same excerpt]

Given these concepts, iterate them one by one
and say whether you find evidence of each:
1. delay in delivery
2. failure to meet concrete strength


Answer one verdict per concept, with a citation

Page text:
[the same excerpt]

Given these concepts, iterate them one by one
and say whether you find evidence of each:
1. delay in delivery
2. failure to meet concrete strength


Answer one verdict per concept, with a citation

One trip. N verdicts. UAT processes stay green: the yes and the no did not change. The bill did.

At scale, with list prices for a high-end cloud model (input $3/MTok, cache read $0.30, write $3.75, output $15), 25 concepts and a page excerpt of about 8,000 tokens — thousands of characters, not the 250 in the example — the per-document bill looks, roughly, like this. It is an estimate; the chart claims nothing else.

Table 7. Estimated cost, same document, three approaches.


Approach

Per document

At 1,000 documents

A · one call per concept

~$0.69

~$690

B · prompt cache on the excerpt

~$0.18

~$180

C · one call, N concepts

~$0.10

~$100


FIGURE 2 · Estimated cost · 25 concepts per document02505007501,000Documents0175350525700Cost (USD)A · one call per concept (~$690 / 1,000)B · prompt cache (~$180 / 1,000)C · one call, N concepts (~$100 / 1,000)

Figure 2. Rough estimate: 25 concepts, ~8,000 page tokens, list prices for a high-end model. All three lines are linear; the shallower slope wins.

All three grow linearly. What changes is the slope. Caching the excerpt flattens relative to the naive loop and still leaves twenty-five trips. Sending the concepts together flattens again, and wins. Nothing stops you from caching inside that single call; as a primary strategy, caching what you should have stopped repeating is the wrong pattern. Cheap code wires in the discount. The business asks you not to send the page again.

Could it have been written in the spec? “Don’t resend the excerpt N times; one call, N verdicts.” It is one line. It is almost never there, because the spec asks for the correct yes or no, not a count of how many times the same text crosses the API. An agent implements one call per concept, because it is correct and it fits in a loop. UAT processes sign off. The dollars we will have to pay will show up when the batch reaches a thousand documents.

Forgetting the cache pattern is easy: the model already answered, the vendor discount already shows on the line. The code was cheap. The twenty-five calls that were one, were not.

___

So if you made it this far, you may be asking yourself whether you are living the dream you always wanted — living in the cheap code era — or whether this has started to turn into a nightmare. Will my business stay competitive?

Let’s not let fear win. Meet that uncertainty with the right questions: the ones that let you diagnose the current state of the processes you are shipping and how they will scale in the organization. Check whether your teams are auditing those processes the right way. And for that you need teams with a quality that is inherent, human: curiosity. Curiosity is what leads us to ask original questions of the form “what if…?” In the geometry case the concrete question was almost ridiculous, it was so small: does highlight fidelity change if we strip eight digits from a float between 0 and 1? In the sample case: are we measuring the algorithm against a representative sample? In the cache case: are we making a repetition cheaper that we should have eliminated? If nobody asked them, the process was not audited: it was deployed. And if they were asked, the next step is not a debate of opinion: it is an A/B PoC — a representative sample, the same box, the same output with the index cold or hot, a clock budget, the same verdicts with one call per concept, with prompt cache, or with a single call and N concepts — that lets you make a decision fit for the long term, not a panic cut when the object bill is already in regime, or when the step eats six minutes on the real batch and the ten-page document never showed it.

References

In the article I mention facts such as that code became cheap to produce, that spec-driven work returned to the realm of the possible, and I compare the stage we are living through with the industrial revolution. These are not opinions only: they are a reading of the world we live in. Additional reading is below.

  1. Richard Stubbs, Spec-Driven Development (2025). Code became cheap to produce; value moved to specifying and orchestrating. richardstubbs.io

  2. Allstacks, Spec-Driven Development Isn’t Waterfall (2025). Agile did not win because specs were bad: it won because the loop from spec to running software took too long. If implementing is cheap, you can afford the spec again. allstacks.com

  3. Anton Korinek and Donghyun Suh, Scenarios for the Transition to AGI (NBER w31815, rev. 2026). The macro frame for “if this were an industrial revolution”: growth, labor share, uncertainty. nber.org

  4. Chris Frequency, What’s Expensive in the Age of AI? What is expensive is no longer typing; it is clarity of intent. chrisfrequency.com

  5. MindStudio, The Hidden Cost of AI-Assisted Development. The agent optimizes for correctness and speed, not the bill to operate. mindstudio.ai


Share it

Share it

Share it

Share it

Let’s

Talk.

We’re excited to hear about new projects! Please fill out the details below and we’ll get back to you within one business day.

Shape icon

Let’s

Talk.

We’re excited to hear about new projects! Please fill out the details below and we’ll get back to you within one business day.

Shape icon

Let’s Talk.

We’re excited to hear about new projects! Please fill out the details below and we’ll get back to you within one business day.

Shape icon

Let’s Talk.

We’re excited to hear about new projects! Please fill out the details below and we’ll get back to you within one business day.

Shape icon

Let’s Talk /

USA

1050 SW 6th ave. Suite 1100 Portland, OR 97204, US / +1 971 724 7505


4075 Wilson Blvd - 8th Floor Office 852- Arlington VA 22203 / +1 (971) 496 1920

ARGENTINA

Jujuy 1412, Cardinales Building

2 Block, 2 Floor, Office 201, 5000 Córdoba

+54 351 881 66 29

COLOMBIA

Calle 10 B #36 -32 El Ático 2 Building

Office 402 - Medellin

+57 302 3281060

Let’s Talk /

USA

1050 SW 6th ave. Suite 1100 Portland, OR 97204, US / +1 971 724 7505

4075 Wilson Blvd - 8th Floor Office 852- Arlington VA 22203 / +1 (971) 496 1920

ARGENTINA

Jujuy 1412, Cardinales Building

2 Block, 2 Floor, Office 201, 5000 Córdoba

+54 351 881 66 29

COLOMBIA

Calle 10 B #36 -32 El Ático 2 Building

Office 402 - Medellin

+57 302 3281060