Spaces:
Running
Loom Model Panel: A Privacy-First Gateway for Uncorrelated Frontier-Model Judgment on Free Tiers
System design document β 2026-06. All claims cross-checked against the code at the commit that ships this file; constants quoted are the env-overridable defaults.
1. Problem & motivation
A single LLM reviewing its own plans inherits its own blind spots. The highest-leverage use of other frontier models is as uncorrelated second opinions: a panel of models with different training lineages (Kimi, DeepSeek, GLM, Nemotron, Llama, MiniMaxβ¦) judging one artifact produces failure-mode diversity a single model cannot.
Three constraints shape the system:
- Cost: zero. Every model must come from recurring-free tiers (NVIDIA NIM free
credits, Cloudflare Workers AI, GitHub Models, OpenRouter
:free). Free tiers are throttled, flaky, and quota-capped β so reliability must be engineered around the providers, not assumed from them. - Privacy: per-user, verifiable. The deployment model is one instance per user, the user's own provider keys, nothing shared β privacy by isolation rather than by access control. On top, a request-level privacy router refuses providers that train on or log prompts.
- Operations: a single ephemeral container. The target platform (Hugging Face Spaces, free Docker tier) gives one container that is recycled on every deploy. Anything that must survive β async jobs, metrics, templates, cache β needs explicit durability machinery.
The result is a stdlib-only Python service (plus httpx and optionally
huggingface_hub) exposing a token-guarded HTTP API: one-shot panels
(/api/critique, /api/panel), whole-repo review (files/repo_url forms), and
multi-stage orchestrated pipelines (/api/run + templates).
2. Architecture overview
client ββHTTPβββΆ BoundedThreadingHTTPServer (MAX_WORKERS=48, REQUEST_TIMEOUT_S=30)
β bearer auth (constant-time), body cap 8MB, 503/429 backpressure
βΌ
request builders (panel / critique / run)
ββ repopack: {files|repo_url} β packed blob + per-judge context budgeting
ββ privacy router: strict | fallback | off
ββ prompt cache (exact-match, per-space)
β
ββββββββββββ΄ββββββββββββ
sync (asyncio.run) async JobRunner (one background event loop)
β β durable: JobStore (atomic file + HF Dataset mirror)
βΌ βΌ reload-on-boot; MAX_INFLIGHT_JOBS=200
route_judge / orchestrator stages
β per logical model:
βΌ
SlotScheduler βββΆ provider hosts (NVIDIA / Cloudflare / GitHub / OpenRouter)
rotation Β· pacing Β· cooldown/blacklist Β· cross-provider failover
β
βΌ every call β per-profile MetricStore (HF-mirrored)
Two execution planes share one substrate. The synchronous plane runs a request to
completion on a bounded HTTP worker via asyncio.run. The asynchronous plane
({"async": true}) hands work to a single long-lived background event loop in
JobRunner, returns a job_id immediately, and the client polls GET /api/jobs/<id>
for partial results. Both planes call the same route_judge/scheduler/metrics core, so
routing, privacy, caching, and telemetry behave identically regardless of plane.
The canonical request is for a logical model (e.g. kimi-k2.6); the scheduler maps
it to an ordered list of physical (provider, model) hosts and bounces across them on
failure. Catalogs (providers_catalog.json, models_catalog.json, benchmarks.json,
reasoning_catalog.json, panel.json) are the single source of truth β adding a model
or provider is a config edit, not a code change.
3. Subsystems
3.1 Routing & cross-provider failover (scheduler.py)
SlotScheduler owns rotation and resilience. pick_slot_from(candidates, role, exclude_providers) returns the best eligible host for a logical model, where eligible =
not blacklisted, not in cooldown, not excluded. Ranking favors the least-recently-attempted
provider (rotation), then healthier model families, then freshness β so a flaky provider
drifts to the back instead of being hammered. route_judge (jobs.py) drives the bounce
loop: on any 429/5xx/empty/timeout it records a penalty (cooldown or blacklist), emits a
metric, and re-picks the next provider hosting the same logical model. Retries are
cross-provider by default (JUDGE_RETRIES=0): we move hosts rather than re-hit a failing
one. Pacing reserves each provider's next allowed call time under a lock
(wait_for_provider_pacing), so concurrent callers across both planes stagger to
distinct slots and stay under per-provider RPM.
3.2 Privacy router (jobs.py _privacy_tiers / _pick_tiered, providers.slot_is_privacy_safe)
Each provider carries a trains_on_data posture (true/false/"per-model"); a host is
privacy-safe iff it does not train on or log submissions. OpenRouter :free routes require
training consent, so they are per-model β unsafe; HF Inference is a proxy whose posture is
the routed backend's. Three request modes: strict (default) drops every unsafe host and
returns privacy_blocked rather than leak; fallback tries safe hosts first and an unsafe
host only as a last resort; off uses all. PRIVACY_MODE locks an instance to strict;
no_store runs a job without persisting its content anywhere.
3.3 Prompt cache (promptcache.py)
A per-space exact-match cache keyed by sha256(logical, system_prompt, user_msg, max_tokens, reasoning, research, privacy). Privacy is part of the key β a result produced under
privacy=off can never be served to a strict request. In-memory LRU (CACHE_MAX=512) +
atomic disk mirror, TTL CACHE_TTL_S=24h. Only successful calls are cached; no_store
skips writes. A hit returns prior output with zero provider calls and transmits nothing.
3.4 Durable async jobs (jobs.py JobRunner, jobstore.py)
Free containers recycle on deploy, which previously killed long async runs. JobStore
writes each job to data/jobs/<id>.json via atomic tmp-rename on every state transition and
mirrors to a private HF Dataset, so jobs survive both crashes and redeploys. On boot the
runner re-hydrates: panel jobs reschedule any unfinished judge (the _exec block carries the
prompt/budgets needed to resume), and mid-flight orchestration runs are marked interrupted.
MAX_INFLIGHT_JOBS=200 caps simultaneously-running jobs (the async plane bypasses the HTTP
worker cap); JOB_TTL_S=6h prunes finished jobs.
3.5 Codebase ingestion (repopack.py)
Turns {path: content} or a fetched repo_url tarball (GitHub codeload, stdlib only,
REPO_MAX_BYTES=50MB) into one annotated blob: binaries/lockfiles/node_modules filtered,
per-file ===== FILE: ===== delimiters, token estimate chars/3 for code. Each judge gets a
pack fitted to its own context window (ctx per host in models_catalog.json); when a
repo exceeds a judge's budget, low-value files (generated/bulk) are dropped first, tests and
docs last, and the result reports coverage so partial-context reviews are honest. A
diff_mode {base_ref, head_ref} packs only the changed files plus a unified diff.
3.6 Orchestrator & template library (orchestrator/, orchestrator/templates/)
Beyond one-shot panels, /api/run executes multi-stage schematics: stages run
sequentially, a stage may fan out (one shard per item in a JSON list a prior stage
emitted, shards spread across providers via shared in-flight exclusion), and a judge loop
retries the final body up to max_rounds if rules/criteria fail. The built-in power
templates β repo_audit, design_doc, redteam, panel_debate β each map β fan-out β
synthesize β judge, and bake in hard-won lessons (e.g. repo_audit quarantines
confident-but-unverifiable concurrency claims into a "False-positive watch" section).
File-output mode reconstructs a multi-file artifact tree from marker blocks.
3.7 Three-layer judge (judge/)
Output hardening runs in order and merges reports: Layer 1 declarative rules
(engine.py: min/max words, required sections, banned phrases, regex); Layer 2 code
plugins (citation_integrity, url_health, json_parses); Layer 3 LLM-as-judge
natural-language criteria. Hard fails trigger a retry round; soft flags annotate.
3.8 Per-profile metrics (metrics.py)
Every call emits a metric tagged with a profile, so cost/throttle/latency are tracked per
identity, not globally. Rolling per-provider call/token windows (pruned on write, 24h) feed a
budget guard that cools a provider when a profile hits its published daily ceiling.
HOT_WINDOW=5000 events in memory; flushed every FLUSH_EVERY=25 and mirrored to the HF
Dataset. Outputs are sampled only if METRICS_SAMPLE_OUTPUTS=1 (off by default), and never
for no_store jobs. Exposed via GET /api/stats, /api/metrics?profile=, /api/roster.
4. Guarantees
- β₯2 privacy-safe frontier models, always.
panel.roster()joins the benchmark catalog with live host health and the privacy classification;/health.frontier_okis true iff at least two frontier logical models have a privacy-safe host routable right now. It reports false honestly rather than silently routing to an unsafe or non-frontier host. - Privacy by isolation + verifiable routing. One instance per user with their own keys;
strict-by-default routing;
no_storefor zero retention. Backpressure responses (503/429) carry no prompt content. - Durability across crash and redeploy. Atomic local writes + private HF Dataset mirror; reload-on-boot resumes unfinished panel judges.
5. Threat model & security
- Auth. Every non-health route is bearer-gated with a constant-time compare
(
hmac.compare_digest); an unsetCRITIQUE_TOKENrefuses service (503) rather than serving open. - Resource exhaustion / DoS.
BoundedThreadingHTTPServercaps concurrent sync workers (MAX_WORKERS=48) and returns 503+Retry-Afterover capacity without spawning a thread;REQUEST_TIMEOUT_S=30drops slowloris connections;MAX_INFLIGHT_JOBS=200caps the async plane with 429+Retry-After;MAX_BODY_BYTES=8MBbounds request size. Per-user instances make these per-tenant by construction; the frontend can tune the env knobs per user. - Data at rest. Job snapshots and the cache live on the instance's local disk and the
user's private HF Dataset.
no_storeopts a job out entirely (trading deploy-resume for zero retention). Metrics store no prompt/output text unless explicitly enabled. - Provider trust. The privacy router is the control that keeps prompts off
training/logging hosts;
:freeand unpinned-proxy routes are classified unsafe. - Secret hygiene. Keys are Space secrets, never committed;
data/is gitignored.
6. Evaluation
- Offline simulation (zero quota). A
MOCK_MODEprovider yields deterministic, fault-injectable responses; nine sims exercise rotation/failover/budget/profile isolation (sim_scenarios), research ReAct (sim_research), crash-resume (sim_persistence), orchestration + fanout diversity (sim_orchestrator), the privacy router (sim_privacy), the cache incl. the privacy-key (sim_cache), ingestion + budget fitting (sim_repopack), the template library (sim_templates), and the concurrency bounds β 503/slowloris/429 (sim_server_limits). All green. - Live verification. Every roster slug is probed (16-token call) before catalog entry
(
docs/ROSTER-2026-06.md); failures are recorded and excluded, not shipped broken. - Whole-repo reality check. A 163k-token review of this codebase confirmed strong hosts
(Kimi, MiniMax) genuinely reason over the full repo, surfaced two real P1 bugs since fixed
(cache privacy bypass;
privacy_blockedfallthrough) and several false positives (later disproved), and exposed reliability variance (qwen3.5-397b returned empty content β demoted from the default panel).
7. Limitations & future work
/api/rundoes not context-fit the prompt. The repo-pack per-judge budgeting lives in the/api/panelfilesform, not the orchestrator path β so a large prompt sent through a template hits small-context hosts (e.g. Cloudflare llama-3.3, 24k) with 413 and only survives via cross-provider failover to big-context hosts. Fix: have the scheduler skip hosts whosectxcannot fit the estimated input, or extend ingestion budgeting to/api/run.- Per-request event loop on the sync plane. Each sync request runs
asyncio.runwith a freshhttpx.AsyncClient, discarding connection pooling; acceptable under the worker cap, but a shared loop/client would be more efficient at scale. - Benchmarks are indicative.
benchmarks.jsonscores are hand-curated and dated, used only to order the roster and define "frontier" β not an authoritative ranking. - Provider breadth. Current pool is four providers; expanding to Groq, Cerebras, Mistral La Plateforme, and HF Inference (each with a privacy block + live probe) widens the frontier-safety margin (planned).
8. Reproducibility
Duplicate the Hugging Face Space, set your own provider keys as Space secrets
(NVIDIA_API_KEY, CF_API_TOKEN+CF_ACCOUNT_ID, GITHUB_TOKEN, OPENROUTER_API_KEY) and
a CRITIQUE_TOKEN; optionally METRICS_HF_REPO/JOBS_HF_REPO for cross-deploy durability.
The service boots with whatever keys are present and reports its live posture at /health
and /api/roster. Everything is stdlib + httpx; no build step. See README.md for the
API surface and PRIVACY.md for the full privacy/off-switch matrix.