the path · 06
Code-review agent — a loop you do not write.
run itnika try 06-code-review
06-code-review.nika.yaml
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## 06 · Code-review agent — a loop you do not write.## The first five files are graphs: you decide the steps. Here you decide the# BOUNDS — which tools, how many turns, how many tokens — and the model# decides the steps inside them. That is the whole trade, and the reason the# grants below matter more than the prompt does.## Demonstrates ·# - the `agent:` verb · model + tools, looping until done or out of budget# - `tools:` · a default-deny whitelist · the agent gets ONLY these# - `max_turns:` + `max_tokens_total:` · the two budget caps# - `nika:done` · the completion sentinel that lets the loop end cleanly# - the workflow boundary applies INSIDE the loop · granting the agent# `nika:read` is not enough, the files need `permits.fs.read` too## Runs fully offline on a local tool-calling model, against a fixture# committed next to this file. Measured: 2 turns, ~1.6k tokens of the 8000# budget — it reads once, then reports. Wall clock is whatever your machine# and a resident-or-cold model make it (30s and 5m both observed here).## Needs · ollama with the model pulled · `ollama pull qwen3.5:4b`.## Run · nika run examples/06-code-review.nika.yaml (from the repo root ·# paths resolve against your working directory, not the file's location)nika: v1workflow: id: code-reviewmodel: ollama/qwen3.5:4b # local tool-calling model · zero key · the diff never leaves the machineconst: target: "./examples/fixtures/review-me.rs"permits: tools: ["nika:done", "nika:read"] fs: # The agent may call `nika:read`, and it may read THIS file. Those are two # different permissions and both are required — a tool grant with no # `fs.read` behind it fails mid-loop with # « NIKA-SEC-004 · agent tool `nika:read` refused by the security boundary », # after the model has already spent a turn deciding to read. # # To review your own file, change this line AND `const.target` together. # The permit does not follow the path around; that is what makes it a wall. # (A bound cannot interpolate — `${{ const.target }}` here is NIKA-AUTH-007.) read: ["./examples/fixtures/review-me.rs"]tasks: review: agent: # Default-deny: this list IS the agent's universe. Anything not named # here does not exist as far as the loop is concerned — there is no # ambient toolbox to opt out of. tools: - "nika:read" # open the file under review - "nika:done" # say "finished" and end the loop cleanly # Two independent stops, because they fail differently: a model that # ping-pongs forever hits `max_turns`, a model that reads something huge # hits `max_tokens_total`. The loop also just ends when the model # returns no tool call. max_turns: 8 max_tokens_total: 8000 system: | You are a senior Rust reviewer. Read the file you are given with nika:read, then reply with your findings as a short markdown list — one line per finding, each starting with blocker/high/med/low. Then call nika:done. prompt: "Review ${{ const.target }} and report bugs and style issues."# The agent's final message. It is free text — an agent answer is prose unless# you constrain it, and constraining it is a separate job (see 04 for the# schema machinery).outputs: findings: ${{ tasks.review.output }}nika-spec@6ac29351a · sha256 b5a6e43e79108d70…