Skip to content

πŸ¦‹ Nika 0.64.0 β€” The Mega Sprint

Choose a tag to compare

@github-actions github-actions released this 03 Apr 22:42

πŸ¦‹ 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: 50

nika: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: depth

This 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: article

When 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: tasks

Warning

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:read raw mode -- Read file content without line numbers (raw text output)
  • nika:read optional fallback -- optional: true returns a default value instead of failing on missing files
  • nika:write accepts 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:write overwrite param -- overwrite: true replaces existing files instead of NIKA-215

βœ… Validation + DX

  • nika check validates skill + context file paths -- Catches missing files at validation time
  • nika check warns on unknown providers (NIKA-033) -- Typos like provider: antropic get suggestions
  • nika check respects 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_each limit raised to 50K -- Up from 10K for large-scale data processing
  • Configurable max_stdout -- Prevent OOM on verbose exec: 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: sitemap on 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_example path resolution -- Template paths resolve relative to project root
  • πŸ›‘οΈ Shell warning suppression -- No false "unescaped binding" warnings inside single quotes
  • πŸ”’ aggregate count -- Returns total array length, not just numeric items
  • πŸ”„ json_unflatten collision -- Conflicting keys replace scalar with object
  • πŸ” json_query empty results -- Returns [] instead of null
  • ⚠️ EventLog eviction warning -- Warns instead of silent loss at capacity
  • βœ… allow_exec in AcceptEdits -- nika:write works 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_host correctly strips [ ] from IPv6 addresses
  • πŸ”— Relative hreflang resolution -- hreflang URLs resolved to absolute
  • 🐍 interpreter -c/-e in templates -- python3 -c, node -e no 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