If I had to pick the single highest-leverage piece of infrastructure we maintain, it wouldn't be the Flask template. It would be a small Python package called dangercorn-claude-helperdcch on the pip index, dcch in the import line.

Every Dangercorn app that uses Claude imports from dcch. Not from the official anthropic SDK directly. The import looks like this in every repo:

from dcch import claude

summary = claude.prompt(
    "Summarize this inspection log in 2 sentences",
    context=inspection_text,
)

There are good reasons for that indirection, and they're worth explaining because the pattern applies to a lot more than Claude wrappers.

What dcch Actually Does

The package is a thin wrapper around the Anthropic Python SDK. "Thin" is doing some work in that sentence — there's not much code in there — but the things it handles matter:

Retry with exponential backoff. Anthropic's API, like any API, occasionally returns transient errors. Rate limits, timeouts, brief 500s. Without retry logic every one of those becomes a user-facing crash. dcch retries up to three times with exponential backoff and only surfaces the error if all three attempts fail.

Model downshift on 429. If you hit a rate limit on Claude Sonnet, dcch automatically retries on Claude Haiku. If you hit a rate limit there, it falls back to a templated response (more on this in a second). The user sees slightly less capable output, but they don't see an error.

JSON mode with code-fence stripping. When you ask Claude for JSON, it sometimes returns ```json\n{...}\n``` with the markdown code fence still wrapped around the JSON. dcch's structured() call strips those automatically and parses the result. Every app gets this for free, and nobody has to rediscover the "why is my JSON parser failing" bug.

Fallback-to-template. Every call can specify a template fallback — a pre-written default that gets returned if the API call fails entirely. This is the thing that makes self-host viable. A user who's self-hosting and doesn't want to provide an Anthropic API key can still use the app; they just get the templated fallback response instead of the AI-generated one.

Cost logging. Every call logs the model used, input tokens, output tokens, and estimated cost in dollars to a shared log file. At the end of the week I can grep across all apps to see what we're spending and where. If one app is suddenly 3x more expensive than last week, I'll notice.

Why Not Just Use the SDK

The official anthropic package is good. It's well-maintained. It supports everything Claude does. I could have each app import it directly. Why add another layer?

The answer: the behaviors above are ones I want to be identical across every app. If retry logic is per-app, different apps will have different retry logic, which means different bugs, which means different debugging. If cost logging is per-app, I'll have 40 different log formats to reconcile at month-end. If fallback-to-template is per-app, I'll write the same boilerplate 40 times.

A shared wrapper makes all of those shared. When I improve the retry logic, every app gets the improvement on next pip install -U dcch. When I add a new model, every app can opt into it with a single config change. When I find a bug in cost logging, I fix it in one place.

The Rule of Three

There's a principle in software design that goes: the first time you write something, write it. The second time you write it, notice the duplication. The third time, extract it into a shared library.

I wrote the retry-and-fallback pattern in cheesemaking first, because the AI vision feature needed to be resilient. Then I wrote it again in honeybees for the frame reader. By bookcircle, I knew this was going to be in every app that used Claude, and I extracted it. dcch was born.

This is a hard pattern to get right. Extract too early and you're building abstractions for use cases that don't exist yet — you end up with a library that's wrong for everyone. Extract too late and you have 15 copies of nearly-identical code with subtly different bugs. Three is the magic number where you have enough real use cases to know what the abstraction should look like.

What Went Wrong the First Time

The first version of dcch was too opinionated. It enforced a structured-prompt format, assumed certain patterns, tried to be clever about caching. When I tried to use it in the 4th app, none of those assumptions fit. I had to rewrite the package.

Version 2 is much simpler. It does four things (retry, fallback, structured-mode, cost-log) and it does them predictably. It does not try to be an "agent framework" or a "prompt orchestrator." Those are separate concerns. dcch is just "the shared Claude call, with the boring reliability stuff handled."

That narrower scope is what makes it reusable. A library that does a lot is almost always less reusable than a library that does one thing.

The value of a shared library scales with the number of places it's used. 40 apps, one wrapper: every improvement is 40x leveraged. 40 apps, 40 wrappers: every improvement is 1x leveraged and happens 40 times.

What dcch Doesn't Do

Things I've deliberately kept out, so the package stays narrow:

What It Looks Like Upstream

For the beekeeping AI frame reader, the call from the app looks like:

from dcch import claude

frame_analysis = claude.structured(
    prompt=FRAME_READER_PROMPT,
    image=frame_photo_bytes,
    schema={
        "frame_type": "str",
        "brood_pattern_grade": "str",
        "comb_coverage_pct": "int",
        "mite_signs": "bool",
        "next_step": "str",
    },
    fallback_to_template="frame_reader_default",
)

That's it. No retry logic, no JSON parsing, no error handling. It's all in the wrapper. The app code reads like what it's actually doing: "take a photo, analyze it, give me structured output." 10 lines in the vertical, 200+ lines of reliability code hidden behind the call.

The Broader Pattern

Every time we write something in the second vertical that we wrote in the first, I make a note. When we write it a third time, it's a candidate for extraction. We have a few shared packages now — dcch for Claude, dcst for the Flask template, and dcimg for image processing that several verticals need.

The rule is: extract only when the pattern has three real use cases, and keep the extracted library boring. No fancy APIs, no configuration inheritance, no plugin systems. The shared code should be shorter and simpler than the original in-app version, not more complex.

This is the opposite of how enterprise software usually goes — extractions tend to become larger and more abstract over time. We're deliberately going the other direction. Shared libraries should reduce total system complexity, not move it around.

If you're running a portfolio of apps and not doing this, start. Pick the thing you've written three times. Extract it. Use it everywhere. Don't look back.