FIG 6.0 · pick a workflow

“I want to…”

Start from the outcome. Anything you ask an AI to do more than once belongs in a file you can read before it runs. Every card below is a real workflow — pulled from the language’s own test suite, never mocked — with its plan (the tasks and what they wait on) and the exact YAML that runs it.

20
workflows
spec-valid
3
audiences
everyone → devs
4
tiers
T1 → T4
125
tasks
projected plans

01 · for everyone6 workflows

Everyone

No coding background needed. If you can read a checklist, you can read these files.

7.1.1

Turn meeting notes into owned actions

T1

A transcript goes in, a task list comes out. each item typed {owner, task, due}, checked, ready for your tracker.

invokeinfer
the plan
t1-meeting-actions.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: meeting-actionsdescription: "Transcript → typed action items {owner, task, due}"model: mock/echo            # swap for openai/gpt-5.2 or any provider in the catalogvars:  transcript_path:    type: string    required: true    description: "Path to the raw meeting transcript"tasks:  - id: transcript    invoke:      tool: "nika:read"      args: { path: "${{ vars.transcript_path }}" }  - id: extract    depends_on: [transcript]    infer:      prompt: |        Extract every action item from this meeting transcript ·        ${{ tasks.transcript.output }}      schema:                          # the contract the model must satisfy        type: object        required: [actions]        properties:          actions:            type: array            items:              type: object              required: [owner, task]              properties:                owner: { type: string }                task: { type: string }                due: { type: string }  - id: save    depends_on: [extract]    invoke:      tool: "nika:write"      args:        path: "./action-items.json"        content: "${{ tasks.extract.output.actions }}"  - id: trace    depends_on: [extract]    invoke:      tool: "nika:log"      args:        level: info        message: "Action items extracted to ./action-items.json"outputs:  actions:    value: ${{ tasks.extract.output.actions }}    type: array    description: "Typed action items, tracker-ready"

Nobody re-reads the transcript. The tracker import is already done.

7.1.2

Watch a price and get pinged when it moves

T1

No AI involved at all. one fetch, one compare, one ping — a robot you can already trust.

invoke
the plan
t1-price-watch.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: price-watchdescription: "Watch a product price, ping me when it drops below my target"vars:  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: truetasks:  - id: check    invoke:      tool: "nika:fetch"      args:        url: "${{ vars.product_api }}"        mode: jq        jq: "."    output:                           # named jq bindings over the raw response      price: ".price"      name: ".name"  - id: alert    depends_on: [check]    when: ${{ tasks.check.price < vars.alert_below }}    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.alerts_webhook }}"        message: "Price drop · ${{ tasks.check.name }} is now ${{ tasks.check.price }} (target ${{ vars.alert_below }})"        severity: infooutputs:  price: ${{ tasks.check.price }}

Not everything needs an LLM. The engine alone is a robot you can already trust.

7.1.3

Say it once, publish it everywhere

T1

One post becomes a thread, a LinkedIn version and a newsletter blurb. rewritten in parallel, same voice on every channel.

invokeinfer
the plan
t1-social-repurpose.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: social-repurposedescription: "One post → thread + LinkedIn + newsletter, in parallel"model: mock/echo            # swap for mistral/mistral-large or any providervars:  post_path: "./blog/launch-post.md"tasks:  - id: post    invoke:      tool: "nika:read"      args: { path: "${{ vars.post_path }}" }  # Three rewrites · no deps between them · they run concurrently.  - id: thread    depends_on: [post]    infer:      prompt: "Turn this post into a 6-tweet thread · keep the voice · ${{ tasks.post.output }}"  - id: linkedin    depends_on: [post]    infer:      prompt: "Rewrite this post for LinkedIn · hook first · ${{ tasks.post.output }}"  - id: newsletter    depends_on: [post]    infer:      prompt: "Write a 3-sentence newsletter blurb for this post · ${{ tasks.post.output }}"  - id: bundle    depends_on: [thread, linkedin, newsletter]    with:      t: ${{ tasks.thread.output }}      l: ${{ tasks.linkedin.output }}      n: ${{ tasks.newsletter.output }}    invoke:      tool: "nika:write"      args:        path: "./social-bundle.md"        content: |          # Social bundle          ## Thread          ${{ with.t }}          ## LinkedIn          ${{ with.l }}          ## Newsletter          ${{ with.n }}outputs:  bundle_path: ${{ tasks.bundle.output }}

Write once, publish everywhere, same voice on every channel.

7.1.4

Chase overdue invoices without the awkward part

T2

The reminders get drafted for you. nothing is saved until you read them and type yes.

invokeinfer
the plan
t2-invoice-chaser.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: invoice-chaserdescription: "Ledger CSV → overdue filter → drafted reminders → human gate → drafts file"model: mock/echo            # swap for groq/llama-3.3-70b — drafting is a fast-model jobvars:  ledger_csv: "./finance/invoices.csv"tasks:  - id: ledger    invoke:      tool: "nika:read"      args: { path: "${{ vars.ledger_csv }}" }  - id: rows    depends_on: [ledger]    invoke:      tool: "nika:convert"      args:        input: "${{ tasks.ledger.output }}"        from: csv        to: json        has_header: true  - id: overdue    depends_on: [rows]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.rows.output }}"        expression: 'map(select(.status == "overdue"))'  - id: drafts    depends_on: [overdue]    when: ${{ size(tasks.overdue.output) > 0 }}    infer:      prompt: |        Draft one short, polite payment reminder per overdue invoice ·        ${{ tasks.overdue.output }}        Markdown · one section per client · firm but warm.  - id: approve    depends_on: [drafts]    when: ${{ tasks.drafts.output != null }}   # nothing drafted · nothing to approve    invoke:      tool: "nika:prompt"      args:        message: "Save the reminder drafts for sending?"        default: false  - id: save    depends_on: [approve, drafts]    when: ${{ tasks.drafts.output != null && tasks.approve.output == true }}   # zero overdue → drafts skipped (null) · don't write nothing    invoke:      tool: "nika:write"      args:        path: "./finance/reminders-to-send.md"        content: "${{ tasks.drafts.output }}"outputs:  overdue:    value: ${{ tasks.overdue.output }}    type: array    description: "The overdue rows the reminders were drafted for"

Friday’s awkward chore shrinks to reading the drafts and typing yes.

7.1.5

Check a contract without it leaving your machine

T2

A local model reads the clauses. the document never touches the internet, and two checks gate the memo.

invokeinfer
the plan
t2-contract-guard.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: contract-guarddescription: "Local-model clause extraction → schema gate → risk memo"model: ollama/llama3.1      # the whole review runs offline · zero cloudvars:  contract_path:    type: string    required: true    description: "Path to the contract (markdown or plain text)"tasks:  - id: contract    invoke:      tool: "nika:read"      args: { path: "${{ vars.contract_path }}" }  - id: clauses    depends_on: [contract]    infer:      prompt: |        Extract every risk-bearing clause from this contract ·        ${{ tasks.contract.output }}        Quote each clause verbatim · classify its risk.      schema:        type: object        required: [clauses]        properties:          clauses:            type: array            items:              type: object              required: [quote, type, risk]              properties:                quote: { type: string }                type: { type: string, enum: [liability, termination, ip, payment, data, other] }                risk: { type: string, enum: [low, medium, high] }  - id: check    depends_on: [clauses]    invoke:      tool: "nika:validate"      args:        data: "${{ tasks.clauses.output }}"        format: json        schema:          type: object          required: [clauses]          properties:            clauses:              type: array              minItems: 1  - id: gate    depends_on: [check]    invoke:      tool: "nika:assert"      args:        condition: "${{ tasks.check.output.valid == true }}"        message: "Clause extraction failed the schema gate — refusing to write the memo"  - id: memo    depends_on: [clauses, gate]    infer:      prompt: |        Write a one-page risk memo from these clauses ·        ${{ tasks.clauses.output.clauses }}        Order by risk · high first · cite the quoted text.  - id: save    depends_on: [memo]    invoke:      tool: "nika:write"      args:        path: "./legal/risk-memo.md"        content: "${{ tasks.memo.output }}"        create_dirs: trueoutputs:  clauses:    value: ${{ tasks.clauses.output.clauses }}    type: array    description: "Typed risk-bearing clauses, verbatim quotes"  memo: ${{ tasks.memo.output }}

Legal-grade caution, sovereign by default. Ollama runs the whole review offline.

7.1.6

Screen forty resumes with one fair rubric

T3

Candidate #1 and #40 get the same questions. scored on a local model, so the personal data stays home.

invokeinfer
the plan
t3-resume-screener.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: resume-screenerdescription: "glob CVs → local-model rubric per candidate → deterministic shortlist"model: ollama/llama3.1      # PII stays on the machine · the whole screen is offlinepermits:                    # the file IS the blast radius · no net category at all —  fs:                       # CVs cannot leave this machine even if a prompt is hijacked    read: ["./hiring/inbox/**"]    write: ["./hiring/out/**", "./hiring/shortlist-brief.md"]  tools: ["nika:glob", "nika:read", "nika:jq", "nika:write"]vars:  role: "Senior Rust engineer"  cv_glob: "./hiring/inbox/*.md"  shortlist_size: 5tasks:  - id: pool    invoke:      tool: "nika:glob"      args: { pattern: "${{ vars.cv_glob }}" }  - id: cvs    depends_on: [pool]    for_each: ${{ tasks.pool.output }}    max_parallel: 8    fail_fast: false                   # one unreadable CV must not stop the batch    on_error:      recover: null                    # null keeps the zip aligned · filtered later    invoke:      tool: "nika:read"      args: { path: "${{ item }}" }  - id: pairs    depends_on: [pool, cvs]    invoke:                            # zip path + content · order survived the nulls      tool: "nika:jq"      args:        input: ["${{ tasks.pool.output }}", "${{ tasks.cvs.output }}"]        expression: "transpose | map(select(.[1] != null)) | map({path: .[0], text: .[1]})"  - id: screened    depends_on: [pairs]    for_each: ${{ tasks.pairs.output }}    max_parallel: 2                    # local model · don't thrash the GPU    fail_fast: false    on_error:      recover: null    with:      cv_path: ${{ item.path }}    infer:      prompt: |        Role · ${{ vars.role }}        Candidate file · ${{ with.cv_path }}        CV ·        ${{ item.text }}        Score this candidate against the role. Quote evidence from the        CV for every rating — no rating without a quote.      schema:        type: object        required: [file, fit, strengths, concerns]        properties:          file: { type: string }          fit: { type: string, enum: [strong, possible, weak] }          years_relevant: { type: integer }          strengths: { type: array, items: { type: string } }          concerns: { type: array, items: { type: string } }  - id: ranked    depends_on: [screened]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.screened.output }}"        expression: 'map(select(. != null)) | map(select(.fit != "weak")) | sort_by(.fit != "strong", -(.years_relevant // 0))'  - id: shortlist    depends_on: [ranked]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.ranked.output }}"        expression: ".[:${{ vars.shortlist_size }}]"  - id: brief    depends_on: [shortlist]    when: ${{ size(tasks.shortlist.output) > 0 }}    infer:      prompt: |        Write the screening brief for these shortlisted candidates ·        ${{ tasks.shortlist.output }}        One paragraph each · lead with the evidence quotes · end with        the suggested interview focus.  - id: save    depends_on: [brief]    when: ${{ tasks.brief.output != null }}    # skip cascades by VALUE · a skipped brief is null    invoke:      tool: "nika:write"      args:        path: "./hiring/shortlist-brief.md"        content: "${{ tasks.brief.output }}"        create_dirs: trueoutputs:  shortlist:    value: ${{ tasks.shortlist.output }}    type: array    description: "Ranked candidates · strong first, then by relevant years"

Candidate #1 and #40 get the same rubric, and the PII never leaves the machine.

02 · for founders & ops6 workflows

Founders & ops

The Monday-morning chores — briefs, queues, radars — described once, done every week.

7.2.1

Know what competitors shipped, every Monday

T3

It reads their sites while you sleep. one brief on your desk at 8am, with what it signals.

invokeinfer
the plan
t3-competitor-radar.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: competitor-radardescription: "Sitemap → parallel page reads → one competitive brief + ping"model: mock/echo            # swap for anthropic/claude-sonnet-4-6vars:  competitor_sitemap: "https://competitor.example.com/sitemap.xml"secrets:  team_webhook:    source: env    key: TEAM_WEBHOOK_URL    egress:                       # sanction the one send · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  - id: map    invoke:      tool: "nika:fetch"      args:        url: "${{ vars.competitor_sitemap }}"        mode: sitemap    output:      recent: ".urls[:8]"             # cap the radar at the 8 freshest pages  - id: pages    depends_on: [map]    for_each: ${{ tasks.map.recent }}    max_parallel: 4                    # be polite · 4 fetches in flight max    fail_fast: false                   # one dead page must not kill the radar    on_error:      recover: null                    # a dead page yields null at its index · the radar lives    timeout: "30s"    retry:      max_attempts: 3      backoff_strategy: exponential      jitter: true    invoke:      tool: "nika:fetch"      args:        url: "${{ item }}"        mode: article  - id: digest    depends_on: [pages]    infer:      prompt: |        These are the pages a competitor published recently ·        ${{ tasks.pages.output }}        Write the Monday brief · what they shipped · what it signals ·        what we should watch. One page, plain words.  - id: save    depends_on: [digest]    invoke:      tool: "nika:write"      args:        path: "./radar/competitor-brief.md"        content: "${{ tasks.digest.output }}"        create_dirs: true  - id: ping    depends_on: [save]    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.team_webhook }}"        message: "Competitor radar is ready · ./radar/competitor-brief.md"        severity: infooutputs:  brief: ${{ tasks.digest.output }}

Monday 8am, everything they shipped last week is on your desk, with what it signals.

7.2.2

Start the week with the numbers already gathered

T4

Market, repo pulse and the KPI sheet collected in parallel. the ping even reports its own cost.

invokeexecinfer
the plan
t4-ceo-monday-brief.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: ceo-monday-briefdescription: "news + repo pulse + KPIs → thinking synthesis → dated brief + cost ping"model: mistral/mistral-largevars:  watch_query: "AI workflow engines"  kpi_csv: "./finance/kpis.csv"secrets:  founders_webhook:    source: env    key: FOUNDERS_WEBHOOK_URL    egress:                       # sanction the one send · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  # ── branch 1 · market signal ──  - id: news    invoke:      tool: "nika:fetch"      args:        url: "https://hn.algolia.com/api/v1/search?query=${{ vars.watch_query }}"        mode: jq        jq: ".hits[:10] | map(.title)"  # ── branch 2 · engineering pulse ──  - id: pulse    exec:      command: "git shortlog -sn --since='1 week ago'"  # ── branch 3 · the numbers ──  - id: kpi_raw    invoke:      tool: "nika:read"      args: { path: "${{ vars.kpi_csv }}" }  - id: kpis    depends_on: [kpi_raw]    invoke:      tool: "nika:convert"      args:        input: "${{ tasks.kpi_raw.output }}"        from: csv        to: json        has_header: true  - id: revenue    depends_on: [kpis]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.kpis.output }}"        expression: "map(.weekly_revenue | tonumber) | add"  # ── synthesis · with a thinking budget ──  - id: brief    depends_on: [news, pulse, kpis, revenue]    infer:      model: anthropic/claude-sonnet-4-6   # per-task override · thinking budget      prompt: |        Market signal · ${{ tasks.news.output }}        Engineering pulse · ${{ tasks.pulse.output }}        KPIs · ${{ tasks.kpis.output }}        Revenue (summed) · ${{ tasks.revenue.output }}        Write my Monday brief · 5 sections · market · product ·        numbers · risks · the ONE decision this week needs.      thinking:        enabled: true        budget_tokens: 10000  - id: stamp    invoke:      tool: "nika:date"      args: { op: now }    output:      day: ".iso[:10]"               # YYYY-MM-DD from the ISO timestamp  - id: save    depends_on: [brief, stamp]    invoke:      tool: "nika:write"      args:        path: "./briefs/${{ tasks.stamp.day }}-monday.md"        content: "${{ tasks.brief.output }}"        create_dirs: true  # ── the run reports its own bill ──  - id: bill    depends_on: [save]    invoke:      tool: "nika:inspect"      args: { view: cost }  - id: ping    depends_on: [bill, stamp]    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.founders_webhook }}"        message: "Monday brief ready · briefs/${{ tasks.stamp.day }}-monday.md · run cost $${{ tasks.bill.output.total_usd }}"        severity: info    on_finally:      - invoke:          tool: "nika:emit"          args:            event_type: "brief.monday.run"            payload:              day: "${{ tasks.stamp.day }}"outputs:  brief: ${{ tasks.brief.output }}  cost_usd: ${{ tasks.bill.output.total_usd }}

The ping ends with the run’s own cost line. The workflow reports its own bill.

7.2.3

Turn “get me up to speed” into a brief you can audit

T4

A fast model plans, an agent digs inside hard budgets, a careful model writes. every step leaves a record.

inferagentinvoke
the plan
t4-deep-research-brief.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: deep-research-briefdescription: "plan → budgeted research agent → thinking synthesis → brief on disk"model: anthropic/claude-sonnet-4-6vars:  topic:    type: string    required: true    description: "What to research"tasks:  - id: plan    infer:      model: anthropic/claude-haiku-4-5    # planning is a fast-model job      prompt: "Break '${{ vars.topic }}' into 4 sharp research queries · no overlap."      schema:        type: object        required: [queries]        properties:          queries:            type: array            items: { type: string }  - id: investigate    depends_on: [plan]    agent:      system: |        You are a rigorous researcher. Work the queries one by one ·        fetch sources · keep verbatim quotes · note what each source        actually supports. Call nika:done when the plan is exhausted.      prompt: "Research plan · ${{ tasks.plan.output.queries }}"      tools:        - "nika:fetch"                 # the web        - "nika:write"                 # scratch notes        - "nika:done"                  # the loop-completion sentinel      max_turns: 25      max_tokens_total: 150000      schema:        type: object        required: [findings, sources]        properties:          findings:            type: array            items: { type: string }          sources:            type: array            items: { type: string }  - id: brief    depends_on: [investigate]    infer:      prompt: |        Findings · ${{ tasks.investigate.output.findings }}        Sources · ${{ tasks.investigate.output.sources }}        Write the executive brief · what's true · what's contested ·        what it means · what to do. Two pages max.      thinking:        enabled: true        budget_tokens: 8000  - id: save    depends_on: [brief]    invoke:      tool: "nika:write"      args:        path: "./research/${{ vars.topic }}.md"        content: "${{ tasks.brief.output }}"        create_dirs: trueoutputs:  brief: ${{ tasks.brief.output }}  sources:    value: ${{ tasks.investigate.output.sources }}    type: array    description: "Every source the agent actually used"

“Get me up to speed by Thursday” becomes a pipeline you can audit end to end.

7.2.4

Wake up to a sorted support queue

T2

Overnight tickets get tagged, drafted and batched. humans start at 9am on the hard ones.

invokeinfer
the plan
t2-support-triage.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: support-triagedescription: "Ticket queue → typed triage → urgent escalation → triage board"model: mock/echo            # swap for groq/llama-3.3-70b — triage wants speedvars:  queue_path: "./support/overnight-queue.json"secrets:  oncall_webhook:    source: env    key: ONCALL_WEBHOOK_URL    egress:                       # sanction the one send · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  - id: batch    invoke:      tool: "nika:uuid"      args: { version: v7 }  - id: queue    invoke:      tool: "nika:read"      args: { path: "${{ vars.queue_path }}" }  - id: triage    depends_on: [queue]    infer:      prompt: |        Triage every ticket in this queue ·        ${{ tasks.queue.output }}        For each · classify category and urgency, draft a 2-sentence first reply.      schema:        type: object        required: [tickets]        properties:          tickets:            type: array            items:              type: object              required: [id, category, urgency, first_reply]              properties:                id: { type: string }                category: { type: string, enum: [billing, bug, how-to, account, other] }                urgency: { type: string, enum: [low, normal, high, critical] }                first_reply: { type: string }  - id: urgent    depends_on: [triage]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.triage.output.tickets }}"        expression: 'map(select(.urgency == "high" or .urgency == "critical"))'  - id: escalate    depends_on: [urgent, batch]    when: ${{ size(tasks.urgent.output) > 0 }}    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.oncall_webhook }}"        message: "Urgent tickets in triage batch ${{ tasks.batch.output }} · ${{ tasks.urgent.output }}"        severity: warning  - id: board    depends_on: [triage, batch]    invoke:      tool: "nika:write"      args:        path: "./support/triage-${{ tasks.batch.output }}.json"        content: "${{ tasks.triage.output.tickets }}"outputs:  tickets:    value: ${{ tasks.triage.output.tickets }}    type: array    description: "The classified queue with drafted first replies"

By 9am the board is tagged, drafted and batched. Humans handle the hard ones.

7.2.5

Turn a rival’s best page into your content brief

T2

It reads what actually ranks. your writer starts from the gaps, not a blank page.

invokeinfer
the plan
t2-seo-content-brief.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: seo-content-briefdescription: "Competitor sitemap → top page → gap analysis → typed brief"model: mock/echo            # swap for openai/gpt-5.2vars:  competitor_sitemap: "https://competitor.example.com/sitemap.xml"  topic:    type: string    required: true    description: "The keyword/topic you want to rank for"tasks:  - id: map    invoke:      tool: "nika:fetch"      args:        url: "${{ vars.competitor_sitemap }}"        mode: sitemap    output:      top: ".urls[:5]"  - id: top_page    depends_on: [map]    invoke:      tool: "nika:fetch"      args:        url: "${{ tasks.map.top[0] }}"        mode: article  - id: brief    depends_on: [map, top_page]    infer:      prompt: |        Topic to rank for · ${{ vars.topic }}        Competitor's top URLs · ${{ tasks.map.top }}        Their best page on it ·        ${{ tasks.top_page.output }}        Write a content brief that BEATS this page · find the gaps they        missed · angle for search intent.      schema:        type: object        required: [title, angle, outline, keywords]        properties:          title: { type: string }          angle: { type: string }          outline: { type: array, items: { type: string } }          keywords: { type: array, items: { type: string } }  - id: save    depends_on: [brief]    invoke:      tool: "nika:write"      args:        path: "./briefs/${{ vars.topic }}.json"        content: "${{ tasks.brief.output }}"        create_dirs: trueoutputs:  brief:    value: ${{ tasks.brief.output }}    type: object    description: "The typed content brief"

Your writer starts from gaps and search intent, grounded in pages that actually rank.

7.2.6

Get the docs in French by lunch

T3

Every file found, translated in parallel, filed back in place. same folder layout, same voice.

invokeinfer
the plan
t3-localization-factory.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: localization-factorydescription: "glob docs → parallel read → parallel translate → mirror tree"model: mistral/mistral-large    # EU model for EU locales · pick yoursvars:  lang: "fr"  source_glob: "./docs/**/*.md"tasks:  - id: files    invoke:      tool: "nika:glob"      args:        pattern: "${{ vars.source_glob }}"        exclude: ["**/node_modules/**"]  - id: texts    depends_on: [files]    for_each: ${{ tasks.files.output }}    max_parallel: 8    invoke:      tool: "nika:read"      args: { path: "${{ item }}" }  - id: pairs    depends_on: [files, texts]    invoke:      tool: "nika:jq"      args:        input: ["${{ tasks.files.output }}", "${{ tasks.texts.output }}"]        expression: "transpose | map({path: .[0], text: .[1]})"  - id: translated    depends_on: [pairs]    for_each: ${{ tasks.pairs.output }}    max_parallel: 3                    # rate-limit the provider    fail_fast: false                   # finish the batch · a failed file yields null at its index    on_error:      recover: null                    # null keeps the transpose zip aligned (order preserved)    infer:      prompt: |        Translate to ${{ vars.lang }} · keep markdown structure, code blocks        untouched, and the original tone ·        ${{ item.text }}  - id: bundle    depends_on: [pairs, translated]    invoke:      tool: "nika:jq"      args:        input: ["${{ tasks.pairs.output }}", "${{ tasks.translated.output }}"]        expression: "transpose | map(select(.[1] != null)) | map({path: .[0].path, text: .[1]})"  - id: mirror    depends_on: [bundle]    for_each: ${{ tasks.bundle.output }}    max_parallel: 8    invoke:      tool: "nika:write"      args:        path: "./i18n/${{ vars.lang }}/${{ item.path }}"        content: "${{ item.text }}"        create_dirs: trueoutputs:  files:    value: ${{ tasks.files.output }}    type: array    description: "Every source file that was mirrored"

“Can we have the docs in French?” Yes, by lunch, with the original paths preserved.

03 · for developers8 workflows

Developers

The boring parts of shipping run themselves — with a human gate exactly where it counts.

7.3.1

Have the standup note already written

T1

It reads yesterday’s commits and writes your three bullets. you glance, tweak one word, go.

invokeexecinfer
the plan
t1-standup-digest.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: standup-digestdescription: "Read yesterday's commits, write today's standup note"model: mock/echo            # deterministic · swap for ollama/llama3.1 (local · zero key)tasks:  # No deps between these two → the engine runs them in parallel.  - id: today    invoke:      tool: "nika:date"      args: { op: now }  - id: history    exec:      command: "git log --since=yesterday --oneline --no-merges"  - id: digest    depends_on: [today, history]    infer:      prompt: |        Date · ${{ tasks.today.output }}        Commits since yesterday ·        ${{ tasks.history.output }}        Write my standup note · 3 bullets · done / doing / blocked.        Plain words · no fluff.  - id: save    depends_on: [digest]    invoke:      tool: "nika:write"      args:        path: "./standup-note.md"        content: "${{ tasks.digest.output }}"outputs:  note: ${{ tasks.digest.output }}

Every morning: the note is already written. You glance, you tweak one word, you go.

7.3.2

Ship release notes in your own voice

T2

git log in, changelog out, team pinged. zero copy-paste on release day.

execinferinvoke
the plan
t2-release-notes.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: release-notesdescription: "git log → typed release notes → CHANGELOG insert → team ping"model: mock/echo            # swap for mistral/mistral-largevars:  since_tag: "v0.80.0"secrets:  team_webhook:    source: env    key: TEAM_WEBHOOK_URL    egress:                       # sanction the one send · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  - id: history    exec:      command: "git log ${{ vars.since_tag }}..HEAD --oneline --no-merges"  - id: notes    depends_on: [history]    infer:      prompt: |        Write release notes from these commits ·        ${{ tasks.history.output }}        Tone · plain, direct, no marketing fluff.      schema:        type: object        required: [headline, body]        properties:          headline: { type: string }          breaking: { type: array, items: { type: string } }          body: { type: string }  - id: changelog    depends_on: [notes]    invoke:      tool: "nika:edit"      args:        path: "./CHANGELOG.md"        find: "# Changelog"        replace: |          # Changelog          ## ${{ vars.since_tag }}..HEAD · ${{ tasks.notes.output.headline }}          ${{ tasks.notes.output.body }}  - id: announce    depends_on: [notes, changelog]    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.team_webhook }}"        message: "Release notes ready · ${{ tasks.notes.output.headline }}"        severity: infooutputs:  headline: ${{ tasks.notes.output.headline }}  body: ${{ tasks.notes.output.body }}

Release day: one command, a changelog in your voice, zero copy-paste.

7.3.3

Hear about dependency releases only when they matter

T2

It diffs the release feeds against last run. no ping means nothing shipped.

invokeinfer
the plan
t2-release-radar.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: release-radardescription: "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: trueoutputs:  new_entries:    value: ${{ tasks.fresh.output }}    type: array    description: "RFC 6902 ops · empty = nothing new since last run"

No ping, nothing shipped. A ping, something worth a look. You stop checking tabs.

7.3.4

Give big PRs a reviewer per file

T3

One read-only reviewer per changed file, in parallel, under budget. attention finally scales with the diff.

execinvokeagentinfer
the plan
t3-pr-review-fanout.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: pr-review-fanoutdescription: "changed files → one read-only review agent each → merged REVIEW.md"model: anthropic/claude-sonnet-4-6   # agent loops want a tool-calling modelvars:  base_ref: "main"tasks:  - id: changed    exec:      command: "git diff --name-only ${{ vars.base_ref }}...HEAD"  - id: files    depends_on: [changed]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.changed.output }}"        expression: 'split("\n") | map(select(length > 0))'  - id: todo_sweep    invoke:      tool: "nika:grep"      args:        pattern: "TODO|FIXME|HACK"        path: "./src"  - id: reviews    depends_on: [files]    for_each: ${{ tasks.files.output }}    max_parallel: 4    fail_fast: false    on_error:      recover: null                    # a budget-exhausted review yields null · the swarm lives    agent:      system: "You are a precise code reviewer. Read the file, then report findings."      prompt: "Review ${{ item }} · bugs first, then risky patterns. Read it before judging."      tools:        - "nika:read"                  # read-only swarm · least privilege        - "nika:done"      max_turns: 6      max_tokens_total: 30000      schema:        type: object        required: [file, findings]        properties:          file: { type: string }          findings:            type: array            items:              type: object              required: [severity, message]              properties:                severity: { type: string, enum: [blocker, high, med, low] }                message: { type: string }                line: { type: integer }  - id: merged    depends_on: [reviews]    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.reviews.output }}"        expression: 'map(select(. != null)) | map(.findings[] + {file: .file}) | sort_by(.severity)'  - id: summary    depends_on: [merged, todo_sweep]    infer:      prompt: |        Merge these review findings into REVIEW.md · blockers first ·        ${{ tasks.merged.output }}        Open TODO debt found by grep ·        ${{ tasks.todo_sweep.output }}        End with a verdict · ship / fix-first / redesign.  - id: save    depends_on: [summary]    invoke:      tool: "nika:write"      args:        path: "./REVIEW.md"        content: "${{ tasks.summary.output }}"outputs:  findings:    value: ${{ tasks.merged.output }}    type: array    description: "All findings, severity-sorted, with file attribution"  review: ${{ tasks.summary.output }}

Big PRs get deep reviews, because attention now scales with the diff.

7.3.5

Ship only when everything is green — and a human says go

T4

Tests, lint and audit run in parallel; a person signs the GO. it ships on time or not at all, and the record shows which.

invokeexec
the plan
t4-release-train.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: release-traindescription: "parallel gates → human GO → hold until the window → ship · verify · record"vars:  version:    type: string    required: true    description: "The version to ship (e.g. 1.4.0)"  window: "2026-07-28T09:00:00Z"secrets:  team_webhook:    source: env    key: TEAM_WEBHOOK_URL    egress:                       # sanction the on_finally ping · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  - id: t0    invoke:      tool: "nika:date"      args: { op: now }  # ── the gate wave · all three run in parallel ──  - id: tests    exec:      command: "cargo test --workspace --quiet"      capture: structured    timeout: "15m"  - id: lint    exec:      command: "cargo clippy --workspace --all-targets -- -D warnings"      capture: structured    timeout: "10m"  - id: audit    exec:      command: "cargo audit"      capture: structured    timeout: "5m"    retry:      max_attempts: 2                  # advisory DB fetch can flake  - id: gates_green    depends_on: [tests, lint, audit]    invoke:      tool: "nika:assert"      args:        condition: "${{ tasks.tests.output.exit_code == 0 && tasks.lint.output.exit_code == 0 && tasks.audit.output.exit_code == 0 }}"        message: "A release gate is RED — the train does not depart"  - id: gate_time    depends_on: [t0, gates_green]    invoke:      tool: "nika:date"      args:        op: diff        start: "${{ tasks.t0.output }}"        end: "now"        unit: minutes  # ── the human signs the departure ──  - id: conductor    depends_on: [gates_green, gate_time]    invoke:      tool: "nika:prompt"      args:        message: "Gates GREEN in ${{ tasks.gate_time.output }} min. Ship ${{ vars.version }} in the ${{ vars.window }} window?"        default: false  - id: approved    depends_on: [conductor]    invoke:      tool: "nika:assert"      args:        condition: "${{ tasks.conductor.output == true }}"        message: "Departure not signed — train cancelled"  # ── hold until the window · absolute time, not a sleep ──  - id: hold    depends_on: [approved]    invoke:      tool: "nika:wait"      args:        until: "${{ vars.window }}"        timeout: "48h"  - id: ship    depends_on: [hold]    exec:      command: "./scripts/release.sh ${{ vars.version }}"      capture: structured    timeout: "30m"  - id: verify    depends_on: [ship]    invoke:      tool: "nika:fetch"      args:        url: "https://api.example.com/v1/version"        mode: jq        jq: ".version"    retry:      max_attempts: 5      backoff_strategy: exponential      backoff_ms: 5000  - id: live    depends_on: [verify]    invoke:      tool: "nika:assert"      args:        condition: "${{ tasks.verify.output == vars.version }}"        message: "Prod does not report the shipped version — investigate before announcing"  # the always-pattern · `when: true` replaces the default success-gate ·  # this task runs whether `live` succeeded, failed, or never started  # (upstream abort) — a record that must land on EVERY outcome is a  # terminal task, not a cleanup hook (03 §Task states).  - id: record    depends_on: [live]    when: true    invoke:      tool: "nika:emit"      args:        event_type: "release.train.departed"        payload:          version: "${{ vars.version }}"          status: "${{ tasks.live.status }}"    on_finally:      - invoke:          tool: "nika:notify"          args:            channel: webhook            target: "${{ secrets.team_webhook }}"            message: "Release train ${{ vars.version }} · ${{ tasks.live.status }}"            severity: infooutputs:  shipped: ${{ tasks.verify.output }}

No green gates, no departure. No human GO, no departure. It ships on time or not at all — and the journal records which.

7.3.6

Get paged only for changes nobody approved

T3

It diffs live prod against the signed-off baseline. silence means prod matches exactly.

invokeinfer
the plan
t3-config-drift-sentinel.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: config-drift-sentineldescription: "live config vs sanctioned baseline → typed drift → explained alert"model: mock/echo            # swap for anthropic/claude-haiku-4-5 — explain is cheapvars:  config_url: "https://api.internal.example.com/v1/config"  baseline_path: "./ops/config-baseline.json"  approved_overrides:    type: object    description: "Sanctioned config overrides (RFC 7396 merge-patch shape)"secrets:  oncall_webhook:    source: env    key: ONCALL_WEBHOOK_URL    egress:                       # sanction the one send · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  - id: live    invoke:      tool: "nika:fetch"      args:        url: "${{ vars.config_url }}"        mode: jq        jq: "."    retry:      max_attempts: 3      backoff_strategy: exponential  - id: baseline    invoke:      tool: "nika:read"      args: { path: "${{ vars.baseline_path }}" }  - id: expected    depends_on: [baseline]    invoke:      tool: "nika:json_merge_patch"      args:        target: "${{ tasks.baseline.output }}"        patch: "${{ vars.approved_overrides }}"  - id: drift    depends_on: [expected, live]    invoke:      tool: "nika:json_diff"      args:        before: "${{ tasks.expected.output }}"        after: "${{ tasks.live.output }}"  - id: fingerprint    depends_on: [live]    invoke:      tool: "nika:hash"      args:        algo: blake3        content: "${{ tasks.live.output }}"        encoding: hex  - id: explain    depends_on: [drift]    when: ${{ size(tasks.drift.output) > 0 }}    on_error:      recover: "(explanation unavailable — model call failed · the raw patch is attached)"    infer:      prompt: |        This RFC 6902 patch is UNSANCTIONED config drift in production ·        ${{ tasks.drift.output }}        Explain in 3 bullets · what changed · likely blast radius · first check.  - id: alert    depends_on: [explain, drift, fingerprint]    when: ${{ size(tasks.drift.output) > 0 }}    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.oncall_webhook }}"        message: "Config drift detected · ${{ tasks.explain.output }} · live config blake3 ${{ tasks.fingerprint.output }}"        severity: critical  - id: record    depends_on: [drift, fingerprint]    invoke:      tool: "nika:emit"      args:        event_type: "config.drift.scan"        payload:          patch: "${{ tasks.drift.output }}"          live_hash: "${{ tasks.fingerprint.output }}"outputs:  drift:    value: ${{ tasks.drift.output }}    type: array    description: "RFC 6902 operations · empty when prod matches the sanctioned state"

Silence means prod matches exactly what was signed off. That is the whole alert policy.

7.3.7

Stop re-running the whole night for three bad rows

T2

A checkpoint splits good rows from bad. rejects land in quarantine, the pipeline keeps going.

invoke
the plan
t2-etl-quarantine.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: etl-quarantinedescription: "CSV batch → schema gate → quarantine the bad · aggregate the good"vars:  batch_csv: "./data/incoming/orders.csv"tasks:  # A deterministic empty fallback · the recover target when parsing dies.  - id: empty_batch    invoke:      tool: "nika:jq"      args: { input: [], expression: "." }  - id: raw    invoke:      tool: "nika:read"      args: { path: "${{ vars.batch_csv }}" }  - id: rows    depends_on: [raw]    invoke:      tool: "nika:convert"      args:        input: "${{ tasks.raw.output }}"        from: csv        to: json        has_header: true    on_error:      recover: ${{ tasks.empty_batch.output }}    # malformed CSV → empty batch · pipeline lives  - id: check    depends_on: [rows]    invoke:      tool: "nika:validate"      args:        data: "${{ tasks.rows.output }}"        format: json        schema:          type: array          items:            type: object            required: [order_id, amount, currency]            properties:              order_id: { type: string }              amount: { type: string }              currency: { type: string, enum: [EUR, USD, GBP] }  - id: good    depends_on: [rows, check]    when: ${{ tasks.check.output.valid == true }}    invoke:      tool: "nika:jq"      args:        input: "${{ tasks.rows.output }}"        expression: 'group_by(.currency) | map({currency: .[0].currency, orders: length, total: (map(.amount | tonumber) | add)})'  - id: quarantine    depends_on: [rows, check]    when: ${{ tasks.check.output.valid == false }}    invoke:      tool: "nika:write"      args:        path: "./data/quarantine/orders-rejected.json"        content: "${{ tasks.check.output.errors }}"        create_dirs: true  - id: report    depends_on: [good]    when: ${{ tasks.good.output != null && size(tasks.good.output) > 0 }}   # good is SKIPPED (null) on the quarantine path · guard before size()    invoke:      tool: "nika:write"      args:        path: "./data/reports/daily-totals.json"        content: "${{ tasks.good.output }}"        create_dirs: trueoutputs:  totals:    value: ${{ tasks.good.output }}    type: array    description: "Per-currency order totals · empty when the batch was quarantined"

“Re-run the whole night” turns into “fix three rows tomorrow morning”.

7.3.8

Have the postmortem drafted before the retro

T4

Logs, status history and the runbook gathered in parallel. a typed timeline, verified before any draft.

execinvokeinfer
the plan
t4-incident-war-room.nika.yamlwalkthrough ↗
yaml
nika: v1workflow: incident-war-roomdescription: "parallel evidence → typed timeline → settle + recheck → postmortem draft"model: mistral/mistral-largevars:  service: "checkout-api"  status_url: "https://status.internal.example.com/v1/services/checkout-api"  log_window: "90 minutes ago"secrets:  oncall_webhook:    source: env    key: ONCALL_WEBHOOK_URL    egress:                       # sanction the on_finally ping · the secret IS the URL      - to: "nika:notify"        host_from_self: truetasks:  # ── the gather wave · all three run in parallel ──  - id: logs    exec:      command: "journalctl -u ${{ vars.service }} --since '${{ vars.log_window }}' --no-pager"      capture: structured  - id: status_history    invoke:      tool: "nika:fetch"      args:        url: "${{ vars.status_url }}"        mode: jq        jq: ".history"    retry:      max_attempts: 4      backoff_strategy: exponential      jitter: true  - id: runbook    invoke:      tool: "nika:read"      args: { path: "./runbooks/${{ vars.service }}.md" }  # ── reconstruct · typed timeline ──  - id: timeline    depends_on: [logs, status_history, runbook]    infer:      prompt: |        Service logs ·        ${{ tasks.logs.output.stdout }}        Status history · ${{ tasks.status_history.output }}        Runbook · ${{ tasks.runbook.output }}        Reconstruct the incident timeline as events.      schema:        type: object        required: [events]        properties:          events:            type: array            items:              type: object              required: [at, what]              properties:                at: { type: string }                what: { type: string }                evidence: { type: string }  # ── settle, then confirm recovery before claiming it ──  - id: settle    depends_on: [timeline]    invoke:      tool: "nika:wait"      args: { duration: "60s" }  - id: recheck    depends_on: [settle]    invoke:      tool: "nika:fetch"      args:        url: "${{ vars.status_url }}"        mode: jq        jq: ".current.state"  - id: confirmed    depends_on: [recheck]    invoke:      tool: "nika:assert"      args:        condition: "${{ tasks.recheck.output == 'operational' }}"        message: "Service is NOT back to operational — postmortem draft blocked"  # ── the draft · only after recovery is proven ──  - id: postmortem    depends_on: [timeline, confirmed]    infer:      model: anthropic/claude-sonnet-4-6   # per-task override · thinking budget      prompt: |        Timeline · ${{ tasks.timeline.output.events }}        Write the postmortem draft · summary · impact · root-cause        hypotheses · 3 follow-up actions with owners left blank.      thinking:        enabled: true        budget_tokens: 6000  - id: save    depends_on: [postmortem]    invoke:      tool: "nika:write"      args:        path: "./incidents/${{ vars.service }}-postmortem.md"        content: "${{ tasks.postmortem.output }}"        create_dirs: true  # the always-pattern · the on-call ping fires on EVERY outcome —  # including the designed failure path (recovery NOT confirmed → the  # assert fails → save never starts → this still runs · when: true  # replaces the default gate · 03 §Task states).  - id: ping    depends_on: [save]    when: true    invoke:      tool: "nika:emit"      args:        event_type: "incident.postmortem.drafted"        payload:          service: "${{ vars.service }}"          status: "${{ tasks.save.status }}"    on_finally:      - invoke:          tool: "nika:notify"          args:            channel: webhook            target: "${{ secrets.oncall_webhook }}"            message: "Postmortem draft run finished for ${{ vars.service }} · ${{ tasks.save.status }}"            severity: infooutputs:  events:    value: ${{ tasks.timeline.output.events }}    type: array    description: "The reconstructed, typed incident timeline"  postmortem: ${{ tasks.postmortem.output }}

The postmortem draft is ready before the retro is scheduled.

20 workflows · four tiers · nika-spec/examples/showcase — every file audited before it runs: plan, cost, secrets