showcase · tier 2 · builders
Release radar
Reads your dependencies’ release feeds and diffs against last run, so only new ships reach you.
- 8tasksthe plan falls out of the bindings
- 5wavesparallel by construction
- 2verbs exercisedinvoke · infer
- T2the tierbuilders
The whole file
8 tasks · 5 wavesThe plan falls out of the bindings, nothing is scheduled by hand.
release-radar.nika.yamlsource
nika: release-radarmodel: ollama/qwen3.5:4b # local · zero key · swap for any provider in the catalogconst: releases_feed: "https://github.com/tokio-rs/tokio/releases.atom" state_path: "out/release-radar.json"permits: tools: ["nika:fetch", "nika:jq", "nika:json_diff", "nika:prompt", "nika:read", "nika:write"] net: http: ["github.com"] # the feed's host · net entries are exact names, never globs fs: # The state-file pattern touches exactly ONE path, on both sides: read # last run's state, write the next. `const.state_path` names it and a run # cannot move a const, so the same literal is the whole boundary — # `create_dirs:` makes out/ underneath it on the first run. read: ["out/release-radar.json"] write: ["out/release-radar.json"]tasks: # The human gate first (NIKA-SEC-009 · NEP-0002, the Rule of Two as a # static check). This boundary permits all three legs — private read # (the state file) + untrusted ingress (the release feed) + egress # (the state write) — so a blocking prompt (no default:) dominates the # flow before a byte moves. Headless surfaces pause durably and resume # with `--resume <trace> --answer approve=true`. approve: invoke: tool: "nika:prompt" args: message: "This run fetches ${{ const.releases_feed }}, diffs against ${{ const.state_path }}, and rewrites that state file. Proceed?" # First run has no state file · recover to an empty list. no_state: with: go: ${{ tasks.approve.output }} when: ${{ with.go == true }} invoke: tool: "nika:jq" args: { input: [], expression: "." } previous: with: go: ${{ tasks.approve.output }} when: ${{ with.go == true }} invoke: tool: "nika:read" args: { path: "${{ const.state_path }}" } on_error: on_codes: [NIKA-BUILTIN-READ-001] # not-found ONLY · a permission error still fails loudly recover: ${{ tasks.no_state.output }} feed: with: go: ${{ tasks.approve.output }} when: ${{ with.go == true }} invoke: tool: "nika:fetch" args: url: "${{ const.releases_feed }}" mode: feed extract: entries: "[.items[] | {title, link}]" on_error: # No network → this sample stands in for the RAW feed response: it # carries `items:` under the keys `mode: feed` actually returns # (probed live on this feed: title · link · id · author · content — # there is no `published` field), so the `extract:` jq above runs over # it unchanged. Recovering the BINDING's shape instead makes the jq # throw NIKA-VAR-004 — the recovery must mirror the response, never # the binding. recover: items: - { title: "tokio 1.48.0", link: "https://github.com/tokio-rs/tokio/releases/tag/tokio-1.48.0" } - { title: "tokio 1.47.1", link: "https://github.com/tokio-rs/tokio/releases/tag/tokio-1.47.1" } # `nika:read` hands back a STRING — a file is text until you parse it. # Measured: diffing the unparsed read against the entries array emits ONE # whole-document `replace` op at path "" on every run after the first, # so everything is reported as new, forever. The first-run recovery is # already an array; the guard keeps both shapes. previous_parsed: with: previous: ${{ tasks.previous.output }} invoke: tool: "nika:jq" args: input: "${{ with.previous }}" expression: 'if type == "string" then fromjson else . end' fresh: with: go: ${{ tasks.approve.output }} previous: ${{ tasks.previous_parsed.output }} entries: ${{ tasks.feed.entries }} when: ${{ with.go == true }} invoke: tool: "nika:json_diff" args: before: "${{ with.previous }}" after: "${{ with.entries }}" digest: with: go: ${{ tasks.approve.output }} fresh: ${{ tasks.fresh.output }} entries: ${{ tasks.feed.entries }} when: ${{ with.go == true && size(with.fresh) > 0 }} infer: max_tokens: 500 prompt: | New releases appeared on our dependency radar (RFC 6902 patch against last run) · ${{ with.fresh }} Full current feed · ${{ with.entries }} Write 3 bullets · what shipped · whether it looks breaking · what to check in our code. save_state: with: go: ${{ tasks.approve.output }} entries: ${{ tasks.feed.entries }} when: ${{ with.go == true }} invoke: tool: "nika:write" args: path: "${{ const.state_path }}" content: "${{ with.entries }}" create_dirs: true overwrite: trueoutputs: new_entries: value: ${{ tasks.fresh.output }} description: "RFC 6902 ops · empty = nothing new since last run" # Untyped on purpose: `digest` is `when:`-gated, so on a quiet week it is # SKIPPED and this reads null. Exporting it is also what keeps those tokens # from being dead spend — an infer whose output nothing consumes is paid for # and thrown away, and `check` says so. briefing: ${{ tasks.digest.output }}nika inspect · engine 0.111.0 · vendored graph, never re-derived
13 declared edges · 5 waves · the plan falls out of the bindings