Lo de Raúl

Once a Claude skill is a runbook, retire the agent to a cron job

A Claude Code skill I’d run interactively for weeks had stopped being exploratory and become a runbook: fixed steps, validated queries, the only variable being “what changed this week.” At that point the agent isn’t earning its keep on every run. A mature skill is already a debugged procedure — moving it to a scheduled CI job (cron + a chat webhook) is mechanical, not creative.

The part worth calling out: Claude is great at building dynamic-data dashboards — self-contained HTML reports with the data embedded as JSON and the filtering/sorting/charts as plain JS in one file. But once that generator exists, Claude is no longer in the loop. It’s a Python script that runs a few queries and emits HTML. CI runs it on a schedule, drops it in object storage, and posts a presigned link to chat. No model call to produce the report.

That’s the cost and vendor story in one line: the deterministic core — SQL, aggregation, the HTML dashboard, the chat card — needs no model, runs on free CI minutes, and would work unchanged if I swapped LLM vendors or dropped them.

I kept an LLM for exactly one slice where judgment helps: a short “what changed this week vs last” blurb on the chat card. One small call, once a week, and wrapped so it can never block the report:

def interpret(series):
    if not os.environ.get("LLM_API_KEY"):
        return None            # no key → skip
    try:
        return call_model(series) or None
    except Exception:
        return None            # report still ships

If it fails or the key’s missing, the card renders without the blurb. The report never depends on the model.

The posture I’m keeping: agent for the phase where the procedure is still uncertain; boring CI for the phase where it repeats. Deterministic core that’s free and portable, plus a thin, swappable, non-blocking layer of intelligence on top — the inverse of “the agent does everything.”