π¦ Nika 0.64.0 β The Mega Sprint
π¦ Nika 0.64.0 β The Mega Sprint
Inference as Code Β· April 3-4, 2026 Β· 60+ commits
| π§ͺ Tests | π§ Builtins | π¦ Transforms | π Providers |
|---|---|---|---|
| 9,870 | 43 | 39 | 9 |
β§ infer Β· β exec Β· β fetch Β· β invoke Β· β agent
β¨ The biggest Nika release ever. Sixty commits across two days that transformed what the engine can do. OpenAPI spec generation. Conditional task execution. Ten new data transforms. RAG-ready text chunking. A full crawl intelligence suite with HTTP caching, cookies, robots.txt compliance, and per-domain rate limiting. Skills that finally work on infer tasks. Twenty new builtin tools. And a security hardening pass that closed ten vulnerabilities. This is the release where Nika went from "interesting prototype" to "production-grade workflow engine."
π OpenAPI 3.1 + Scalar API Explorer
nika serve now auto-generates a full OpenAPI 3.1 specification at GET /v1/openapi.json, powered by the aide crate. Every endpoint has stable operation IDs, typed request/response schemas, and Bearer auth security documentation. SDK code generation just works.
And at GET /v1/docs, you get Scalar -- an interactive API explorer where you can test endpoints directly in the browser. No Postman needed, no curl gymnastics.
nika serve
# Open http://localhost:3000/v1/docs in your browser| Endpoint | Operation ID | Description |
|---|---|---|
POST /v1/run |
submitWorkflow |
Submit workflow for execution (SSE streaming) |
GET /v1/status/{id} |
getJobStatus |
Poll job state + output |
POST /v1/cancel/{id} |
cancelJob |
Cancel a running job |
GET /v1/jobs |
listJobs |
Paginated job listing with cursor |
GET /v1/jobs/{id}/artifacts |
listArtifacts |
List job artifacts |
GET /v1/workflows |
listWorkflows |
Available workflows |
GET /v1/workflows/{name}/source |
getWorkflowSource |
Raw YAML source |
Tip
The OpenAPI spec includes SSE event type documentation in the submitWorkflow operation. Use it to generate type-safe clients in TypeScript, Python, or any language with OpenAPI tooling.
π¦ 10 New Pipe Transforms
Data reshaping without LLM calls. These ten transforms eliminate the most common "I need an infer task just to restructure JSON" patterns:
| Transform | What it does | Example |
|---|---|---|
pluck(field) |
Extract one field from array of objects | {{with.users | pluck(name)}} -> ["Alice", "Bob"] |
where(field, val) |
Filter array by field value | {{with.items | where(status, active)}} |
pick(f1, f2) |
Keep only named fields | {{with.user | pick(name, email)}} |
omit(f1, f2) |
Remove named fields | {{with.user | omit(password, token)}} |
sort_by(field) |
Sort objects by field | {{with.items | sort_by(price)}} |
group_by(field) |
Group into object by key | {{with.pages | group_by(domain)}} |
merge |
Deep merge array of objects | {{with.configs | merge}} |
regex(pattern) |
Extract first regex match | {{with.text | regex(v[0-9]+)}} |
base64_encode |
Encode to Base64 | {{with.data | base64_encode}} |
base64_decode |
Decode from Base64 | {{with.encoded | base64_decode}} |
Plus the where() operators. The where transform supports six comparison operators beyond simple equality:
# Equality (default): | where(status, active)
# Not equal: | where(status, !=, archived)
# Greater than: | where(depth, >, 3)
# Less than: | where(price, <, 100)
# Greater or equal: | where(score, >=, 80)
# Less or equal: | where(count, <=, 10)β¨ 5 More Transforms: String Tests, Hashing, URL Dedup
| Transform | What it does |
|---|---|
starts_with(str) |
Returns true if string starts with prefix |
ends_with(str) |
Returns true if string ends with suffix |
contains(str) |
Returns true if string contains substring |
content_hash |
BLAKE3 hash for cache keys and deduplication |
unique_urls |
Deduplicate and normalize URL arrays |
π 5 URL Transforms
Purpose-built for crawl and SEO workflows where you need to dissect URLs without jq:
| Transform | Input | Output |
|---|---|---|
url_host |
https://docs.example.com/api?v=2 |
docs.example.com |
url_path |
https://docs.example.com/api?v=2 |
/api |
url_without_query |
https://docs.example.com/api?v=2 |
https://docs.example.com/api |
url_normalize |
HTTPS://Docs.Example.Com/api?b=2&a=1 |
https://docs.example.com/api?a=1&b=2 |
slice(start, end) |
"hello" | slice(1, 3) |
"el" (also works on arrays) |
π jq() Pipe Transform
For when the other transforms aren't enough -- inline jq expressions directly in your templates via the jaq-core engine:
with:
summary: "{{with.data | jq([.[] | select(.active) | .name])}}"Note
The | jq() transform works for simple expressions. For complex multi-line queries, use the nika:jq builtin tool (added in v0.65.0) which bypasses the template parser entirely.
β‘ when: Conditional Tasks
Skip tasks based on runtime conditions without breaking the DAG. The when: field evaluates an expression and skips the task (with a TaskSkipped event) if it's falsy -- downstream dependencies still resolve correctly.
- id: check_count
with: { pages: $crawl }
invoke:
tool: nika:jq
params:
data: "{{with.pages}}"
expr: "length"
- id: analyze_deep_pages
when: "{{with.count | to_number}} > 0"
with:
count: $check_count
pages: $filter_deep
infer: "Analyze these {{with.count}} deep pages..."The when: expression goes through the same template engine as everything else -- you can use bindings, transforms, and comparisons. If the result is false, 0, "", or null, the task is skipped.
π§ RAG Tools: nika:chunk + nika:token_count
Two builtins that make Retrieval-Augmented Generation workflows first-class:
nika:chunk splits text into chunks by token count with configurable overlap. It's paragraph-aware, so it tries to break at natural boundaries rather than mid-sentence:
- id: chunk_document
with: { doc: $load_pdf }
invoke:
tool: nika:chunk
params:
text: "{{with.doc}}"
max_tokens: 500
overlap: 50nika:token_count gives you fast token estimation (cl100k_base compatible) without calling an API:
- id: estimate_cost
with: { text: $document }
invoke:
tool: nika:token_count
params:
text: "{{with.text}}"π§ 8 New Data Builtins
Tools for mechanical data operations that don't need an LLM:
| Tool | Purpose | Example use |
|---|---|---|
| nika:map | Transform array elements | Extract + uppercase names from objects |
| nika:filter | Filter by field conditions | Keep items where status == "active" |
| nika:group_by | Group array by field | Cluster pages by domain |
| nika:aggregate | Sum, avg, min, max, count | Calculate statistics without LLM |
| nika:json_verify | Translation validator | Detect missing keys, broken placeholders |
| nika:yaml_validate | Batch field checker | Verify dot-path fields exist in YAML/JSON |
| nika:json_flatten / unflatten | Dot-notation conversion | Full roundtrip fidelity |
| nika:locale_lookup | BCP-47 code mapping | Convert locale codes for translation APIs |
nika:map deserves special attention. It supports both selector (extract a field) and transform (apply pipe chains) parameters:
invoke:
tool: nika:map
params:
data: "{{with.pages | to_json}}"
selector: url
transform: "| url_path | split('/') | compact | length"
output_field: depthThis computes URL depth for every page -- zero LLM calls, pure data transformation.
π§ More New Builtins
| Tool | Purpose |
|---|---|
| nika:json_merge | Deep merge multiple JSON objects |
| nika:set_diff | Compute difference between two arrays |
| nika:zip | Zip two arrays into pairs |
| nika:json_query | JSONPath queries on in-memory JSON |
| nika:enrich | Add computed fields to array elements |
| nika:fetch (agent) | HTTP requests inside agent: loops |
π·οΈ Crawl Intelligence Suite
Four new capabilities that turn fetch: from "make an HTTP request" into a proper web crawling engine:
HTTP Cache Infrastructure
- id: crawl_page
fetch:
url: "{{with.page_url}}"
cache: true # ETag/Last-Modified support
session: my_crawl # Named session with cookie jar
extract: articleWhen cache: true is set, Nika stores response ETags and Last-Modified headers. On subsequent requests to the same URL, it sends If-None-Match / If-Modified-Since headers. If the server returns 304, the cached response is reused -- saving bandwidth and respecting server resources.
Cookie Jars
The session: field creates a named HTTP session that persists cookies across multiple fetch: tasks. Essential for crawling sites that require login or track state via cookies.
robots.txt Compliance
Nika now automatically fetches and respects robots.txt files. Before crawling a domain, it checks:
- Disallow rules -- Skipped paths return a descriptive error instead of a blocked request
- Crawl-delay -- Honored per-domain to respect server owner preferences
Per-Domain Rate Limiting
Automatic rate limiting per hostname prevents your workflows from overwhelming target servers. The rate limiter tracks active requests per domain and enforces configurable delays between requests.
Tip
Combine all four for production crawling: cache: true to avoid re-downloading unchanged pages, session: to maintain cookies, and the automatic robots.txt + rate limiting for responsible crawling.
π― Skills Auto-Injection (Behavior Change)
Workflow-level skills: were a broken promise. You'd declare them at the top of your workflow, they'd work in agent: tasks, but infer: tasks silently ignored them. Now fixed.
schema: "nika/workflow@0.12"
skills:
tone: ./skills/brand-voice.md # NOW injected into ALL infer tasks
tasks:
- id: draft
infer: "Write a tagline for {{inputs.product}}"
# brand-voice.md is auto-injected into the system prompt
# Previously: silently ignored, only worked in agent: tasksWarning
If you declared skills: at workflow level but never expected them to affect infer: tasks, your outputs may change. This is correct behavior -- the old behavior was a bug.
Also new: Missing skill files now cause a hard NIKA-270 error instead of being silently skipped. If ./skills/nonexistent.md doesn't exist, the workflow fails immediately with a clear message.
βοΈ Fetch Enhancements
extract: metadata_links -- A new extract mode that combines metadata + links in a single HTTP request. Returns title, description, canonical, hreflang, OG tags AND internal/external link classification. Saves one fetch per page in link discovery workflows.
response: slim -- Metadata-only response (status, URL, content-type) without body or headers. Ideal for link-checking workflows where you only need to know if a URL is alive.
response: full + extract: combo -- Previously rejected by the validator. Now you can extract content AND get the full response metadata (status, headers, redirect chain, timing).
New metadata fields: extract: metadata now returns favicon, author, robots, feeds, and resolves all relative URLs to absolute.
elapsed_ms in response:full -- Request timing included for performance monitoring.
Redirect chain tracking -- response: full includes the complete redirect chain for SEO audit trails.
π‘ Serve Enhancements
| Feature | Description |
|---|---|
| SSE event IDs | Monotonic IDs for client-side resume via Last-Event-ID |
| Cursor pagination | GET /v1/jobs with cursor-based pagination for large job lists |
| EventBus.publish() | Internal event bus for SSE fan-out to multiple clients |
| Source API | GET /v1/workflows/{name}/source returns raw YAML |
| Per-job scratch dir | .nika/jobs/<id>/ with NIKA_JOB_ID + NIKA_JOB_DIR env vars |
| Daemon exe path | ~/.nika/daemon/nika-exe-path survives binary upgrades |
π File + Read Enhancements
nika:readraw mode -- Read file content without line numbers (raw text output)nika:readoptional fallback --optional: truereturns a default value instead of failing on missing filesnika:writeaccepts Value content -- Pass JSON objects/arrays directly, not just strings- Auto-parse JSON from builtins -- Builtin tool outputs that are valid JSON are automatically parsed for downstream bindings
nika:writeoverwrite param --overwrite: truereplaces existing files instead of NIKA-215
β Validation + DX
nika checkvalidates skill + context file paths -- Catches missing files at validation timenika checkwarns on unknown providers (NIKA-033) -- Typos likeprovider: antropicget suggestionsnika checkrespects DO NOT OVERWRITE sentinel -- Rule files with the sentinel are preserved- LSP autocomplete -- All 39 transforms available in VS Code / JetBrains completion
- 14 E2E workflow tests -- Real YAML -> runner -> verify for all new transforms and builtins
for_eachlimit raised to 50K -- Up from 10K for large-scale data processing- Configurable
max_stdout-- Prevent OOM on verboseexec:outputs
π Security Fixes (10+ items)
| Fix | Description |
|---|---|
| π Token timing leak | SHA-256 hash before ct_eq prevents token length side-channel |
| π Artifact path containment | Uses project_root not workflows_dir for validation |
| βοΈ [policy] config threading | allowed_hosts from nika.toml now reaches the executor |
| π SSE subscribe TOCTOU | Single lock acquisition for channel existence + subscribe |
| ποΈ GC handle tracking | GC task handle stored for graceful shutdown |
| π« Null byte rejection | validate_workflow_path rejects null bytes |
| π X-RateLimit-Limit header | Reflects actual configured rate limit value |
| π Key redaction in verify errors | API keys no longer leaked in structured output errors |
| π€ Agent history turn count | Correct counting prevents premature termination |
| π Rate limiter GC + SlotGuard | Proper slot release on drop, garbage collection |
| π Daemon GetSecret auth | Auth token required for IPC secret retrieval |
π Fixes (20+ items)
Engine:
- π·οΈ
extract: sitemapon non-XML -- Returns empty result instead of failing on HTML 404 pages - π§
| default()on missing paths -- Properly triggers fallback instead of silent null - π
for_each+ artifact aggregation -- Correct array aggregation after all iterations - π
from_examplepath resolution -- Template paths resolve relative to project root - π‘οΈ Shell warning suppression -- No false "unescaped binding" warnings inside single quotes
- π’
aggregatecount -- Returns total array length, not just numeric items - π
json_unflattencollision -- Conflicting keys replace scalar with object - π
json_queryempty results -- Returns[]instead of null β οΈ EventLog eviction warning -- Warns instead of silent loss at capacity- β
allow_execin AcceptEdits --nika:writeworks under AcceptEdits mode
Serve:
- π’ Startup banner workflow count -- Correct recursive scan for nested directories
- π Subprocess stderr logging -- stderr from subprocesses logged at warn level
- π₯οΈ ANSI stripper -- Handles OSC escape sequences in subprocess output
Core:
- π IPv6 bracket stripping --
url_hostcorrectly strips[]from IPv6 addresses - π Relative hreflang resolution -- hreflang URLs resolved to absolute
- π
interpreter -c/-ein templates --python3 -c,node -eno longer trigger false NIKA-053
π‘ Full Example: Crawl Intelligence Pipeline
Everything in this release, working together:
schema: "nika/workflow@0.12"
workflow: crawl-intelligence
description: "Crawl, enrich, filter, and analyze"
provider: anthropic
model: claude-sonnet-4-20250514
skills:
seo: ./skills/seo-analyst.md # Auto-injected into infer tasks
inputs:
site_url: "https://docs.example.com/sitemap.xml"
tasks:
- id: crawl
fetch:
url: "{{inputs.site_url}}"
extract: sitemap
- id: enrich
with: { pages: $crawl }
invoke:
tool: nika:enrich
params:
data: "{{with.pages | to_json}}"
selector: url
transform: "| url_path | split('/') | compact | length"
output_field: depth
- id: deep_pages
with: { enriched: $enrich }
invoke:
tool: nika:filter
params:
data: "{{with.enriched | to_json}}"
field: depth
operator: ">"
value: 3
- id: stats
with: { pages: $deep_pages }
invoke:
tool: nika:aggregate
params:
data: "{{with.pages | pluck(depth) | to_json}}"
- id: analyze
when: "{{with.count | to_number}} > 0"
with:
pages: $deep_pages
count: "{{$deep_pages | length}}"
avg_depth: $stats.avg
infer: |
We found {{with.count}} deeply nested pages
(avg depth: {{with.avg_depth}}).
URLs: {{with.pages | pluck(url) | join('\n')}}
Identify navigation and information architecture issues.β¬οΈ Upgrade Notes
Warning
Skills behavior change. Workflow-level skills: are now injected into ALL infer: task system prompts, not just agent: tasks. If you declared skills but didn't expect them to affect infer tasks, your outputs may change.
Warning
Missing skill files now fail. skills: { ghost: ./nonexistent.md } was silently skipped. Now it's a hard NIKA-270 error.
Note
response: full + extract: combo was previously rejected. If you worked around this with two separate tasks, you can now combine them.
Everything else is backward compatible. New transforms, builtins, and when: are all additive.
π¦ Install
curl -fsSL https://raw.githubusercontent.com/supernovae-st/nika/main/install.sh | sh| Method | Command |
|---|---|
| πΊ 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.64.0 |
| π» VS Code | ext install supernovae.nika-lang |
| πͺ Scoop | scoop install nika via supernovae-st/scoop-nika |
| π§ AUR | yay -S nika-bin |
π All binaries: SHA256 checksums Β· SLSA provenance Β· macOS notarization
Made with π by SuperNovae Studio Β· Open Source, AGPL-3.0
Full Changelog: v0.63.0...v0.64.0