Skip to content

πŸ¦‹ Nika 0.58.0 β€” Serve V2

Choose a tag to compare

@github-actions github-actions released this 31 Mar 23:28

πŸ¦‹ Nika 0.58.0 β€” Serve V2

Inference as Code Β· March 31, 2026 Β· 26 commits

πŸ§ͺ Tests πŸ”§ Builtins πŸ“¦ Transforms 🌐 Providers
9,109 35 31 9

✧ infer Β· ⎈ exec Β· β˜„ fetch Β· βŠ› invoke Β· ❋ agent


⚑ Production-Grade Serve

v0.56.0 introduced nika serve β€” a REST API for running workflows over HTTP. It worked. It also had a UUID collision bug at 4.2M jobs, leaked environment variables to subprocesses, panicked when workers crashed, and let two instances corrupt the same database.

v0.58.0 fixes all of that. This is Serve V2: the version you'd actually deploy behind a load balancer.


πŸ—οΈ The Architecture

nika serve now supports two execution modes. Choose based on your deployment model:

Mode How it works Best for
EmbeddedExecutor Workflows run in-process, inside the Axum server's tokio runtime Single-machine deployments. Low latency. Shared memory.
SubprocessExecutor Each job spawns nika run as a child process with process-group isolation Multi-tenant. Crash isolation. Memory limits per job.
# Embedded (default) β€” in-process, fast
nika serve --bind 0.0.0.0:3000

# Subprocess β€” isolated, safe for multi-tenant
NIKA_SERVE_EXECUTOR=subprocess nika serve --bind 0.0.0.0:3000

The job queue is now atomic. A single AtomicUsize counter replaces two separate database queries for checking concurrency limits. When 20 requests arrive simultaneously, exactly max_concurrent get through β€” not 20.

πŸ” NikaVault v2 β€” Encrypted Secrets at Scale

Secrets management got a complete rewrite. NikaVault v2 stores versioned, multi-field credentials encrypted with XChaCha20Poly1305 + Argon2i key derivation. But the real feature is the $vault binding source:

tasks:
  - id: call_api
    with:
      key: $vault.ELEVENLABS_API_KEY
    fetch:
      url: "https://api.elevenlabs.io/v1/text-to-speech"
      headers:
        xi-api-key: "{{with.key}}"

No more $env.SECRET_KEY leaking through process environment. Vault secrets are resolved at binding time, never exposed to subprocesses or traces.

Manage from the CLI:

nika vault set ELEVENLABS_API_KEY sk-xxx-your-key
nika vault list
nika vault get ELEVENLABS_API_KEY
nika vault delete ELEVENLABS_API_KEY

For teams, the Doppler backend connects NikaVault to your existing secrets infrastructure:

export NIKA_VAULT_BACKEND=doppler
export DOPPLER_TOKEN=dp.st.xxx
# Vault reads from Doppler, writes stay local

Every vault operation is logged in an audit trail β€” who accessed which secret, when.

πŸ›‘οΈ Four Security Patches

These aren't theoretical. They were found during a 10-agent adversarial audit of the serve layer.

🌐 CORS Lockdown

Before: CorsLayer::allow_origin(Any) β€” any website could make authenticated requests to your Nika server via browser JavaScript. Classic CSRF vector.

After: No CORS headers by default. Opt-in with NIKA_SERVE_CORS_ORIGIN=https://your-app.com.

πŸ”‘ Auth Token Minimum

Before: NIKA_SERVE_TOKEN=abc was accepted. Three characters. Brute-forceable.

After: Minimum 16 characters. Empty or short tokens rejected at startup with a clear error message.

πŸ”’ Environment Isolation

Before: Worker subprocesses inherited the server's full environment via env_remove denylist. Any new secret added to the server leaked to workflows automatically.

After: env_clear() + explicit allowlist. Only PATH, HOME, API keys, and NIKA_* vars pass through. Everything else is blocked.

πŸ—„οΈ Database Locking

Before: Two nika serve instances on the same machine could both write to the same SQLite database simultaneously, causing corruption.

After: flock() on <db>.lock enforces single-instance per database file.

⚑ SSE Streaming + Job Lifecycle

Real-time job progress via Server-Sent Events. Connect with Accept: text/event-stream and get structured events as tasks execute:

# Submit a job and stream events
curl -N http://localhost:3000/v1/events/JOB_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: text/event-stream"

# Events:
# data: {"type":"TaskStarted","task_id":"research"}
# data: {"type":"InferChunk","text":"The key finding..."}
# data: {"type":"TaskCompleted","task_id":"research","duration_ms":2340}
# data: {"type":"JobCompleted","output":"..."}

Job garbage collection runs automatically. Completed and failed jobs older than the configurable TTL (default: 7 days) are cleaned up. No manual maintenance.

Cancel kills the process. Before v0.58.0, cancelling a job only killed the tokio task β€” the child process kept running. Now we store the child PID in WorkerHandle and send SIGTERM, then SIGKILL after a grace period. On shutdown, workers get SIGTERM via tokio::select! instead of running until the 30-second drain timeout.

πŸš€ Performance: mimalloc

Switched from the system allocator to mimalloc. Consistent performance across macOS, Linux, and Windows. Particularly noticeable under high concurrency where the system allocator's per-thread arenas fragment.

πŸ”§ Every Fix in Detail

Bug Impact Fix
UUID collision 44-bit IDs collide at ~4.2M jobs Full 128-bit UUID (32 hex chars)
WorkerGuard panic Crashed workers left stale state Drop guard: mark failed + decrement counter + cleanup map
Queue race 20 simultaneous requests all pass concurrency check AtomicUsize counter replaces dual DB queries
Unbounded output Verbose workflows cause OOM Piped reads capped at max_output_bytes
Shutdown leaks workers Workers run until 30s drain tokio::select! races subprocess vs shutdown signal
Cancel leaks subprocess abort() only killed tokio task Store PID, send SIGTERM/SIGKILL to process group
Cancel race Completed job overwritten as cancelled Check cancelled before writing terminal status
Drain timeout Workers detached after 30s, not aborted Abort remaining handles after drain
Windows child.kill() Moved child reference Restructured to piped reads + child.wait()
Inputs silently dropped RunRequest.inputs accepted but ignored Forward as --input key=value CLI args
Interactive hang Subprocess prompts block forever Added -y --no-interactive flags
Deprecated set_var std::env::set_var() deprecated Direct ServeConfig construction

πŸ“Š By the Numbers

πŸ“Š Serve V2 β€” What Changed
β”œβ”€β”€ πŸ” Security patches: 4 (CORS, auth, env, flock)
β”œβ”€β”€ πŸ› Reliability fixes: 12 (every race condition found)
β”œβ”€β”€ ✨ New features: 10 (vault v2, SSE, GC, mimalloc, ...)
β”œβ”€β”€ πŸ§ͺ New tests: +16 (9,109 total)
└── πŸ—οΈ Architecture: EmbeddedExecutor + SubprocessExecutor

πŸ“¦ Install

Method Command
πŸš€ Quick curl -fsSL https://raw.githubusercontent.com/supernovae-st/nika/main/install.sh | sh
🍺 Homebrew brew install supernovae-st/tap/nika
πŸ“¦ npm npx @supernovae-st/nika
πŸ¦€ Cargo cargo install nika
🐳 Docker docker run --rm ghcr.io/supernovae-st/nika:0.58.0
πŸ’» VS Code Search "Nika" or ext install supernovae.nika-lang
πŸͺŸ Scoop scoop bucket add nika https://github.com/supernovae-st/scoop-nika && scoop install nika
🐧 AUR yay -S nika-bin

πŸ¦‹ Nika Evolution
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
v0.42  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘  8,188 tests
v0.48  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘  8,200 tests
v0.52  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘  8,938 tests
v0.56  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘  9,093 tests
v0.58  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  9,109 tests ← you are here
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

πŸ”§ 35 builtins Β· πŸ“¦ 31 transforms Β· 🌐 7+1+1 providers Β· πŸ¦€ 15 crates

Made with πŸ’œ by SuperNovae Studio β€” Open Source, AGPL-3.0

Full Changelog: v0.56.2...v0.58.0