{
  "version": "0.1.0-draft",
  "templates": [
    {
      "name": "chain",
      "file": "chain.nika.yaml",
      "intent": "take data, produce words, save them",
      "patterns": [
        "deterministic gather",
        "one model job",
        "explicit persist"
      ],
      "slots": 9,
      "sha256": "5442edf406b8099a9dd8414e14e6073cd8df19c402d32889518883615a5ee78b",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · chain · gather facts → one model step → persist.\n# The default shape for « take real data, produce words, save them ».\n#\n# Instantiate (agents · deterministic) ·\n#   1. Copy this file · rename per the job (kebab-case).\n#   2. Fill every `# SLOT:` line · delete slot comments when filled.\n#   3. Validate · `nika check <file>` (or conformance/runner.py validate).\n#   4. Repair using the error's fix line · re-check until valid.\n# Rules the validator enforces · one verb per task · snake_case ids ·\n# every ${{ tasks.X }} reference REQUIRES depends_on: [X] (NIKA-DAG-003).\nnika: v1\nworkflow: chain-template            # SLOT: kebab-case workflow id\ndescription: \"gather → think → persist\"   # SLOT: one honest sentence\n\nmodel: ollama/qwen3.5:4b           # SLOT: provider/model · local · zero key\n\nvars:\n  source: \"./input.txt\"             # SLOT: your inputs · typed where required\n\ntasks:\n  - id: gather\n    invoke:                         # SLOT: the fact source · nika:read / nika:fetch / exec\n      tool: \"nika:read\"\n      args: { path: \"${{ vars.source }}\" }\n\n  - id: think\n    depends_on: [gather]\n    infer:\n      prompt: |\n        # SLOT: the one model job · interpolate ${{ tasks.gather.output }}\n        Summarize · ${{ tasks.gather.output }}\n\n  - id: persist\n    depends_on: [think]\n    invoke:\n      tool: \"nika:write\"\n      args:\n        path: \"./output.md\"         # SLOT: destination\n        content: \"${{ tasks.think.output }}\"   # ALWAYS pass content · a write without it writes nothing\n\noutputs:\n  result: ${{ tasks.think.output }}  # SLOT: the callable contract\n"
    },
    {
      "name": "gate-and-act",
      "file": "gate-and-act.nika.yaml",
      "intent": "watch X, act when Y",
      "patterns": [
        "jq extraction",
        "CEL skip-gate",
        "often zero model calls"
      ],
      "slots": 8,
      "sha256": "8e6ae3f6a33a8addcea8ef4f274d6765afd96789ba2dd86b3250f5aaaaf59eb4",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · gate-and-act: check a condition, act only when it holds.\n# The default shape for monitors, alerts and threshold jobs. Often\n# needs ZERO model calls (pattern 1 · deterministic core).\n#\n# Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.\n# Gate semantics (pick the right one · pattern 6) ·\n#   when:        SKIP gate · routing, not failure\n#   nika:assert  FAIL gate · the run is wrong, stop loudly\n#   nika:prompt  HUMAN gate · blocks until a person decides\nnika: v1\nworkflow: gate-and-act-template     # SLOT: kebab-case workflow id\ndescription: \"watch a value · act only when the condition holds\"   # SLOT\n\nvars:\n  source_url: \"https://api.example.com/v1/value\"   # SLOT: what to watch\n  threshold: 100                    # SLOT: the trigger condition value\n\nsecrets:\n  webhook:\n    source: env\n    key: ALERTS_WEBHOOK_URL         # SLOT: where the act lands\n    egress:\n      - to: \"nika:notify\"\n        host_from_self: true        # the secret value IS the destination URL\n\ntasks:\n  - id: check\n    invoke:\n      tool: \"nika:fetch\"\n      args:\n        url: \"${{ vars.source_url }}\"\n        mode: jq\n        jq: \".\"\n    output:\n      value: \".value\"               # SLOT: the jq path to the watched field\n\n  - id: act\n    depends_on: [check]\n    when: ${{ tasks.check.value > vars.threshold }}   # SLOT: the CEL condition\n    invoke:\n      tool: \"nika:notify\"           # SLOT: the action · notify / write / exec\n      args:\n        channel: webhook\n        target: \"${{ secrets.webhook }}\"\n        message: \"Threshold crossed · ${{ tasks.check.value }}\"   # SLOT\n        severity: warning\n\noutputs:\n  value: ${{ tasks.check.value }}\n"
    },
    {
      "name": "fanout",
      "file": "fanout.nika.yaml",
      "intent": "do this for EVERY item",
      "patterns": [
        "runtime collection",
        "the full leash (max_parallel",
        "fail_fast",
        "retry)"
      ],
      "slots": 9,
      "sha256": "7a16dcdb72c0c3ce9818ba8ec67b6d6e96c54387bc04411be221fa66085ab1ee",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · fanout: discover a collection, process every item in\n# parallel (with a leash), merge the results.\n# The default shape for « N pages / N files / N records » jobs.\n#\n# Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.\n# The leash (pattern 4 · ALWAYS set all three) ·\n#   max_parallel  cap concurrency (APIs rate-limit · GPUs thrash)\n#   fail_fast: false + on_error recover: null: one bad item yields null\n#     at its index (order preserved) · the batch lives · filter downstream\n#   per-iteration retry/timeout · the flaky world is normal\nnika: v1\nworkflow: fanout-template           # SLOT: kebab-case workflow id\ndescription: \"discover N items · process in parallel · merge\"   # SLOT\n\nmodel: ollama/qwen3.5:4b           # SLOT: provider/model · local · zero key\n\nvars:\n  collection_source: \"./items\"      # SLOT: where the collection comes from\n\ntasks:\n  - id: discover\n    invoke:                         # SLOT: glob / fetch sitemap / exec + jq split\n      tool: \"nika:glob\"\n      args: { pattern: \"${{ vars.collection_source }}/*.md\" }\n\n  - id: process\n    depends_on: [discover]\n    for_each: ${{ tasks.discover.output }}\n    max_parallel: 4                 # SLOT: the polite ceiling\n    fail_fast: false\n    timeout: \"60s\"                  # SLOT: per-iteration bound\n    retry:\n      max_attempts: 3\n      backoff_strategy: exponential\n      jitter: true\n    on_error:\n      recover: null                 # a failed item yields null at its index · the batch lives\n    infer:                          # SLOT: the per-item job (any verb)\n      prompt: |\n        Process this item · ${{ item }}\n\n  - id: survivors\n    depends_on: [process]\n    invoke:                         # the null-aware fan-in · order preserved\n      tool: \"nika:jq\"\n      args:\n        input: ${{ tasks.process.output }}\n        expression: \"[ .[] | select(. != null) ]\"\n\n\n  - id: merge\n    depends_on: [survivors]\n    infer:\n      prompt: |\n        # SLOT: the fan-in · the survivors array (failed items filtered)\n        Merge these results into one report · ${{ tasks.survivors.output }}\n\noutputs:\n  report: ${{ tasks.merge.output }}\n"
    },
    {
      "name": "etl-state",
      "file": "etl-state.nika.yaml",
      "intent": "only what changed since last run · survive bad input",
      "patterns": [
        "state read→diff→write",
        "`on_error: recover:` quarantine"
      ],
      "slots": 6,
      "sha256": "c52e0e449427189bb307262a0e90e35799e1783f0be2dd5ea7b1ee78d98fc26c",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · etl-state · incremental data job with a state file.\n# The default shape for « only the NEW » jobs (feeds · exports · syncs)\n# and for batch pipelines that must survive bad input.\n#\n# Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.\n# Two load-bearing moves ·\n#   on_error: recover:   bad data is DATA · the pipeline lives\n#   state read→diff→write   you only process what changed\nnika: v1\nworkflow: etl-state-template        # SLOT: kebab-case workflow id\ndescription: \"read state · fetch fresh · diff · process the delta · save state\"   # SLOT\n\nvars:\n  source_url: \"https://api.example.com/v1/records\"   # SLOT: the data source\n  state_path: \"./state/etl-state.json\"               # SLOT: the cursor file\n\ntasks:\n  # First run · no state file yet · recover to an empty list.\n  - id: empty\n    invoke:\n      tool: \"nika:jq\"\n      args: { input: [], expression: \".\" }\n\n  - id: previous\n    invoke:\n      tool: \"nika:read\"\n      args: { path: \"${{ vars.state_path }}\" }\n    on_error:\n      on_codes: [NIKA-BUILTIN-READ-001]   # not-found ONLY · a permission error still fails loudly\n      recover: ${{ tasks.empty.output }}\n\n  - id: fresh\n    invoke:\n      tool: \"nika:fetch\"            # SLOT: fetch / read / exec · the fresh data\n      args:\n        url: \"${{ vars.source_url }}\"\n        mode: jq\n        jq: \".records\"\n\n  - id: delta\n    depends_on: [previous, fresh]\n    invoke:\n      tool: \"nika:json_diff\"        # RFC 6902 · empty patch = nothing new\n      args:\n        before: \"${{ tasks.previous.output }}\"\n        after: \"${{ tasks.fresh.output }}\"\n\n  - id: process\n    depends_on: [delta]\n    when: ${{ size(tasks.delta.output) > 0 }}\n    invoke:\n      tool: \"nika:jq\"               # SLOT: the delta job (jq · infer · write…)\n      args:\n        input: \"${{ tasks.delta.output }}\"\n        expression: \"length\"\n\n  - id: save_state\n    depends_on: [fresh]\n    invoke:\n      tool: \"nika:write\"\n      args:\n        path: \"${{ vars.state_path }}\"\n        content: \"${{ tasks.fresh.output }}\"\n        create_dirs: true\n        overwrite: true\n\noutputs:\n  changes:\n    value: ${{ tasks.delta.output }}\n    type: array\n    description: \"RFC 6902 ops since last run · empty = no-op run\"\n"
    },
    {
      "name": "agent-loop",
      "file": "agent-loop.nika.yaml",
      "intent": "research / review / open-ended",
      "patterns": [
        "plan-then-execute",
        "default-deny tools",
        "budgets",
        "typed final message"
      ],
      "slots": 7,
      "sha256": "4df4e00ef5b20ef29fa81b7998af5bbbb59aecc622aa2023079d53678f1bd348",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · agent-loop · plan with a fast model, execute with a\n# budgeted agent, validate the typed result.\n# The default shape for open-ended work (research · review · triage).\n#\n# Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.\n# Three leashes (pattern 5 + 8 · NEVER ship an unleashed agent) ·\n#   tools:    default-deny · grant the MINIMUM · nika:done ends cleanly\n#   max_turns + max_tokens_total · the worst case is bounded\n#   schema:   the final message is TYPED · prose is not a contract\nnika: v1\nworkflow: agent-loop-template       # SLOT: kebab-case workflow id\ndescription: \"plan → budgeted agent → typed result\"   # SLOT\n\nmodel: ollama/qwen3.5:4b           # SLOT: a tool-calling model · local by default\n\nvars:\n  goal:\n    type: string\n    required: true\n    description: \"What the agent must accomplish\"   # SLOT\n\ntasks:\n  - id: plan\n    infer:\n      prompt: \"Break '${{ vars.goal }}' into at most 4 concrete steps.\"   # SLOT\n      schema:\n        type: object\n        required: [steps]\n        properties:\n          steps: { type: array, items: { type: string } }\n\n  - id: execute\n    depends_on: [plan]\n    agent:\n      system: \"Work the plan step by step. Call nika:done when finished.\"   # SLOT\n      prompt: \"Plan · ${{ tasks.plan.output.steps }}\"\n      tools:                        # SLOT: the MINIMUM grant for the job\n        - \"nika:read\"\n        - \"nika:done\"\n      max_turns: 15                 # SLOT: the loop bound\n      max_tokens_total: 80000       # SLOT: the spend bound\n      schema:                       # SLOT: the typed final-message contract\n        type: object\n        required: [findings]\n        properties:\n          findings: { type: array, items: { type: string } }\n\n  - id: confirm\n    depends_on: [execute]\n    invoke:\n      tool: \"nika:assert\"\n      args:\n        condition: \"${{ size(tasks.execute.output.findings) > 0 }}\"\n        message: \"Agent returned no findings, do not trust an empty run\"   # SLOT\n\noutputs:\n  findings:\n    value: ${{ tasks.execute.output.findings }}\n    type: array\n    description: \"The agent's typed findings\"   # SLOT\n"
    },
    {
      "name": "human-gated-ship",
      "file": "human-gated-ship.nika.yaml",
      "intent": "anything irreversible (deploy · send · publish)",
      "patterns": [
        "parallel gates",
        "assert",
        "`nika:prompt` GO",
        "`on_finally` record"
      ],
      "slots": 10,
      "sha256": "24b29fb8ced2e7b0949aaada98ef04bbeac95f74a5a7382181425bcde96eb238",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · human-gated-ship: gates in parallel, a human signs,\n# the action ships, evidence always lands.\n# The default shape for anything irreversible (deploys · sends · publishes).\n#\n# Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.\n# The contract (patterns 6 + 9) ·\n#   nothing irreversible happens before nika:prompt returns true\n#   the assert makes a RED gate terminal · the act fails LOUDLY (default\n#   capture) · a terminal `when: true` task records EVERY outcome\n#   (acted · refused · failed): the always-pattern\nnika: v1\nworkflow: human-gated-ship-template  # SLOT: kebab-case workflow id\ndescription: \"verify in parallel · human GO · act · record\"   # SLOT\n\npermits:                            # SLOT: the blast radius · default-deny once present\n  exec: [\"echo\"]                    # SLOT: ONLY the programs the gates + act run (argv form)\n  tools: [\"nika:assert\", \"nika:prompt\", \"nika:notify\"]\n  net: { http: [\"hooks.slack.com\"] }   # SLOT: the webhook host · nothing else may leave\n\nsecrets:\n  webhook:\n    source: env\n    key: TEAM_WEBHOOK_URL           # SLOT: where the record lands\n    egress:\n      - to: \"nika:notify\"\n        host_from_self: true        # the secret value IS the destination URL\n\ntasks:\n  # ── the verification wave · all checks run in parallel ──\n  - id: check_a\n    exec:\n      command: [\"echo\", \"ok\"]        # SLOT: gate 1 (argv form · injection-safe)\n      capture: structured\n\n  - id: check_b\n    exec:\n      command: [\"echo\", \"ok\"]        # SLOT: gate 2\n      capture: structured\n\n  - id: gates\n    depends_on: [check_a, check_b]\n    invoke:\n      tool: \"nika:assert\"\n      args:\n        condition: \"${{ tasks.check_a.output.exit_code == 0 && tasks.check_b.output.exit_code == 0 }}\"\n        message: \"A gate is RED: refusing to proceed\"   # SLOT\n\n  - id: human\n    depends_on: [gates]\n    invoke:\n      tool: \"nika:prompt\"\n      args:\n        message: \"All gates GREEN. Proceed?\"   # SLOT: the decision, fully informed\n        default: false\n\n  - id: act\n    depends_on: [human]\n    when: ${{ tasks.human.output == true }}\n    exec:\n      command: [\"echo\", \"shipped\"]   # SLOT: the irreversible action (argv · program must be in permits.exec)\n      # default capture · a failing ship fails LOUDLY (NIKA-EXEC-001):\n      # never `capture: structured` on the irreversible step (exit codes\n      # would become data and a red ship would read as success)\n\n  - id: record\n    depends_on: [act]\n    when: true                      # the always-pattern · runs on success, failure, OR refusal\n    invoke:\n      tool: \"nika:notify\"\n      args:\n        channel: webhook\n        target: \"${{ secrets.webhook }}\"\n        message: \"Run finished · act=${{ tasks.act.status }}\"   # SLOT · success | failure | skipped\n        severity: info\n\noutputs:\n  acted: ${{ tasks.act.status }}\n"
    },
    {
      "name": "website-brief",
      "file": "website-brief.nika.yaml",
      "intent": "understand a site (domain · theme · assets) from a URL",
      "patterns": [
        "fetch `traverse:` crawl",
        "one typed infer",
        "explicit persist",
        "zero exec"
      ],
      "slots": 10,
      "sha256": "c9ca853a728703f116e6f759e658bf18fe9ee0706c9efd9c40fa92f9fb5b0e1d",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · website-brief · crawl a site → understand it → persist the brief.\n# The shape for « understand a site (domain · theme · assets) from a URL ».\n#\n# Instantiate (agents · deterministic) ·\n#   1. Copy this file · rename per the job (kebab-case).\n#   2. Fill every `# SLOT:` line · delete slot comments when filled.\n#   3. Validate · `nika check <file>` (or conformance/runner.py validate).\n#   4. Repair using the error's fix line · re-check until valid.\n# Native-first · the crawl is `traverse:` (no curl · no helper script);\n# the brief is one typed infer; the persist is `nika:write`. Zero exec.\nnika: v1\nworkflow: website-brief-template    # SLOT: kebab-case workflow id\ndescription: \"crawl → brief → persist\"   # SLOT: one honest sentence\n\nmodel: ollama/qwen3.5:4b            # SLOT: provider/model · local · zero key\n\nvars:\n  site_url: \"https://example.com\"   # SLOT: the site to understand\n  out_path: \"./out/brief.json\"      # SLOT: where the brief lands\n\ntasks:\n  - id: crawl_site\n    invoke:\n      tool: \"nika:fetch\"\n      args:\n        url: \"${{ vars.site_url }}\"\n        traverse: { max_pages: 5 }  # SLOT: crawl bound · 1..=25 (robots honored)\n\n  - id: brief\n    depends_on: [crawl_site]\n    infer:\n      max_tokens: 1200\n      prompt: |\n        # SLOT: the one model job · what should the brief capture?\n        From this site crawl, produce a creative brief: the domain of\n        activity, the dominant visual theme, the audience, the usable\n        colors and image assets.\n        Crawl digest · ${{ tasks.crawl_site.output }}\n      schema:                       # SLOT: the typed shape downstream tasks rely on\n        type: object\n        additionalProperties: false\n        properties:\n          domain: { type: string }\n          theme: { type: string }\n          audience: { type: string }\n          colors: { type: array, items: { type: string } }\n          assets: { type: array, items: { type: string } }\n        required: [domain, theme, audience, colors, assets]\n\n  - id: persist\n    depends_on: [brief]\n    invoke:\n      tool: \"nika:write\"\n      args:\n        path: \"${{ vars.out_path }}\"\n        create_dirs: true\n        content: \"${{ tasks.brief.output }}\"   # ALWAYS pass content\n\noutputs:\n  brief: ${{ tasks.brief.output }}   # SLOT: the callable contract\n"
    },
    {
      "name": "media-asset-pack",
      "file": "media-asset-pack.nika.yaml",
      "intent": "generate image/audio assets from a brief",
      "patterns": [
        "`nika:image_generate`",
        "`nika:jq` manifest",
        "local/mock provider first"
      ],
      "slots": 10,
      "sha256": "05765547f12024ef40d41e3c5bd397fc1a6082635dc3f07f802e1265eb5294e7",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · media-asset-pack · brief → generate assets → manifest.\n# The shape for « generate image (or audio) assets from a creative brief ».\n#\n# Instantiate (agents · deterministic) ·\n#   1. Copy this file · rename per the job (kebab-case).\n#   2. Fill every `# SLOT:` line · delete slot comments when filled.\n#   3. Validate · `nika check <file>` (or conformance/runner.py validate).\n#   4. Repair using the error's fix line · re-check until valid.\n# Native-first · generation is `nika:image_generate` (no provider curl ·\n# no OpenAI helper script); the manifest is `nika:jq` + `nika:write`.\nnika: v1\nworkflow: media-asset-pack-template # SLOT: kebab-case workflow id\ndescription: \"brief → render → manifest\"  # SLOT: one honest sentence\n\nmodel: ollama/qwen3.5:4b            # SLOT: provider/model · local · zero key\n\nvars:\n  subject: \"a calm cosmic landing hero\"   # SLOT: what the asset is about\n  out_dir: \"./out/assets\"           # SLOT: where assets land\n\ntasks:\n  - id: brief\n    infer:\n      max_tokens: 600\n      prompt: |\n        # SLOT: the creative direction · style · constraints\n        Write one vivid, concrete image prompt for: ${{ vars.subject }}.\n        No text in the image · no watermark · a calm central zone.\n      schema:\n        type: object\n        additionalProperties: false\n        properties:\n          image_prompt: { type: string }\n        required: [image_prompt]\n\n  - id: render\n    depends_on: [brief]\n    invoke:\n      tool: \"nika:image_generate\"\n      args:\n        provider: mock              # SLOT: local | openai | gemini | xai (local/mock first)\n        prompt: \"${{ tasks.brief.output.image_prompt }}\"\n        output_dir: \"${{ vars.out_dir }}\"\n        filename_prefix: \"asset\"    # SLOT: filename stem\n\n  - id: manifest\n    depends_on: [brief, render]\n    invoke:\n      tool: \"nika:jq\"\n      args:\n        expression: \"{ brief: .[0], images: .[1].images }\"\n        input:\n          - \"${{ tasks.brief.output }}\"\n          - \"${{ tasks.render.output }}\"\n\n  - id: persist\n    depends_on: [manifest]\n    invoke:\n      tool: \"nika:write\"\n      args:\n        path: \"${{ vars.out_dir }}/manifest.json\"\n        create_dirs: true\n        content: \"${{ tasks.manifest.output }}\"\n\noutputs:\n  manifest: ${{ tasks.manifest.output }}  # SLOT: the callable contract\n"
    },
    {
      "name": "api-upload-and-create",
      "file": "api-upload-and-create.nika.yaml",
      "intent": "call a product API: upload a file, then create from it",
      "patterns": [
        "fetch `multipart:` upload",
        "masked secrets header",
        "mode/jq extraction"
      ],
      "slots": 15,
      "sha256": "ca872e2ac9f1dd2f1b4f46faf48af80326aa9ce06ce29d40afd76a37fe272916",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · api-upload-and-create · upload a file → create a resource from it.\n# The shape for « call a product API: multipart upload, then a JSON create ».\n#\n# Instantiate (agents · deterministic) ·\n#   1. Copy this file · rename per the job (kebab-case).\n#   2. Fill every `# SLOT:` line · delete slot comments when filled.\n#   3. Validate · `nika check <file>` (or conformance/runner.py validate).\n#   4. Repair using the error's fix line · re-check until valid.\n# Native-first · the upload is fetch `multipart:` (file parts ride the\n# permits.fs READ boundary); auth rides a masked secrets header; the\n# create is fetch JSON; extraction is the mode/jq pairing. Zero exec ·\n# no upload helper script.\nnika: v1\nworkflow: api-upload-and-create-template  # SLOT: kebab-case workflow id\ndescription: \"upload → create → result\"   # SLOT: one honest sentence\n\nmodel: ollama/qwen3.5:4b            # SLOT: provider/model · local · zero key\n\nvars:\n  api_base: \"https://api.example.com\"     # SLOT: the product API base\n  asset_path: \"./out/assets/asset-1.png\"  # SLOT: the file to upload\n\nsecrets:\n  API_KEY:\n    source: env\n    key: EXAMPLE_API_KEY            # SLOT: the OS env var holding the key\n    egress:\n      - to: \"nika:fetch\"            # the send · default-deny otherwise\n      - to: \"outputs\"               # the return value derives from the authed response\n\ntasks:\n  - id: upload\n    invoke:\n      tool: \"nika:fetch\"\n      args:\n        url: \"${{ vars.api_base }}/upload\"   # SLOT: the upload endpoint\n        method: POST\n        headers:\n          x-api-key: \"${{ secrets.API_KEY }}\"  # SLOT: the auth header name\n        multipart:\n          - { name: file, path: \"${{ vars.asset_path }}\" }\n          # SLOT: extra text fields · { name: directory, value: \"assets\" }\n        mode: jq\n        jq: \".url\"                  # SLOT: where the response carries the asset URL\n\n  - id: create\n    depends_on: [upload]\n    invoke:\n      tool: \"nika:fetch\"\n      args:\n        url: \"${{ vars.api_base }}/create\"   # SLOT: the create endpoint\n        method: POST\n        headers:\n          x-api-key: \"${{ secrets.API_KEY }}\"\n        body:                       # SLOT: the create payload (objects auto-JSON)\n          asset_url: \"${{ tasks.upload.output }}\"\n        mode: jq\n        jq: \"{ id: .id, url: .url }\"   # SLOT: the fields downstream needs\n\noutputs:\n  result: ${{ tasks.create.output }}   # SLOT: the callable contract\n"
    },
    {
      "name": "docker-report",
      "file": "docker-report.nika.yaml",
      "intent": "read a system's state (docker · kubectl · gh), explain it, keep the report",
      "patterns": [
        "argv-array exec (provable allowlist)",
        "parallel reads",
        "exec ledger",
        "one artifact"
      ],
      "slots": 10,
      "sha256": "a72ad242a22698e5f506fe1a3fe23a4147e9ab1b5812df99e79fc52fe1c24c6b",
      "yaml": "# SPDX-License-Identifier: Apache-2.0\n# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json\n#\n# TEMPLATE · docker-report: read a system's real state via a pinned CLI,\n# have a model explain it, keep the report as a file. The audited-ops\n# shape: argv-array exec (a provable `permits.exec` allowlist — a shell\n# string can smuggle any program through a pipe, so the checker refuses\n# to bless one), reads fan out in parallel, one model call, one artifact.\n# Swap `docker` for any product CLI (kubectl · gh · terraform · psql).\n#\n# Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.\n#\n# EXEC LEDGER · (the native-first law: every surviving exec names why)\n# | task | command          | why no native path               | unlock that removes it |\n# | ps   | docker ps        | product CLI · no MCP surface yet | a docker MCP server    |\n# | df   | docker system df | same                             | same                   |\nnika: v1\nworkflow: docker-report-template    # SLOT: kebab-case workflow id\ndescription: \"read the daemon's state · explain it · keep the report\"   # SLOT\n\nmodel: ollama/qwen3.5:4b            # SLOT: local-first · mock/echo for offline rehearsal\n\npermits:\n  exec:\n    - \"docker\"                      # SLOT: the ONE program the reads may launch\n  tools:\n    - \"nika:write\"\n  fs:\n    write:\n      - \"./docker-health.md\"        # SLOT: where the report lands (must match `keep`)\n\ntasks:\n  # The reads run IN PARALLEL (no depends_on between them) — the\n  # scheduler proves it from the DAG, nobody orders it.\n  - id: ps\n    exec:\n      # SLOT: argv ARRAY form — one program, exactly these arguments\n      command: [\"docker\", \"ps\", \"--all\", \"--format\", \"{{.Names}}\\t{{.Status}}\\t{{.Image}}\"]\n\n  - id: df\n    exec:\n      command: [\"docker\", \"system\", \"df\"]   # SLOT: the second read (drop the task if one suffices)\n\n  - id: diagnose\n    depends_on: [ps, df]\n    infer:\n      # SLOT: what should the model DO with the readings?\n      prompt: |\n        You are reading a Docker host's state. Containers (name·status·image):\n        ${{ tasks.ps.output }}\n\n        Disk usage:\n        ${{ tasks.df.output }}\n\n        Write a short health report: what is running, what exited, what\n        looks unhealthy (restart loops · old exits), and whether disk\n        usage needs attention. Plain prose, no preamble.\n      max_tokens: 600               # SLOT: the spend ceiling for this call\n\n  - id: keep\n    depends_on: [diagnose]\n    invoke:\n      tool: \"nika:write\"\n      args:\n        path: \"./docker-health.md\"  # SLOT: same path as permits.fs.write\n        content: \"${{ tasks.diagnose.output }}\"\n\noutputs:\n  report: ${{ tasks.keep.output }}\n"
    }
  ]
}
