XO Ruby NYC · 2026

Elastic Software

Calculating tech debt using LLM tokens

A closed bamboo steamer basket, glowing softly QR code — follow along with the slides ↑ follow along

About me

Founding team
RubiconMD

Health tech built on Ruby. Acquired — now part of CVS Health.

VP of Engineering
Teladoc Health

Also Ruby! Oversaw 500+ engineers through the pandemic.

Founder
Coolhand Labs

A supervisor for prod agents — catches silent failures, now.

I may be one of the most AI-pilled people there is...

…but this AI presentation is full of ACTUAL CODE!

🤯

Okay, but before we get there…

Audience participation quiz time!

  1. Why do skyscrapers sway?
  2. Why do they put that putty stuff in sidewalk cracks?
  3. Why is the metal frame of your car designed to crumple?

Softness & flexibility add resilience in engineering.

Now, let's talk about tech debt!

  1. Is tech debt a good thing or a bad thing?
  2. Now, how about a credit card — good or bad?
  3. Okay, what about a mortgage — good or bad?

It Depends!

If you don't overdo it, debt gives you leverage.

Tech debt can be an asset or a liability, depending on what you do with it.

But how do you measure it?

It's hard to quantify the advantage it gives us, if we can't quantify the cost of the debt.

Okay, here comes some code!

Health tech, 2010s.

  • You have signed a clinical client.
  • You need their patient data in your system.
  • There is a standard for this. FHIR. Easy peasy!

Day one.

def call_parser(data)
  fhir_parser.call(data)
end

A hundred clients later.

def call_parser(client_name, data)
  case client_name
  when client_a then client_a_parser.call(data)
  when client_b then client_b_parser.call(data)
  when client_c then client_c_parser.call(data)
  when client_d then client_d_parser.call(data)
  when client_e then client_e_parser.call(data)
  when client_f then client_f_parser.call(data)
  when client_g then client_g_parser.call(data)
  when client_h then client_h_parser.call(data)
  when client_i then client_i_parser.call(data)
  when client_j then client_j_parser.call(data)
  when client_k then client_k_parser.call(data)
  when client_l then client_l_parser.call(data)
  when client_m then client_m_parser.call(data)
  when client_n then client_n_parser.call(data)
  when client_o then client_o_parser.call(data)
  when client_p then client_p_parser.call(data)
  #=> ad nauseum

No matter if people or AI codes it, it's terrible software architecture!

  • Hard to DRY.
  • Any data format change could break it.
  • Every breakage is a scramble.
  • Adds cost to every new contract.
  • The more clients you sell, the worse it gets!

The worst kind of debt: uncontrollable, unavoidable & unmanageable.

Let's try this again.

def call_parser(data)
  fhir_parser.call(data)
  # fhir_parser.call(data)
  RubyLLM.chat
    .with_instructions("Please parse this data: #{data}")
    .with_schema(PatientRecord)
    .ask
end

Same function signature. Different implementation.

I know what you are thinking right now:

🤮

But think of the benefits!

🛡️

Zero brittle code.

⚡

Instant integration.

🤸

Flexibility to handle external factors you can't change.

So now you are thinking:

🤕

But it'll cost a lot of money in LLM tokens!

⚙️

Refactor the high-volume bits to be deterministic.

🤖

Keep AI for everything else.

🧮

Now your tech debt has a quantity you can measure

Wouldn't it be nice if there were an elegant Ruby DSL for managing & tuning code-AI hybrid use cases?

A closed bamboo steamer basket, glowing softly — something's inside

Introducing

Squishling mascot — a smiling ruby gem popping out of a bamboo steamer basket

Don't just use AI to write code. Use AI to not need code.

Enter squishling

Make your class squish. Just define your goal and output format, squishling does the rest.

Squishling.configure do |config|
  config.default_model = "claude-haiku-4-5-20251001"
  # falls back to RubyLLM's default when nil
end
Squishling mascot — a smiling ruby gem
class Parser
  include Squishling

  purpose "Map the inbound record onto the schema..."
  output_schema do
    string :patient_name
    string :patient_gender, enum: %w[male female trans unknown]
    array  :diagnosis_codes, of: :string
    array  :symptoms, of: :string
  end

  # no call method — every invocation is squished.
end

Run against all 100 fixtures:

pass: 73/100 cost: $0.48 / 100 calls

Let's improve our pass rate!

class Parser
  include Squishling

  purpose <<~PURPOSE
    Map the record onto the schema below.

    Name: output "First Last" order, even if the source writes
    it as HL7's LAST^FIRST, often in all caps.

    Gender: translate to "male", "female", "trans", or "unknown".
    "unknown" for any code you don't recognize — including HL7's
    bare O / U / A / N letter codes. Never guess.

    Symptoms: only from an explicit symptom field. A diagnosis is
    not a symptom — never infer one from a diagnosis name.
  PURPOSE

  ...
end

Run against all 100 fixtures:

pass: 95/100 cost: $0.51 / 100 calls

What if we ask twice?

Two squishling mascots, glossy red gems, cuddling happily together
class Parser
  include Squishling

  ...

  squishling escalation: ["claude-haiku-4-5", "claude-sonnet-5-5"],
    harness: { type: :judged_squishsum, judge: "claude-opus-5-5" }
end

Two samples per fixture, judge on disagreement:

pass: 95/100 cost: $1.02 / 100 calls

Elastic Architecture

Now, let's make it cheaper!

↓
Flexible
Model handles it. Higher cost per request.
Efficient
Hardened parser. Cost per request near zero.

A quick analysis of the data shows 63% of the requests fit a standard format!

Code the standard shapes. Squish the rest.

Squishling mascot — a glossy red gem with celebratory splashes on either side, smiling
class Parser
  include Squishling

  ...
  squishling escalation: ["claude-haiku-4-5", "claude-sonnet-5-5"],
    harness: { type: :judged_squishsum, judge: "claude-opus-5-5" }

  def call(data)
    return fhir_parser.call(data) if fhir_message?(data)

    squish!
  end
end

Run against the same 100 fixtures:

pass: 95/100 cost: $0.12 / 100 calls

The perfect balance?

↓
Flexible
Model handles it. Higher cost per request.
Efficient
Hardened parser. Cost per request near zero.

🚀

Integrations work on Day 1.

🪙

Low cost per request.

📉

Total LLM cost well below maintenance costs.

And we can keep revisiting this balance as the business evolves.

It runs backwards, too.

Drowning under a thousand legacy parsers?

  • Find the ones at the tail of your usage curve.
  • Squish them instead.
  • Delete the code.

"Yeah, cool. But we don't need to parse health data."

Amazing! Do you integrate with services?

When a service call fails, you should squish it.

Squishling mascot — a glossy red gem with a stacked cap, smiling
class WebhookHandler
  include Squishling

  ...

  def call(service:, body:)
    VendorWebhooks.fetch(service).parse(body)
  rescue VendorWebhooks::ParseError
    squish!
  end
end

Save yourself — and the engineers who come after you — uncountable headaches!

squish it good!

"We don't integrate with services or APIs, either."

Wow, are you hiring?

Also, do you ever have production errors?

Even a bug in your own code can squish.

class SomeHelperMethod
  def call(payload)
    payload.dig("user", "email")
  end
end

payload = { "user" => "alice@example.com" }
# oops calling method sent a string, not a hash

SomeHelperMethod.call(payload)
# => NoMethodError: undefined method 'dig' for an instance of String
class SomeHelperMethod
  include Squishling

  ...

  def call(payload)
    payload.dig("user", "email")
  rescue
    squish!
  end
end

SomeHelperMethod.call(payload)
# => "alice@example.com"

If the answer is in the stack trace somewhere, squish can find & return it!

What's next

Squishling Roadmap

  • Bring Your Own Harness.
  • Framework-specific defaults.
  • Support non-RubyLLM implementations.
  • Output format annotations.
  • Spend less time generating Squishling images.
A stack of three squishling gems with an anvil on top labeled "critical"
Squishling wordmark with mascot — a smiling red gem

Squish the star button & leave issues on GitHub.

QR code to github.com/Coolhand-Labs/squishling — please squish the star button, share with your friends, and leave feedback

Elastic Software: Calculating tech debt using LLM tokens

A talk by Michael Carroll, founder of Coolhand Labs, given at XO Ruby NYC in October 2026.

Flexibility is a known engineering trick

Skyscrapers sway on purpose. Sidewalks get a strip of soft putty at every expansion joint. A car's frame is designed to crumple. In every case, a little deliberate softness is what keeps the rigid parts from cracking under load. Softness and flexibility add resilience in engineering — that is not controversial once you say it about concrete and steel.

Now say it about tech debt. Ask a room whether tech debt is good or bad and you get a chorus of "bad." Ask about a credit card and the room splits. Ask about a mortgage and it usually flips to "good." The answer was never really about the debt — it was about whether it was leverage, taken on deliberately and paid down on purpose, or just debt, taken on carelessly. Tech debt can be an asset or a liability depending on what you do with it. But that just restates the real problem: it's hard to quantify the advantage tech debt gives you if you can't first quantify the cost of carrying it.

Health tech, 2010s: the parser that could not stay clean

Here is the shape of the problem, from my own history building health care software at RubiconMD. You sign a clinical client. You need their patient data in your system. There is a standard for this — FHIR. Easy peasy. Day one, the parser is a single clean method: hand it the data, call fhir_parser, done.

A hundred clients later, that one line has become a case statement with a branch per client, running on so long the slide itself becomes illegible — which is the point. You stop trying to read it long before anyone finishes writing it. And it does not matter whether a person or an AI coding agent wrote that case statement: it is terrible software architecture either way. It is hard to DRY, any data format change can break it, every breakage is a scramble, it adds cost to every new contract, and the more clients you sell the worse it gets. That is the worst kind of debt — uncontrollable, unavoidable, and unmanageable.

Swap the parser for an LLM call

Let's try this again. Same method, same signature — comment out fhir_parser.call(data) and replace it with a RubyLLM call: instructions describing the record, a schema describing what should come back. The call site never changes; only what's inside it does.

The gut reaction is to recoil — handing parsing to a model feels wrong on first sight. But look at what you just bought: zero brittle code, instant integration, and the flexibility to handle external factors you can't change. Then the second objection lands right behind the first: won't this cost a lot of money in LLM tokens? The answer is the actual thesis of this talk — refactor the high-volume paths back into deterministic code, keep AI for everything else, and for the first time your tech debt has a quantity you can measure.

Enter squishling

Wouldn't it be nice if there were an elegant Ruby DSL for managing and tuning exactly that code/AI hybrid? That's squishling — my own gem, built directly on this talk's premise. Don't just use AI to write code; use AI to not need code. A squishling class declares a purpose and an output_schema and nothing else — no call method at all. A method with no implementation is squished straight to the LLM by default.

First real run against all 100 fixtures — real Synthea-generated FHIR patients, rendered into dozens of genuinely different wire shapes, from canonical FHIR and HL7 v2.3 down to a PDF, an OCR'd fax transcript, a SOAP note, and a plain SMS chat log: 73 pass, for forty-eight cents across all 100 calls. Every diagnosis code — exact SNOMED codes, pulled out of wildly different shapes — came back right. The failures clustered in three places: HL7 names coming back untouched, a few mis-translated gender codes, and a handful of hallucinated symptoms on records that should have reported none.

The fix was not more code — squishling has none to fix. It was the purpose: spelling out, in plain language, what a hand-written parser would have known by construction. HL7 writes names LAST^FIRST, often in all caps. A diagnosis is not a symptom — never infer one from a diagnosis name. An unrecognized gender code, including HL7's bare O/U/A/N letters, maps to "unknown," never a guess. Same class, same schema, same no-call-method trick — only the purpose changed, and the pass rate jumped to 95 of 100, for 51 cents. A prompt is code too, and it benefits from the same iteration a case statement would.

What if we ask twice?

During prep for this talk I filed an issue for a harness squishling didn't have yet — sample two different models and have a third judge disagreements — and squishling's maintainer shipped it the same day. Wiring it up on the pinned commit, though, surfaced an honest gap: the harness samples the escalation's first step twice, concurrently, rather than sampling two different steps. So a second model declared in the escalation sits unused — both samples come from the same model regardless.

Run it anyway: 95 of 100, the exact same five failures as a single attempt, at double the cost — $1.02 — and zero judge calls across all 100 fixtures. At a low temperature, that one model doesn't just usually agree with itself; it agreed on every fixture, including the five it got wrong, every single time. These are systematic misreadings, not sampling noise — a harness built to catch disagreement never gets a chance to fire unless the samples actually come from different models.

The elastic slider, and the router

A quick analysis of the data shows 63% of requests fit the standard canonical FHIR shape. That's the elasticity, drawn as a slider between fully flexible (the model handles everything, at a higher cost per request) and fully efficient (a hardened parser, cost near zero) — and the position on that slider is a decision you keep revisiting per integration, not a bet you place once at design time.

So: code the standard shapes, squish the rest. One branch routes the 63% that already match canonical FHIR straight to a fast, free, hand-written parser; the other 37% fall through to squishling. Same 100 fixtures, same 95 pass rate, 12 cents — a 76% discount over squishing everything, with the same five honest misses, because the pinned harness still samples one model twice. The payoff: integrations work on day one, cost per request stays low, and total LLM cost lands well below what the equivalent maintenance would have cost — without a rewrite, a re-architecture, or a bet placed up front.

It runs backward too. If you're an enterprise drowning under a thousand legacy parsers, find the ones sitting at the tail of your usage curve, squish them instead, and delete the code.

It was never just about health data

"Yeah, cool, but we don't need to parse health data." Fair — but do you integrate with services? Every vendor integration has this exact same shape. When a vendor webhook payload breaks your hand-written parser, rescue the parse error and squish! it — same trick as the FHIR router, just triggered by an exception instead of a format check, straight out of squishling's own README.

"We don't integrate with services or APIs either." Then are you hiring? And more seriously: do you ever have production errors? Because the same trick catches those too — even a plain bug in your own code, a dig call written for one shape that blows up on another, can rescue into a squish! and come back with the right answer pulled straight out of the stack trace. Any rescued exception, including ones from your own code, can be the trigger.

What's next for squishling — and the ask

The roadmap is short: Bring Your Own Harness, turning what was a same-day PR for me into a documented plugin point for everyone else; framework-specific defaults for Rails, Sinatra, and friends instead of every app hand-rolling the same configure block; support for non-RubyLLM clients, since today's routing and harness logic is built directly on RubyLLM; and output-format annotations, so a schema can carry units and examples instead of leaving it all to prose in the purpose. The last item is only half a joke — this entire demo leaned on me personally generating mascot art on request, and that does not scale past one deck.

If any of that sounds useful: squish the star button on github.com/Coolhand-Labs/squishling, share it, and leave an issue. I'd genuinely like to know where this pattern is breaking for you — that's the actual reason I'm up here.

Related reading: Beginners Write Software with AI. Pros Use It to Make Software Elastic. in The Everything Engineer.