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…