Standalone AI Gateway — providers, autoscaler, GPU infra, observability, caching, proxy
TypeScript
0
599 commits
updated Oct 15, 2026
A self-hosted, cost-efficient AI infrastructure platform that provides:
Built with TypeScript, providing a unified interface for AI providers and multi-tier GPU autoscaling with automatic failover.
POST /v1/speech endpoint handles STT → LLM → TTS. Transport (GPU vs cloud) is hidden from the caller. No SSE/WebSocket/WebRTC in client code.PUT /v1/deployments/:name with any Docker image (or a profile like qwen3-tts) → autoscaled replicas, scale to zero, cold-start wait, per-deployment replica bounds via API. See docs/deployments.md# Workspace dependency (in monorepo)
bun add @parle/ai-gateway@workspace:*
Send audio, get back transcription + translation + TTS audio in one JSON response. The gateway handles GPU vs cloud routing transparently.
# Full pipeline: audio in → JSON out
curl -X POST "http://localhost:4000/v1/speech?source=fr&target=en&speaker=Ryan" \
--data-binary @audio.wav -H "Content-Type: audio/wav"
# Response:
# {
# "transcription": "Bonjour le monde",
# "response": "Hello world",
# "audio_base64": "UklGR...",
# "content_type": "audio/wav",
# "timing": { "total_ms": 1234, "used_gpu": true }
# }
import { createAIClient } from '@ai-gateway/client';
const client = createAIClient({ registry, defaultProfile });
// Full pipeline — tries GPU first, falls back to cloud automatically
const result = await client.pipeline(audioBuffer, systemPrompt);
// result.stt.text → "Bonjour le monde"
// result.chat.content → "Hello world"
// result.tts.audio → Buffer (WAV)
// result.usedGpu → true/false
// result.totalLatencyMs → 1234
// Individual stages (also transport-transparent)
const stt = await client.transcribe(audioBuffer); // { text, latencyMs }
const chat = await client.chat(messages); // { content, latencyMs }
const tts = await client.synthesize(text); // { audio, contentType }
SSE, WebSocket, and WebRTC are blocked at the proxy level (return 410 Gone). All client code uses the JSON pipeline endpoint or the AIClient API. This keeps transport concerns inside the gateway — callers never parse SSE events, manage WebSocket frames, or negotiate WebRTC.
import { createGateway } from '@ai-gateway';
const gateway = createGateway({
storage: myStorageAdapter, // implements GatewayStorage
stateStore: redisAdapter, // optional, falls back to in-memory
hooks: { // optional observability
onScaleUp: (e) => console.log('GPU booting:', e),
onCostAlert: (e) => console.log('Budget alert:', e),
},
});
const status = await gateway.handleGet(userId);
const result = await gateway.handleAction(userId, 'boot', { tier: 0 });
import { groqLLM, openaiSTT, openaiTTS } from '@ai-gateway/providers';
// Individual providers
const stt = openaiSTT({ apiKey: '...' });
const llm = groqLLM({ apiKey: '...' });
const tts = openaiTTS({ apiKey: '...' });
const transcript = await stt.transcribe({ audio: audioBuffer });
const response = await llm.chat({ messages: [{ role: 'user', content: transcript.text }] });
const audio = await tts.synthesize({ text: response.text, voice: 'alloy' });
import { withProviderFallback } from '@ai-gateway/providers';
const result = await withProviderFallback(
[groqLLM({ apiKey: '...' }), openaiLLM({ apiKey: '...' })],
(provider) => provider.chat({ messages }),
{ maxRetries: 2, cooldownMs: 30_000 }
);
import { RunPodClient, TensorDockClient } from '@ai-gateway/gpu-providers';
const runpod = new RunPodClient({ apiKey: '...' });
const pod = await runpod.boot({
gpuType: 'RTX A5000',
dockerImage: 'marcosremar/parle-s2s:latest',
ports: ['8000/http', '22/tcp'],
});
await runpod.waitForReady(pod.id);
await runpod.terminate(pod.id);
| Import Path | Description |
|---|---|
@ai-gateway | Full API (all modules re-exported) |
@ai-gateway/browser | Browser SDK — SpeechClient (legacy, see transport policy) |
@ai-gateway/providers | AI providers — OpenAI, Groq, Fireworks, etc. |
@ai-gateway/autoscaler | GPU autoscaling engine |
@ai-gateway/gpu-providers | Cloud GPU clients — RunPod, TensorDock, Modal |
@ai-gateway/handlers | Next.js route handler utilities |
@ai-gateway/client | AIClient — profile-based unified API |
@ai-gateway/tracking | Benchmark & spend tracking |
@ai-gateway/adapters | State persistence — InMemory, Redis |
@ai-gateway/auth | GPU token signing/verification (HMAC) |
@ai-gateway/infra | SkyPilot CLI, SSH/SCP utilities |
@ai-gateway/benchmarking | Health checks, latency benchmarks |
┌─────────────────┐
│ createGateway │ ← Single entry point
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌───────▼──────┐ ┌─────▼─────┐ ┌───────▼──────┐
│ Autoscaler │ │ Providers │ │ Handlers │
│ Engine │ │ Registry │ │ (Next.js) │
└───────┬──────┘ └─────┬─────┘ └──────────────┘
│ │
┌────────┼────────┐ │
│ │ │ │
┌──▼──┐ ┌──▼──┐ ┌───▼─┐ ┌▼────────────┐
│RunPod│ │TD │ │Modal│ │OpenAI, Groq,│
│ │ │ │ │ │ │Fireworks,...│
└──────┘ └─────┘ └─────┘ └─────────────┘
| Provider | STT | TTS | LLM | Image | Realtime |
|---|---|---|---|---|---|
| OpenAI | Whisper | TTS-1/HD | GPT-4o | DALL-E 3 | Realtime API |
| Groq | Whisper | PlayAI | LLaMA, Mixtral | - | - |
| Fireworks | Whisper | - | LLaMA, Mixtral | Stable Diffusion | - |
| OpenRouter | - | - | 200+ models | Flux, DALL-E | - |
| Modal | - | MOSS-TTS | - | - | - |
| Self-hosted | Generic | Generic | Generic | - | - |
| Provider | Status | Notes |
|---|---|---|
| RunPod | Working | Primary. ~31s boot. Use HTTP+TCP ports. |
| TensorDock | Working | RTX 3090 inference backend. |
| Modal | Working | Serverless, auto-scales to zero. |
| Hyperstack | Working | KVM w/ nested virt (/dev/kvm confirmed). Custom image build pipeline wired. offloadOnIdle = 1.3s wake. |
| Vast.ai | Improved | Desktop offer policy (reliability ≥0.95, download >1 Gbps, ≤~$0.20/hr) + in-memory hot pool (claim/release). Direct HTTP still often needs SSH proxy on residential hosts; prefer offerPolicy: 'desktop' for quality. |
| SkyPilot | Working | Multi-cloud orchestration. |
These offer sandboxed GPU runtimes (sub-second cold start), per-second billing, and fit the Modal-style scale-up model we could adopt instead of rolling our own. Priority in order of fit:
| Provider | Runtime | Cheapest GPU | Cold start (GPU) | Free trial | Worth testing? |
|---|---|---|---|---|---|
| Koyeb | Firecracker + Cloud Hypervisor (GPU) | L4 $0.70/hr, L40S $1.20/hr | seconds (GPU VRAM snapshot is WIP, CPU gets 200ms) | $10 / 7d Pro trial, GPU eligible | Yes — free trial covers PoC |
| Northflank | Firecracker / Kata / gVisor (choose per workload) | L4 $0.80/hr, A100 40G $1.42/hr | 1–2 s end-to-end | Sandbox plan free (CPU only); GPU requires pay-as-you-go | Yes if you need runtime choice |
| RunPod Serverless | Proprietary (FlashBoot) | T4 ~$0.20/hr active, L4/L40S ~$0.40–0.70/hr active | <200ms in 48% of calls | Community credits occasionally | Yes — currently the best latency/$ in the market |
| Beam / Beta9 | runc or gVisor (runtime configurable) + custom CLIP lazy image + cuda-checkpoint | ~$0.70–1.00/hr | 2–3 s typical, 50ms warm | $30 credit | Yes — also open source (AGPL), self-hostable as beta9 |
| Inferless | Container (no microVM, no CRIU) — just hf-transfer accelerated weight download | T4 $0.33/hr, A10 $0.61/hr | Llama-3 8B 13.3s / Phi-3 7.8s (measured, T4) | $30 credit + 10h free | Low priority — weight-loading only |
| Salad.com | Distributed marketplace (no sandbox) | RTX 4090 $0.204/hr | marketplace (unreliable like Vast) | — | Batch only, not latency-sensitive |
| CoreWeave | Bare-metal | L40S $2.25/hr | ~200ms bare-metal | — | Expensive but reliable |
| Fly.io GPU | Firecracker + Cloud Hypervisor | L40S $1.25/hr | ~30s / 7B model | — | ❌ Deprecated Aug 2026 |
Measured comparison baseline (Hyperstack L40 + our offloadOnIdle): 1.3s wake on the same VM. Competitive with Northflank (1–2s). Beaten by RunPod Serverless FlashBoot (<200ms). Koyeb GPU currently does not deliver its 200ms CPU story for GPU workloads (VRAM snapshot limitation).
hf-transfer accelerated weight download + container. Measured cold starts: 7.8s (Phi-3, T4), 13.3s (Llama-3 8B). Fine for small models on T4/A10, doesn't replace Hyperstack offload.Sources: Koyeb pricing, Koyeb scale-to-zero blog, Koyeb GPU scale-to-zero caveats, Northflank pricing, Northflank sandbox runtimes, RunPod Serverless, Beam Beta9 GitHub, Beam serverless platform guide, Inferless pricing, Salad pricing.
This section is generated from docs/quality-controls.json. Run bun run quality:readme after changing the quality gate manifest.
| Control | Automation | Enforcement | Purpose |
|---|---|---|---|
| AI quality fitness gate | bun run quality:fitness / bun run quality:fitness:debt | Blocking in quality:ai and CI | Project-specific boundaries: src/server/web isolation, forbidden infra imports, undeclared dependencies and shared UI reuse. Structural debt is visible through the debt/strict modes. |
| Architecture dependency graph | bun run quality:architecture / bun run quality:architecture:strict | Blocking in quality:ai | dependency-cruiser enforces publishable-library boundaries. The strict variant surfaces existing circular dependencies as ratchet warnings. |
| Typecheck, lint and security lint | bun run typecheck && bun run lint / bun run lint:warnings | Blocking in quality:ai and CI | TypeScript strict checks and ESLint errors block merges. lint:warnings exposes legacy style/security warnings as audit debt. |
| Static application security testing | CodeQL workflow and Semgrep project rules | Blocking on security findings in CI | Detects injection, XSS, path traversal, unsafe regex, SSRF-like patterns, weak crypto and other security classes common in AI-generated code. |
| Dependency and vulnerability review | bun audit, GitHub Dependency Review and OSV-Scanner | Blocking for critical/new vulnerable dependencies | Stops hallucinated, vulnerable or license-incompatible dependencies from entering pull requests. |
| Secret scanning | Gitleaks workflow plus GitHub secret scanning when enabled | Blocking in CI | Catches API keys, tokens and provider credentials before merge. |
| Container and Dockerfile scanning | Docker security workflow with Trivy plus supply-chain policy scan | Blocking for high/critical image findings; ratchet warnings for floating tags and curl-pipe-shell | Protects GPU images, Dockerfiles and deployment scripts from vulnerable bases and unsafe install patterns. |
| Dead code and dependency hygiene | bun run quality:deadcode / bun run quality:deadcode:strict | Manual/audit ratchet until current debt is triaged | Knip finds unused files, exports and dependencies so AI-generated scaffolding does not accumulate silently. |
| Property-based tests | bun run test:properties | Available as a focused gate for high-risk pure logic | fast-check generates many edge cases for invariants in routing, compatibility, parsing, cache keys and state machines. |
| Mutation testing | bun run quality:mutation | Deep gate via quality:ai:deep | Stryker verifies whether tests actually fail when behavior changes, instead of trusting coverage alone. |
| Contract/API drift checks | Existing contract unit tests plus planned OpenAPI/Pact gate | Unit gate today; OpenAPI/Pact can become blocking after specs are generated | Prevents SDK, HTTP routes and WebSocket payload expectations from drifting across Gateway clients and server handlers. |
| CI supply-chain policy | bun run quality:supply-chain | Blocking in quality:ai for hard failures; warnings ratcheted with --strict | Audits GitHub Actions permissions, action pinning, floating refs, Docker latest tags and unsafe shell installers. |
| Provenance and SBOM | GitHub artifact attestations and SBOM upload in release workflows | Release hardening target | Creates verifiable build provenance for published artifacts and Docker images. |
bun run quality:ai # typecheck + lint + build + fitness + supply-chain + architecture
bun run quality:ai:test # quality:ai plus the unit suite
bun run quality:ai:deep # quality:ai:test plus mutation testing
bun run quality:fitness:debt # inspect current complexity/module-size structural debt
bun run quality:fitness:strict # fail on current complexity/module-size ratchet warnings
bun run quality:supply-chain # audit CI/Docker supply-chain hardening
bun run quality:architecture:strict # include circular dependency warnings
bun run quality:deadcode # Knip dead-code/dependency audit without failing on existing debt
bun run quality:deadcode:strict # fail on Knip issues after the debt is triaged
bun run test:properties # property-based invariant tests
bun run build # tsup → dist/ (ESM + CJS + declarations + sourcemaps)
bun run deploy:prod # from a clean checkout of origin/main
Production is a single Railway instance, so a publish restarts it. The script refuses during a parle class (the declared
warmSchedule, Mon–Thu 17:30–20:30 Europe/Paris; --during-class overrides it for an emergency), a dirty tree or a
HEAD that is not origin/main; then it stamps the build, runs railway up and waits until /health reports the new
commit (plus fallbackProbe with AI_GATEWAY_ADMIN_KEY set). Never call railway up by hand.
initial_promptThe gateway's STT pipeline uses Whisper's initial_prompt parameter to anchor transcription to domain-specific vocabulary. The following research informs our implementation:
| Technique | Effect | Status |
|---|---|---|
| Rolling prior-segment context (sliding window) | Most effective — conditions each segment on recent transcription | Implemented |
| LLM-expanded domain prompt (title → 2-3 sentences) | ~17% relative WER reduction on domain-specific content | Implemented |
| Glossary term injection | Biases spelling of technical vocabulary | Implemented |
condition_on_previous_text=True | Enables decoder to attend to prior output | Default in pipeline |
Ground truth: professional human subtitles (TEDx manual captions). WER-sem = WER after LLM normalization (removes style differences: numbers, abbreviations, punctuation).
| Video | Category | WER-sem (no prompt) | WER-sem (rolling + seed) | Δ |
|---|---|---|---|---|
| Les biais cognitifs | Psychology | 28.5% | 20.1% | −8.4pp ✅ |
| Le désir, moteur de nos vies | Philosophy | 23.3% | 17.3% | −6.0pp ✅ |
| Étienne Klein — Contre la montre | Physics/Philosophy | 15.3% | 16.6% | +1.3pp ≈ |
| Pourquoi sommes-nous fascinés par les fictions | Humanities | 13.7% | 19.1% | +5.4pp ⚠️ |
| Average | 20.2% | 18.3% | −1.9pp |
Key findings:
Rolling context alone is the most impactful technique. Without it, WER is ~5pp higher on average. It resolves cross-boundary phrases and maintains spelling consistency chunk-to-chunk.
Seed context (LLM-expanded title) helps domain-specific talks significantly — Psychology (−8.4pp) and Philosophy (−6.0pp) benefit because the prompt anchors domain vocabulary (e.g. biais cognitif, heuristique, Schopenhauer).
Seed context does NOT help — or hurts — general-vocabulary talks (Klein, Fictions). Two mechanisms:
Prompt style matters: vocabulary-dense prompts outperform register-only prompts for technical content. Register-only prompts ("Conférence TEDx en français...") are neutral at best.
Raw WER increases slightly with seed prompts (~1-2pp) even when WER-sem improves — the prompt shifts Whisper's output style (more formal, different punctuation), which doesn't match the reference literally but is semantically correct.
Practical rule for the "Session title" field:
initial_prompt significantly improves recognition of OOV vocabulary.Private — Part of the Parle ecosystem.
Standalone AI Gateway — providers, autoscaler, GPU infra, observability, caching, proxy
TypeScript
0
599 commits
updated Oct 15, 2026
A self-hosted, cost-efficient AI infrastructure platform that provides:
Built with TypeScript, providing a unified interface for AI providers and multi-tier GPU autoscaling with automatic failover.
POST /v1/speech endpoint handles STT → LLM → TTS. Transport (GPU vs cloud) is hidden from the caller. No SSE/WebSocket/WebRTC in client code.PUT /v1/deployments/:name with any Docker image (or a profile like qwen3-tts) → autoscaled replicas, scale to zero, cold-start wait, per-deployment replica bounds via API. See docs/deployments.md# Workspace dependency (in monorepo)
bun add @parle/ai-gateway@workspace:*
Send audio, get back transcription + translation + TTS audio in one JSON response. The gateway handles GPU vs cloud routing transparently.
# Full pipeline: audio in → JSON out
curl -X POST "http://localhost:4000/v1/speech?source=fr&target=en&speaker=Ryan" \
--data-binary @audio.wav -H "Content-Type: audio/wav"
# Response:
# {
# "transcription": "Bonjour le monde",
# "response": "Hello world",
# "audio_base64": "UklGR...",
# "content_type": "audio/wav",
# "timing": { "total_ms": 1234, "used_gpu": true }
# }
import { createAIClient } from '@ai-gateway/client';
const client = createAIClient({ registry, defaultProfile });
// Full pipeline — tries GPU first, falls back to cloud automatically
const result = await client.pipeline(audioBuffer, systemPrompt);
// result.stt.text → "Bonjour le monde"
// result.chat.content → "Hello world"
// result.tts.audio → Buffer (WAV)
// result.usedGpu → true/false
// result.totalLatencyMs → 1234
// Individual stages (also transport-transparent)
const stt = await client.transcribe(audioBuffer); // { text, latencyMs }
const chat = await client.chat(messages); // { content, latencyMs }
const tts = await client.synthesize(text); // { audio, contentType }
SSE, WebSocket, and WebRTC are blocked at the proxy level (return 410 Gone). All client code uses the JSON pipeline endpoint or the AIClient API. This keeps transport concerns inside the gateway — callers never parse SSE events, manage WebSocket frames, or negotiate WebRTC.
import { createGateway } from '@ai-gateway';
const gateway = createGateway({
storage: myStorageAdapter, // implements GatewayStorage
stateStore: redisAdapter, // optional, falls back to in-memory
hooks: { // optional observability
onScaleUp: (e) => console.log('GPU booting:', e),
onCostAlert: (e) => console.log('Budget alert:', e),
},
});
const status = await gateway.handleGet(userId);
const result = await gateway.handleAction(userId, 'boot', { tier: 0 });
import { groqLLM, openaiSTT, openaiTTS } from '@ai-gateway/providers';
// Individual providers
const stt = openaiSTT({ apiKey: '...' });
const llm = groqLLM({ apiKey: '...' });
const tts = openaiTTS({ apiKey: '...' });
const transcript = await stt.transcribe({ audio: audioBuffer });
const response = await llm.chat({ messages: [{ role: 'user', content: transcript.text }] });
const audio = await tts.synthesize({ text: response.text, voice: 'alloy' });
import { withProviderFallback } from '@ai-gateway/providers';
const result = await withProviderFallback(
[groqLLM({ apiKey: '...' }), openaiLLM({ apiKey: '...' })],
(provider) => provider.chat({ messages }),
{ maxRetries: 2, cooldownMs: 30_000 }
);
import { RunPodClient, TensorDockClient } from '@ai-gateway/gpu-providers';
const runpod = new RunPodClient({ apiKey: '...' });
const pod = await runpod.boot({
gpuType: 'RTX A5000',
dockerImage: 'marcosremar/parle-s2s:latest',
ports: ['8000/http', '22/tcp'],
});
await runpod.waitForReady(pod.id);
await runpod.terminate(pod.id);
| Import Path | Description |
|---|---|
@ai-gateway | Full API (all modules re-exported) |
@ai-gateway/browser | Browser SDK — SpeechClient (legacy, see transport policy) |
@ai-gateway/providers | AI providers — OpenAI, Groq, Fireworks, etc. |
@ai-gateway/autoscaler | GPU autoscaling engine |
@ai-gateway/gpu-providers | Cloud GPU clients — RunPod, TensorDock, Modal |
@ai-gateway/handlers | Next.js route handler utilities |
@ai-gateway/client | AIClient — profile-based unified API |
@ai-gateway/tracking | Benchmark & spend tracking |
@ai-gateway/adapters | State persistence — InMemory, Redis |
@ai-gateway/auth | GPU token signing/verification (HMAC) |
@ai-gateway/infra | SkyPilot CLI, SSH/SCP utilities |
@ai-gateway/benchmarking | Health checks, latency benchmarks |
┌─────────────────┐
│ createGateway │ ← Single entry point
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌───────▼──────┐ ┌─────▼─────┐ ┌───────▼──────┐
│ Autoscaler │ │ Providers │ │ Handlers │
│ Engine │ │ Registry │ │ (Next.js) │
└───────┬──────┘ └─────┬─────┘ └──────────────┘
│ │
┌────────┼────────┐ │
│ │ │ │
┌──▼──┐ ┌──▼──┐ ┌───▼─┐ ┌▼────────────┐
│RunPod│ │TD │ │Modal│ │OpenAI, Groq,│
│ │ │ │ │ │ │Fireworks,...│
└──────┘ └─────┘ └─────┘ └─────────────┘
| Provider | STT | TTS | LLM | Image | Realtime |
|---|---|---|---|---|---|
| OpenAI | Whisper | TTS-1/HD | GPT-4o | DALL-E 3 | Realtime API |
| Groq | Whisper | PlayAI | LLaMA, Mixtral | - | - |
| Fireworks | Whisper | - | LLaMA, Mixtral | Stable Diffusion | - |
| OpenRouter | - | - | 200+ models | Flux, DALL-E | - |
| Modal | - | MOSS-TTS | - | - | - |
| Self-hosted | Generic | Generic | Generic | - | - |
| Provider | Status | Notes |
|---|---|---|
| RunPod | Working | Primary. ~31s boot. Use HTTP+TCP ports. |
| TensorDock | Working | RTX 3090 inference backend. |
| Modal | Working | Serverless, auto-scales to zero. |
| Hyperstack | Working | KVM w/ nested virt (/dev/kvm confirmed). Custom image build pipeline wired. offloadOnIdle = 1.3s wake. |
| Vast.ai | Improved | Desktop offer policy (reliability ≥0.95, download >1 Gbps, ≤~$0.20/hr) + in-memory hot pool (claim/release). Direct HTTP still often needs SSH proxy on residential hosts; prefer offerPolicy: 'desktop' for quality. |
| SkyPilot | Working | Multi-cloud orchestration. |
These offer sandboxed GPU runtimes (sub-second cold start), per-second billing, and fit the Modal-style scale-up model we could adopt instead of rolling our own. Priority in order of fit:
| Provider | Runtime | Cheapest GPU | Cold start (GPU) | Free trial | Worth testing? |
|---|---|---|---|---|---|
| Koyeb | Firecracker + Cloud Hypervisor (GPU) | L4 $0.70/hr, L40S $1.20/hr | seconds (GPU VRAM snapshot is WIP, CPU gets 200ms) | $10 / 7d Pro trial, GPU eligible | Yes — free trial covers PoC |
| Northflank | Firecracker / Kata / gVisor (choose per workload) | L4 $0.80/hr, A100 40G $1.42/hr | 1–2 s end-to-end | Sandbox plan free (CPU only); GPU requires pay-as-you-go | Yes if you need runtime choice |
| RunPod Serverless | Proprietary (FlashBoot) | T4 ~$0.20/hr active, L4/L40S ~$0.40–0.70/hr active | <200ms in 48% of calls | Community credits occasionally | Yes — currently the best latency/$ in the market |
| Beam / Beta9 | runc or gVisor (runtime configurable) + custom CLIP lazy image + cuda-checkpoint | ~$0.70–1.00/hr | 2–3 s typical, 50ms warm | $30 credit | Yes — also open source (AGPL), self-hostable as beta9 |
| Inferless | Container (no microVM, no CRIU) — just hf-transfer accelerated weight download | T4 $0.33/hr, A10 $0.61/hr | Llama-3 8B 13.3s / Phi-3 7.8s (measured, T4) | $30 credit + 10h free | Low priority — weight-loading only |
| Salad.com | Distributed marketplace (no sandbox) | RTX 4090 $0.204/hr | marketplace (unreliable like Vast) | — | Batch only, not latency-sensitive |
| CoreWeave | Bare-metal | L40S $2.25/hr | ~200ms bare-metal | — | Expensive but reliable |
| Fly.io GPU | Firecracker + Cloud Hypervisor | L40S $1.25/hr | ~30s / 7B model | — | ❌ Deprecated Aug 2026 |
Measured comparison baseline (Hyperstack L40 + our offloadOnIdle): 1.3s wake on the same VM. Competitive with Northflank (1–2s). Beaten by RunPod Serverless FlashBoot (<200ms). Koyeb GPU currently does not deliver its 200ms CPU story for GPU workloads (VRAM snapshot limitation).
hf-transfer accelerated weight download + container. Measured cold starts: 7.8s (Phi-3, T4), 13.3s (Llama-3 8B). Fine for small models on T4/A10, doesn't replace Hyperstack offload.Sources: Koyeb pricing, Koyeb scale-to-zero blog, Koyeb GPU scale-to-zero caveats, Northflank pricing, Northflank sandbox runtimes, RunPod Serverless, Beam Beta9 GitHub, Beam serverless platform guide, Inferless pricing, Salad pricing.
This section is generated from docs/quality-controls.json. Run bun run quality:readme after changing the quality gate manifest.
| Control | Automation | Enforcement | Purpose |
|---|---|---|---|
| AI quality fitness gate | bun run quality:fitness / bun run quality:fitness:debt | Blocking in quality:ai and CI | Project-specific boundaries: src/server/web isolation, forbidden infra imports, undeclared dependencies and shared UI reuse. Structural debt is visible through the debt/strict modes. |
| Architecture dependency graph | bun run quality:architecture / bun run quality:architecture:strict | Blocking in quality:ai | dependency-cruiser enforces publishable-library boundaries. The strict variant surfaces existing circular dependencies as ratchet warnings. |
| Typecheck, lint and security lint | bun run typecheck && bun run lint / bun run lint:warnings | Blocking in quality:ai and CI | TypeScript strict checks and ESLint errors block merges. lint:warnings exposes legacy style/security warnings as audit debt. |
| Static application security testing | CodeQL workflow and Semgrep project rules | Blocking on security findings in CI | Detects injection, XSS, path traversal, unsafe regex, SSRF-like patterns, weak crypto and other security classes common in AI-generated code. |
| Dependency and vulnerability review | bun audit, GitHub Dependency Review and OSV-Scanner | Blocking for critical/new vulnerable dependencies | Stops hallucinated, vulnerable or license-incompatible dependencies from entering pull requests. |
| Secret scanning | Gitleaks workflow plus GitHub secret scanning when enabled | Blocking in CI | Catches API keys, tokens and provider credentials before merge. |
| Container and Dockerfile scanning | Docker security workflow with Trivy plus supply-chain policy scan | Blocking for high/critical image findings; ratchet warnings for floating tags and curl-pipe-shell | Protects GPU images, Dockerfiles and deployment scripts from vulnerable bases and unsafe install patterns. |
| Dead code and dependency hygiene | bun run quality:deadcode / bun run quality:deadcode:strict | Manual/audit ratchet until current debt is triaged | Knip finds unused files, exports and dependencies so AI-generated scaffolding does not accumulate silently. |
| Property-based tests | bun run test:properties | Available as a focused gate for high-risk pure logic | fast-check generates many edge cases for invariants in routing, compatibility, parsing, cache keys and state machines. |
| Mutation testing | bun run quality:mutation | Deep gate via quality:ai:deep | Stryker verifies whether tests actually fail when behavior changes, instead of trusting coverage alone. |
| Contract/API drift checks | Existing contract unit tests plus planned OpenAPI/Pact gate | Unit gate today; OpenAPI/Pact can become blocking after specs are generated | Prevents SDK, HTTP routes and WebSocket payload expectations from drifting across Gateway clients and server handlers. |
| CI supply-chain policy | bun run quality:supply-chain | Blocking in quality:ai for hard failures; warnings ratcheted with --strict | Audits GitHub Actions permissions, action pinning, floating refs, Docker latest tags and unsafe shell installers. |
| Provenance and SBOM | GitHub artifact attestations and SBOM upload in release workflows | Release hardening target | Creates verifiable build provenance for published artifacts and Docker images. |
bun run quality:ai # typecheck + lint + build + fitness + supply-chain + architecture
bun run quality:ai:test # quality:ai plus the unit suite
bun run quality:ai:deep # quality:ai:test plus mutation testing
bun run quality:fitness:debt # inspect current complexity/module-size structural debt
bun run quality:fitness:strict # fail on current complexity/module-size ratchet warnings
bun run quality:supply-chain # audit CI/Docker supply-chain hardening
bun run quality:architecture:strict # include circular dependency warnings
bun run quality:deadcode # Knip dead-code/dependency audit without failing on existing debt
bun run quality:deadcode:strict # fail on Knip issues after the debt is triaged
bun run test:properties # property-based invariant tests
bun run build # tsup → dist/ (ESM + CJS + declarations + sourcemaps)
bun run deploy:prod # from a clean checkout of origin/main
Production is a single Railway instance, so a publish restarts it. The script refuses during a parle class (the declared
warmSchedule, Mon–Thu 17:30–20:30 Europe/Paris; --during-class overrides it for an emergency), a dirty tree or a
HEAD that is not origin/main; then it stamps the build, runs railway up and waits until /health reports the new
commit (plus fallbackProbe with AI_GATEWAY_ADMIN_KEY set). Never call railway up by hand.
initial_promptThe gateway's STT pipeline uses Whisper's initial_prompt parameter to anchor transcription to domain-specific vocabulary. The following research informs our implementation:
| Technique | Effect | Status |
|---|---|---|
| Rolling prior-segment context (sliding window) | Most effective — conditions each segment on recent transcription | Implemented |
| LLM-expanded domain prompt (title → 2-3 sentences) | ~17% relative WER reduction on domain-specific content | Implemented |
| Glossary term injection | Biases spelling of technical vocabulary | Implemented |
condition_on_previous_text=True | Enables decoder to attend to prior output | Default in pipeline |
Ground truth: professional human subtitles (TEDx manual captions). WER-sem = WER after LLM normalization (removes style differences: numbers, abbreviations, punctuation).
| Video | Category | WER-sem (no prompt) | WER-sem (rolling + seed) | Δ |
|---|---|---|---|---|
| Les biais cognitifs | Psychology | 28.5% | 20.1% | −8.4pp ✅ |
| Le désir, moteur de nos vies | Philosophy | 23.3% | 17.3% | −6.0pp ✅ |
| Étienne Klein — Contre la montre | Physics/Philosophy | 15.3% | 16.6% | +1.3pp ≈ |
| Pourquoi sommes-nous fascinés par les fictions | Humanities | 13.7% | 19.1% | +5.4pp ⚠️ |
| Average | 20.2% | 18.3% | −1.9pp |
Key findings:
Rolling context alone is the most impactful technique. Without it, WER is ~5pp higher on average. It resolves cross-boundary phrases and maintains spelling consistency chunk-to-chunk.
Seed context (LLM-expanded title) helps domain-specific talks significantly — Psychology (−8.4pp) and Philosophy (−6.0pp) benefit because the prompt anchors domain vocabulary (e.g. biais cognitif, heuristique, Schopenhauer).
Seed context does NOT help — or hurts — general-vocabulary talks (Klein, Fictions). Two mechanisms:
Prompt style matters: vocabulary-dense prompts outperform register-only prompts for technical content. Register-only prompts ("Conférence TEDx en français...") are neutral at best.
Raw WER increases slightly with seed prompts (~1-2pp) even when WER-sem improves — the prompt shifts Whisper's output style (more formal, different punctuation), which doesn't match the reference literally but is semantically correct.
Practical rule for the "Session title" field:
initial_prompt significantly improves recognition of OOV vocabulary.Private — Part of the Parle ecosystem.