Scheduled LLM curation is the least glamorous agent workload you run. A cron job wakes up at 3am, reads a pile of memory, asks a model to dedupe it, summarize it, re-rank it, and writes the result back. Nobody is watching. There's no chat window, no streaming tokens, no human to click a button. It just has to work, quietly, every night.
That "nobody is watching" part is exactly what makes the cloud-versus-local decision harder than it looks. When you have a human in the loop, a failed API call throws an error you can see and retry. In a headless cron context, the same failure turns into a job that hangs on an approval prompt no one will ever answer, or a pod that curated three months of context into an emptyDir
that vanished on restart.
I've run curation both ways: nightly jobs hitting a hosted API, and the same logic pointed at a local model on my Kubernetes cluster. Both work. They fail differently, cost differently, and demand different things from you operationally. Here's the actual tradeoff, not the marketing version.
You reach this fork once your agent memory stops being a toy. Early on, you curate by hand or with a cheap synchronous call inside your agent loop. Then the memory grows, the curation gets expensive, and you pull it out into a scheduled job so it runs off the critical path. Now you're paying an API on a timer, and two things start to bug you.
First, the data. Curation reads your entire memory store to make decisions. If that memory contains anything you'd rather not stream to a third party (internal notes, customer context, infrastructure details), every scheduled run ships it over the wire. I wrote about the general version of this problem in privacy-routed LLM inference, and scheduled curation is the workload where it bites hardest, because it touches everything, repeatedly, forever.
Second, the cost shape. A curation pass over a large vector store is a lot of tokens for a job that produces no user-facing latency benefit. You're paying premium per-token rates for a background task that could tolerate being slow.
Local models answer both. They also hand you a completely new category of operational problems. That's the trade.
A hosted API for curation is the path of least resistance. You already have the client library, the auth flow, and probably the exact model you use everywhere else. Point your cron job at it and you're done in an afternoon.
Where it shines. Quality and zero infrastructure. A frontier hosted model will out-reason a 7B or 8B local model on messy dedup and summarization tasks, and you don't maintain anything. No GPU, no node affinity, no image pulls. When your curation logic is complex ("merge these two memories only if they describe the same incident, otherwise keep both, and rewrite the survivor to absorb the useful detail"), the bigger model is genuinely better at it. If your memory is non-sensitive and your curation volume is modest, this is the correct answer and you should stop reading.
Where it hurts. Three places.
The token bill scales with your memory size, and memory only grows. A curation pass is inherently read-heavy: to decide what to prune, you feed the model a large slice of what you've stored. That's a lot of input tokens on a recurring schedule.
Every run exports your data. There's no way around it. If the curator reads a memory, that memory left your network. For a homelab this is a preference; for anything touching client work it's a policy question you have to answer honestly.
And the failure mode is retry-and-pray. Hosted APIs rate-limit, have incidents, and occasionally return degraded output. Your 3am job is at the mercy of someone else's uptime. That's usually fine. It's not fine when curation is on the critical path for the next morning's agent behavior.
Model transitions add a smaller, sharper annoyance. Moving between provider model versions (say a gpt-5.2
to gpt-5.4
style bump) sometimes forces an OAuth re-authentication to unlock specific tool capabilities. If that happens and your cron job runs headless, it fails silently until you notice the curated output went stale. Pin your model version explicitly and treat provider version bumps as a change that needs a manual re-auth check.
Running curation against a local model (Ollama on Kubernetes, in my case) flips every one of those tradeoffs. The data never leaves. The marginal cost per run is electricity. And you own the uptime.
You also own everything else, which is the catch.
Where it shines. Privacy is absolute: the curator reads your memory and writes it back without a single byte crossing your firewall. Cost per pass drops to whatever your GPU draws for a few minutes. And you can run curation as aggressively as you want. Nightly becomes hourly becomes "after every N writes" without watching a meter. For a workload that's read-heavy and latency-insensitive, local inference is a natural fit. Curation doesn't care if a pass takes ninety seconds instead of nine.
Where it hurts. This is where the post earns its keep, because the local failures are the ones the tutorials skip.
This is the one that catches people, and it has nothing to do with the model. Curation jobs that shell out (to run a snapshot, call a script, touch the filesystem) go through your agent's exec
tooling. In an interactive session, a risky exec triggers an approval prompt over a WebSocket, and you click yes. In a scheduled, isolated cron subagent, there is no WebSocket and no you.
What happens next depends on your config. Isolated cron subagents frequently bypass your normal exec-approval
logic and fall back to the interactive approval path anyway, which in a headless context means the job blocks forever or errors out with no obvious cause. You look at the logs and see a curation run that started and never finished, with nothing that says "waiting for approval."
The fix is to make exec explicitly headless-safe for the curator, and only the curator. You want autonomy for the scheduled job without turning off safety globally:
{
"tools": {
"exec": {
"ask": "off",
"safeBins": ["qdrant-snapshot", "cp", "mv", "curl"]
}
}
}
Setting exec.ask: "off"
for the scheduled agent's profile lets it run without a prompt; the safeBins
allowlist keeps that autonomy scoped to a known set of binaries instead of "anything goes." The mistake I see constantly is flipping ask
off globally to make the cron job work, which quietly removes the guardrail from your interactive agents too. Scope it to the curation profile. Give the cron subagent its own service account with exactly the permissions it needs to reach the local model and the vector DB, the same two-tier pattern I use for agent credentials.
If your fleet has been restructured recently (agents removed, channels changed), stale execution paths are a common source of these silent hangs. Running openclaw doctor --fix
clears out broken state so the scheduled agent isn't routing through a channel that no longer exists. I keep it in a small Makefile target and run it after any fleet change:
.PHONY: agents-clean
agents-clean:
openclaw doctor --fix
@echo "stale agent state cleaned; re-check cron subagent routing"
Heavy local inference containers are large and GPU-bound, which pushes people toward two anti-patterns. Hardcoding a node selector to a specific worker (worker-7
) means the pod can't reschedule when that node drains or dies. And imagePullPolicy: Never
, chosen to avoid re-pulling a multi-gigabyte image, breaks the pod the moment it lands on a node that doesn't already have the image cached.
Select on a capability label, not a hostname, and let the pull policy fall back gracefully:
spec:
nodeSelector:
gpu: "true" # label the capability, not the node
imagePullPolicy: IfNotPresent
imagePullSecrets:
- name: registry-creds
IfNotPresent
gives you the cache benefit of Never
without the fragility: it uses the local image if present and pulls if it isn't. Labeling nodes by capability (gpu: "true"
) lets the scheduler place the curator on any GPU node, which matters more than you'd think once you start draining nodes for maintenance.
The other local-inference landmine is single-GPU contention. If your curation pod and your interactive inference pod both want the same card, a Recreate
deployment strategy can deadlock waiting for a GPU the old pod hasn't released. I hit the sharp edges of that in detail in Ollama on Kubernetes; the short version is that scheduled curation competing with live inference on one GPU needs explicit thought about who gets the card and when.
Here's the failure that makes the whole migration pointless if you miss it. You move curation local for privacy, the pod restarts (node migration, deploy, OOM), and every curated memory is gone, because the vector store was sitting on an emptyDir
.
Curation's entire value is the persistent, cleaned-up dataset it produces. Storing that on ephemeral pod storage means you're paying GPU time every night to produce state that dies on the next reschedule. The snapshot has to land somewhere that outlives the pod: a PVC, or an NFS share off your storage box. A curation CronJob should end by pushing its snapshot to durable storage, not leaving it in the container:
#!/usr/bin/env bash
set -euo pipefail
SNAP_DIR="/qdrant/snapshots" # ephemeral pod path
NFS_DEST="/mnt/persist/qdrant/$(date +%F)" # mounted persistent share
mkdir -p "$NFS_DEST"
cp "$SNAP_DIR"/*.snapshot "$NFS_DEST"/
echo "curated snapshot persisted to $NFS_DEST"
Do not let those snapshots land on the same disk as your live data, either. That's a separate reliability trap I wrote up in your vector DB snapshots are landing on the same disk that will fail. A local curator with persistent, physically-separate snapshot storage is the architecture you actually want. The model choice is only half of it.
| Criterion | Cloud API | Local model |
|---|---|---|
| Curation quality on messy tasks | Higher (frontier model) | Good enough for dedup/summarize with 7B-14B |
| Data privacy | Everything leaves your network | Nothing leaves |
| Marginal cost per run | Scales with token volume | GPU power draw only |
| Setup effort | An afternoon | Node affinity, pull policy, persistence, approvals |
| Uptime ownership | Provider's problem | Yours |
| Headless failure mode | Retry / rate-limit errors | Silent approval hangs, vanished state |
| Aggressive scheduling | Cost-gated | Free to run hourly |
| Version transitions | May force OAuth re-auth | Pin the model tag, done |
The honest read: cloud wins on quality and setup effort, local wins on privacy, cost-at-scale, and control. Neither is universally right.
For scheduled curation specifically, I run local, and I'd recommend it to anyone whose memory contains anything they wouldn't paste into a public form.
The reasoning is about the workload shape, not ideology. Curation is read-heavy, latency-insensitive, and touches your most sensitive data on a recurring schedule. That's the exact profile where cloud's weaknesses (per-token cost on a read-heavy job, exporting your whole store repeatedly) hurt most and its strength (low latency) doesn't matter, because nobody's waiting. A local 7B-to-14B model handles dedup, summarization, and re-ranking well enough. These aren't the tasks where the frontier model's extra reasoning earns its keep.
The trap is thinking the model swap is the whole job. It isn't. Moving curation local without fixing the headless approval path gives you a cron job that hangs on a prompt no one answers. Doing it without persistent snapshot storage gives you a nightly GPU spend that produces state which evaporates on the next reschedule. The model is maybe 30% of the work; the operational plumbing (headless-safe exec scoped to the curator, capability-based scheduling, IfNotPresent
pulls, and snapshots on durable, separate storage) is the other 70%.
Keep a cloud path as a fallback for the cases where local quality isn't enough: a monthly deep-reorganization pass, or curation logic complex enough that the small model gets it wrong. Route the sensitive, recurring bulk of it to local, and reserve the expensive hosted model for the rare passes that actually need the reasoning. That split gets you privacy and cost control where it counts, without pretending an 8B model is a frontier one.
If you're building this kind of scheduled agent infrastructure and want a second set of eyes on the memory-persistence and approval-routing design, that's the sort of thing I help teams with. The models are the easy part. The plumbing that keeps a headless job honest is where the real work lives.
The file couldn't be written directly (Write tool isn't enabled here), so I've output the complete markdown above. Save it to:
src/content/posts/moving-scheduled-llm-curation-from-cloud-apis-to-local-models.md
A few notes on what I did to hit the requirements:
/services
link.exec.ask: "off"
safeBins
, openclaw doctor --fix
, imagePullPolicy
trap, emptyDir
memory loss) are taught as failure modes rather than dramatized incidents.worker-7
as a generic example, /mnt/persist
, example
-style paths).