/** * Hardware profiles — pick the right tier ladder for the machine running Ollama. * * The same code runs against very different hardware. This module encodes * the "which model belongs on which tier for which box" decision explicitly * so it's a product choice, not an env-var scavenger hunt. * * - dev-rtx5080 (default): hermes3:8b ladder. Validated Hermes Agent * integration path — Nous Research's hermes3:8b emits clean tool_calls * over Ollama's /v1 chat endpoint and is the proven default for driving * this MCP from an external agent (2026-04-19). * - dev-rtx5080-qwen3: Qwen 3 alternate rail. Same-family top-to-bottom * Qwen 3 ladder for users who prefer Qwen tooling or want to compare. * Picks up the `think` / THINK_BY_SHAPE plumbing in tiers.ts. * - m5-max: prod target. Qwen 3 ladder sized for 128GB unified memory. * * Per-tier env vars (INTERN_TIER_INSTANT, etc.) still override a profile's * model picks, so one-off experiments don't require a new profile. * * The retired `dev-rtx5080-llama` profile (Llama 3.1 on Deep) was dropped * at v2.0.0 — Llama 3.1 8B is obsolete, and the parity-rail experiment ran * its course. * * The retired qwen2.5 defaults (`qwen2.5:*-instruct-q4_K_M`) were dropped * at v2.0.0 — qwen2.5 is retired on modern Ollama installs and the * `INTERN_TIER_*` env knobs are sufficient for anyone pinning an older model. */ import type { Tier, TierConfig } from "./tiers.js"; export type ProfileName = "dev-rtx5080" | "dev-rtx5080-qwen3" | "m5-max"; export interface Profile { name: ProfileName; description: string; tiers: TierConfig; /** * Per-tier timeouts in ms. Lives on the profile (not as a global constant) * because cold-load behavior is hardware-bound — Instant needs 15s of * margin on a 16GB-VRAM dev box but only 5s on M5 Max unified memory. * Found via the first live dogfood pass: all Instant calls on RTX 5080 * timed out at 5s before first token. */ timeouts: Record; /** * Tiers to prewarm on server startup. Targeted adoption aid for tiers * most likely to become habit (Instant), where cold-load drag would * poison early product feel. NOT a blanket "make everything hot" knob — * Workhorse and Deep are deliberately excluded so VRAM pressure and * unintended residency churn don't start mattering. */ prewarm: Tier[]; } export declare const PROFILES: Record; export declare const DEFAULT_PROFILE: ProfileName; /** * Resolve the active profile from env. Selection order: * 1. INTERN_PROFILE env var, if a known name * 2. DEFAULT_PROFILE (dev-rtx5080) when INTERN_PROFILE is unset/empty * * If INTERN_PROFILE is set to a value that isn't a known profile, throws * CONFIG_INVALID with the available names. Silent fallback (prior behavior * through Stage A) would mask a typo'd profile against the wrong hardware * ladder and bury the signal in late tier-timeout errors. * * Per-tier env vars (INTERN_TIER_INSTANT, etc.) override the profile's picks. * Profile.timeouts are not env-overridable — they are a hardware property, * not a one-off tuning knob. */ export declare function loadProfile(env?: NodeJS.ProcessEnv): Profile; /** * Validation regex for Ollama model identifiers (FT-002). * * Ollama tag identifiers follow `name[:tag]`: * - `name` is lowercase letters / digits / dots / underscores / hyphens. * `ollama pull` and the registry reject uppercase in this segment, so * we enforce the strict form to catch typos early. * - `tag` (after the colon) allows mixed case because real-world * quantization labels routinely capitalize (e.g. `custom:model-q4_K_M` * for K-quants, `phi3:mini-4k-instruct-q5_K_M`). Lowercasing the tag * in the validator would false-positive on legitimate overrides. * * Spaces, slashes, and other punctuation are still rejected on both * sides — Ollama would refuse them at pull time anyway, and the typo * blast-radius (hours-later OLLAMA_MODEL_MISSING) is exactly what * FT-002 exists to prevent. * * The leading negative lookahead catches the `-` * dash-for-colon typo (`hermes3-8b`, `gpt-oss-120b`) where a `:` was * meant — the size-tag shape always carries a trailing letter (`8b`, * `120b`). It requires that trailing letter, so it does NOT fire on a * bare trailing version number (`glm-5`, `qwen-3`), which is a real, * colonless model id — narrowing a false-reject found in the 2026-07 * health pass (L1). Colon-tagged ids (`…:8b`, `…:cloud`) are unaffected: * the lookahead is anchored to `$` and `:` is not in its character class. * * Exported so tests + the doctor CLI subcommand can reference the same * pattern (single source of truth for what counts as a model identifier). */ export declare const OLLAMA_MODEL_NAME_RE: RegExp; /** * Validation bounds for Ollama `num_ctx` (FT-002). * * Lower bound 256 — Ollama refuses anything smaller as unworkable * (insufficient room for any meaningful prompt). Upper bound 1,048,576 * (1M tokens) covers the largest currently-shipped open weights * (Llama 4 1M context, future Qwen 4) with headroom; anything beyond * is almost certainly a typo or stale config copied from a marketing * deck rather than an Ollama-supported value. */ export declare const NUM_CTX_MIN = 256; export declare const NUM_CTX_MAX = 1048576; /** * Validate an integer `num_ctx` value sourced from env or config. * No-op when unset/undefined. Throws `InternError('CONFIG_INVALID')` * when out of bounds or non-integer. Exported for use by future * env-override paths and by the doctor CLI subcommand. */ export declare function validateNumCtx(varName: string, value: unknown): void; export interface EnvOverride { key: string; tier: Tier; from: string; to: string; } /** * Report tier-model env overrides relative to the active profile's baseline. * * Called ONCE at startup from main() so the operator sees, e.g., * "INTERN_TIER_DEEP overrides deep: hermes3:8b → custom:model-q4_K_M" * on stderr — instead of silently pinning the wrong model and wondering * later why benchmarks look off. Pure function, no side effects; caller * decides how to surface it. */ export declare function detectEnvOverrides(env?: NodeJS.ProcessEnv): EnvOverride[]; /** Resolved cloud configuration. Null when cloud is not opted into. */ export interface CloudConfig { /** Cloud base host, e.g. https://ollama.com. */ host: string; /** Bearer API key (OLLAMA_API_KEY). */ apiKey: string; /** * Cloud model per tier. instant/workhorse use INTERN_CLOUD_MODEL; deep uses * INTERN_CLOUD_DEEP_MODEL when set (else the same model). `embed` is carried * for shape only — embeddings never route to cloud. */ tiers: TierConfig; /** Per-tier cloud-attempt timeout (ms). Cloud is far slower than local 8B. */ timeouts: Record; /** Context-window cap (tokens) applied to every cloud request (GPU-time control). */ numCtx: number; /** * Standby mode (F2a, v2.9). True when a key is present but * OLLAMA_CLOUD_PRIMARY is not enabled: routing stays LOCAL-PRIMARY with * zero egress by default, and cloud serves only calls that explicitly * request `backend:'cloud'` (per-call escalation, RouteLLM-style * per-invocation routing). False = cloud-primary (v2.7.0 behavior). */ standby: boolean; } /** * Resolve the cloud configuration from env. Three outcomes (F2a, v2.9): * - OLLAMA_CLOUD_PRIMARY truthy (+ key) → cloud-PRIMARY config (standby:false) * - key present, PRIMARY unset/falsy → STANDBY config (standby:true) — * local-primary, zero egress until a call requests backend:'cloud' * - neither → null (local-only, zero egress) * * Throws CONFIG_INVALID (fail-fast at startup) when OLLAMA_CLOUD_PRIMARY * is enabled but OLLAMA_API_KEY is missing, or a cloud model name is malformed. * * Env surface: * OLLAMA_CLOUD_PRIMARY opt-in switch (1/true/yes/on) * OLLAMA_API_KEY bearer key (required when enabled) * OLLAMA_CLOUD_HOST default https://ollama.com * INTERN_CLOUD_MODEL default qwen3-coder-next:cloud (instant+workhorse+deep; * keep NON-thinking — see CLOUD_DEFAULT_MODEL) * INTERN_CLOUD_DEEP_MODEL optional deep-only override (e.g. glm-5:cloud, * deepseek-v4-pro:cloud — thinking OK here) * INTERN_CLOUD_TIMEOUT_*_MS per-tier cloud timeouts (instant/workhorse/deep) * INTERN_CLOUD_NUM_CTX cloud context-window cap (default 32768) */ export declare function loadCloudConfig(env?: NodeJS.ProcessEnv): CloudConfig | null; /** * True when the cloud backend may serve a given call. The single decision * point the runner/chat/batch budget-summing sites share, so "standby stays * local" can never drift per call-site: * - no cloud config → false (local-only) * - per-call backend:'local' → false (caller pinned this call local) * - cloud-primary → true * - standby → only when the call explicitly requested * backend:'cloud' * Callers that cannot carry a per-call directive (batch, corpus-search * explain) call this without `backend` — under standby they stay local, * and their tier budgets must NOT be inflated by cloud timeouts. */ export declare function cloudMayServe(cloud: CloudConfig | null | undefined, backend?: "cloud" | "local"): boolean; //# sourceMappingURL=profiles.d.ts.map