AGPL-3.0-or-later · forever.

the path · 05

Fetch → bind → summarize, with a fallback that keeps the shape.

run itnika try 05-fetch-chain

05-fetch-chain.nika.yaml
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## 05 · Fetch → bind → summarize, with a fallback that keeps the shape.## The first file that reaches outside the process. Two things follow from# that: the workflow has to say which host it may reach, and it has to have# an answer for the day that host is down. Both are declarations, not code.## Demonstrates ·#   - `invoke:` · calling a TOOL · fetching is `nika:fetch`, not a verb#   - `permits.net.http` · the hosts this workflow may reach, named exactly#   - `output:` · named jq bindings → `${{ tasks.<id>.<name> }}`#   - `on_error: recover:` · a literal fallback that substitutes the raw#     output BEFORE extraction, so the SAME bindings work on both paths#   - chaining a tool result into an `infer:` prompt## Runs green offline: the placeholder host resolves nowhere, the recovery# value takes over, the summary still lands. That is the point — the# rehearsal exercises the failure path, which is the one you cannot test# by waiting for it.## Run · nika run examples/05-fetch-chain.nika.yamlnika: v1workflow:  id: fetch-chainmodel: mock/echo   # the dry twin · the lesson here is the tool call, not the proseconst:  api_url: "https://api.example.com/v1/articles/latest"permits:  tools: ["nika:fetch"]  net:    # The one host `const.api_url` names. A `const` is the one authority a run    # cannot move, so the host is knowable while checking and the bound is    # written here rather than deferred.    #    # Worth knowing before you build on this: when a URL arrives from a secret    # or an input instead, `check` cannot see the host and stays green — the    # boundary is then judged mid-run, after tokens are spent. Exact names    # only; this list never takes globs.    http: ["api.example.com"]tasks:  fetch_article:    invoke:      tool: nika:fetch      args:        url: "${{ const.api_url }}"        mode: jq    # extract structured fields · not a markdown dump        jq: "."    output:      # Named jq bindings over the tool's result. Downstream reads      # `${{ tasks.fetch_article.title }}` — not `.output.data.title`.      title: ".data.title"      body: ".data.body"    on_error:      # The literal fallback. It substitutes the RAW result before the      # bindings above run, which is the whole reason it has to mirror the      # live response shape — `{ data: { title, body } }`. Get the shape      # wrong and the recovery succeeds while the bindings come back empty.      # (A recover can also point at another task · 05-errors §recovery.)      recover:        data:          title: "Cached fallback article"          body: "The archived copy — good enough for a summary when the live API is down."  summarize:    with:      title: ${{ tasks.fetch_article.title }}      body: ${{ tasks.fetch_article.body }}    infer:      prompt: |        Summarize this article in 3 bullets.        Title · ${{ with.title }}        Body · ${{ with.body }}      max_tokens: 512# `title` is here so a reader can see WHICH path ran: offline it reads# "Cached fallback article". Read with `--output json`.outputs:  title: ${{ tasks.fetch_article.title }}  summary: ${{ tasks.summarize.output }}

nika-spec@6ac29351a · sha256 16e3c5cc01bff3ee