Skip to main content
T2 chain · devops / dependency hygienemode: feed parses RSS/Atom natively, and the state-file pattern (read last run → diff → write next state) means you only hear about what’s new.

The job

« Did anything we depend on ship this week? » Checking releases pages is a robot’s job. This workflow reads the Atom feed, diffs it against what it saw last run (RFC 6902 — empty patch means silence), digests only the new entries, and saves the state for next time. First run? The missing state file recovers to an empty list.

The shape

The file

t2-release-radar.nika.yaml
nika: v1
workflow: release-radar
description: "dependency release feed → diff vs last run → only the NEW ships"

model: mock/echo            # swap for ollama/llama3.1 (local · zero key)

vars:
  releases_feed: "https://github.com/tokio-rs/tokio/releases.atom"
  state_path: "./state/release-radar.json"

tasks:
  # First run has no state file · recover to an empty list.
  - id: no_state
    invoke:
      tool: "nika:jq"
      args: { input: [], expression: "." }

  - id: previous
    invoke:
      tool: "nika:read"
      args: { path: "${{ vars.state_path }}" }
    on_error:
      on_codes: [NIKA-BUILTIN-READ-001]   # not-found ONLY · a permission error still fails loudly
      recover: ${{ tasks.no_state.output }}

  - id: feed
    invoke:
      tool: "nika:fetch"
      args:
        url: "${{ vars.releases_feed }}"
        mode: feed
    output:
      entries: "[.items[] | {title, url, published}]"

  - id: fresh
    depends_on: [previous, feed]
    invoke:
      tool: "nika:json_diff"
      args:
        before: "${{ tasks.previous.output }}"
        after: "${{ tasks.feed.entries }}"

  - id: digest
    depends_on: [fresh, feed]
    when: ${{ size(tasks.fresh.output) > 0 }}
    infer:
      prompt: |
        New releases appeared on our dependency radar (RFC 6902 patch
        against last run) ·
        ${{ tasks.fresh.output }}
        Full current feed · ${{ tasks.feed.entries }}
        Write 3 bullets · what shipped · whether it looks breaking ·
        what to check in our code.

  - id: save_state
    depends_on: [feed]
    invoke:
      tool: "nika:write"
      args:
        path: "${{ vars.state_path }}"
        content: "${{ tasks.feed.entries }}"
        create_dirs: true
        overwrite: true

outputs:
  new_entries:
    value: ${{ tasks.fresh.output }}
    type: array
    description: "RFC 6902 ops · empty = nothing new since last run"

How it works

1

feed mode does the parsing

mode: feed turns RSS/Atom into structured items — no scraping, no XML wrangling. The output: binding keeps just title/url/date.
2

State makes it incremental

previous reads the state file (recovering to [] on first run) · nika:json_diff against the fresh feed yields ONLY the new entries · save_state overwrites for next time.
3

Silence is the feature

The digest runs when: size(...) > 0 — nothing new, no model call, no noise. Schedule it daily and forget it.

Constructs you just used

ConstructWhereReference
nika:fetch mode: feedfeedBuiltins
state-file patternprevious · save_stateBindings
nika:json_diff (RFC 6902)freshBuiltins
on_error: recover: first-runpreviousError model

Make it yours

  • Watch N dependencies: lift the feed URL into a list and for_each the fetch (Competitor radar shows the leash).
  • Pipe breaking-change suspects into PR review fan-out’s reviewer prompt.
  • Swap the feed for your vendor’s changelog RSS — the shape doesn’t change.

Next · Resume screener

PII never leaves the machine — a local-model rubric per candidate, ranked deterministically.