Every ChannelHelm LLM call — the background pipeline and the four synchronous Studio call sites — funnels through one resolver and routes by processing_profile. One decision per tier picks the model for everything below it. This is the field guide.
Each row in the llm_providers table is one of these three types. The provider abstraction (a single chat() method) is identical across all of them — DojoClaw-style.
The HTTP shape POST /v1/chat/completions spoken by OpenAI, OpenRouter, Ollama, LM Studio, OpenClaw, vLLM, and most local stacks. Set a base URL + (optional) key + model name — done.
Native Claude Messages API. Used directly (not via the OpenAI shape) so you get full feature parity with Sonnet/Opus — long context, thinking budgets where applicable, top-tier instruction following.
premium_multimodal.Spawns the local codex CLI as a subprocess (no HTTP, no API key). Useful as a free local fallback or when you want offline operation. Slower than HTTP — best for non-interactive jobs.
Every LLM call — wherever it originates — goes through complete({ profile, … }) in workers/integrations/lm_studio.ts, which calls getProvider(profile) in workers/integrations/llm/get_provider.ts. The selection rule (#17, locked) is below — eligibility is strict, scoring is deterministic.
Six call sites, two execution contexts, one resolver. The background pipeline enqueues jobs; the Studio runs the LLM synchronously inside a Server Action (the documented carve-out in CLAUDE.md) so the operator isn't waiting on a worker to be alive.
complete() · one getProvider() · three provider classes. The Studio's three sync Server Actions are the documented "LLM in a Server Action" carve-out — bounded, text-only, operator-waiting.The llm_providers table carries a category column: text (chat/LLM — everything above, the default) and image (text-to-image, for AI thumbnail generation). Same table, same /providers editor (a Text/Image toggle), encrypted keys at rest — but two disjoint resolvers that never cross. An image provider is never picked for chat; an LLM is never picked to paint a thumbnail.
category. getImageProvider reuses the exact same selectProvider() scoring as the LLM path — only the candidate rows differ. is_default is scoped per category.getImageProvider(purpose) loads only category='image' rows, then runs the same selectProvider() used for LLMs: exact purpose match (3) → all (2) → is_default (1), tiebreak is_default DESC then id ASC. So a per-profile image provider works identically to a per-profile LLM. Returns null when no image row exists.
workers/integrations/image/get_image_provider.tsselectProvider() from the LLM pathgetProvider filters category='text'; getImageProvider filters category='image'The first (and today only) image provider type. Text-to-image over Runware's HTTP REST API (fetch, no SDK), serving Flux / Z-Image models like runware:z-image@turbo. Add it at /providers with the Image-category Runware preset.
https://api.runware.ai/v1runware:z-image@turbo (or runware:100@1, …)thumbnail_concepts worker — the only image-provider consumerthumbnail_concepts falls back to frame extraction — stills pulled from the video at the best hook timestamps. Configure Runware only when you want AI-painted thumbnails. The LLM still writes the image-gen prompt either way (that's a category='text' call); the image provider only renders it.
All LLM calls go through complete() from one of a handful of entry points: analyze_intelligence (the pipeline brief), generate.ts::generateAssetContent (every asset + the Studio Regenerate / per-section Generate buttons), and generateClipDescription (the Shorts editor's per-clip description generator). The right column is what works well enough; the stars are what's worth paying for.
Profiles are how you trade quality for cost and latency. Set the profile on the brand (default) or per package — every LLM call in that package then follows it.
purpose="all" / is_default row when no exact row exists. A row tagged with one profile never bleeds into another.Audio-only, no visual phase at all. Built for re-mining old material under current prompts (Backlog Revival). Same shape as fast_audio_only today but kept distinct so the two can diverge. Pick the cheapest fast model.
Podcasts, webinars, daily uploads where you just need decent titles + description + tags. No visual pipeline. Aim for cheap + fast over polish.
Full pipeline — audio + visual + fusion + intelligence + clip plans + thumbnails. You want this to feel like a thoughtful human draft. Latency: minutes per package is fine, seconds for Studio actions matters.
Big interviews, paid sponsorships, the videos you want featured in every channel. Maximum reasoning + nuance per call; budget is not the constraint.
Skip the tiering. A single purpose="all" provider handles every pipeline run + every Studio action. Best while you're still finding what you like.
profile="all" calls then go to Anthropic for snappy responses, while bulk pipeline jobs stay local.
All three are valid. Pick the one that matches what you've already paid for and how much fiddling you want to do.
~2 minutes
One row, purpose all, Sonnet 4.6, API key. Done. Pipeline + Studio all go to Claude. The best default if you don't want to think about it.
~5 minutes
Three rows: Haiku → fast_audio_only, Sonnet → standard_audio_visual, Opus → premium_multimodal. Quality scales with package profile; cost stays in check.
~15 minutes
LM Studio (Qwen3-32B at 16k ctx) for the pipeline, Anthropic Haiku for Studio actions. Cheapest steady state on a Mac Studio; needs LM Studio loaded with enough context.
The Providers settings page lets you add, test, and tag rows per purpose. API keys are encrypted at rest and never sent back to the browser.
⚙ Open /providers