AGPL-3.0-or-later · forever.

showcase · tier 1 · business

Price watch

Pulls one field from the shop API, compares it in CEL, pings your webhook. No model call anywhere.

Not everything needs an LLM. The engine alone is a robot you can already trust. · conformance-gated in nika-spec ↗ · re-proven at every push

  • 2tasksthe plan falls out of the bindings
  • 2wavesparallel by construction
  • 1verbs exercisedinvoke
  • T1the tierbusiness

The whole file

2 tasks · 2 waves

The plan falls out of the bindings, nothing is scheduled by hand.

price-watch.nika.yamlsource
nika: v1workflow:  id: price-watch  description: "Watch a product price, ping me when it drops below my target"const:  product_api: "https://api.shop.example.com/v1/products/macbook-air"  alert_below: 899secrets:  alerts_webhook:    source: env    key: ALERTS_WEBHOOK_URL    egress:                       # sanction the one send · the secret IS the URL      - to: "nika:notify"        host_from_self: truepermits:  tools: ["nika:fetch", "nika:notify"]  net:    # TWO hosts, for two different reasons.    #    # `api.shop.example.com` is what `const.product_api` names — a literal    # `check` can read, so a mismatch here is caught before the run.    #    # `hooks.slack.com` is the ALERT host, and it is the entry people leave    # out. `host_from_self:` above sanctions the FLOW (this secret may be the    # URL) — it does not grant the capability. The host stays unknown at    # check, so it is judged at RUN against this bound: name the escalation    # host here or the send is refused mid-run, the first time the price    # actually drops. Swap it for wherever YOUR webhook lives.    http: ["api.shop.example.com", "hooks.slack.com"]tasks:  check:    invoke:      tool: "nika:fetch"      args:        url: "${{ const.product_api }}"        mode: jq        jq: "."    output:                           # named jq bindings over the raw response      price: ".price"      name: ".name"    on_error:      # Offline rehearsal · a sample ABOVE the target, so the gate below stays      # closed and the dry run ends green with the alert skipped. The alert      # path is therefore NOT exercised offline — which is exactly why the      # `net:` bound above has to be right by reading, not by running.      recover: { price: 949, name: "MacBook Air (offline sample)" }  alert:    with:      price: ${{ tasks.check.price }}     # value edges · the named bindings cross the boundary here      name: ${{ tasks.check.name }}    when: ${{ with.price < const.alert_below }}    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.alerts_webhook }}"        message: "Price drop · ${{ with.name }} is now ${{ with.price }} (target ${{ const.alert_below }})"        severity: infooutputs:  price: ${{ tasks.check.price }}

audited · 3 tasks · 3 waves · permits declarednika 0.106.1

nika inspect · engine 0.106.1 · vendored graph, never re-derived

2 declared edges · 2 waves · the plan falls out of the bindings