Skip to main content
T2 chain · finance / freelance: the human-in-the-loop example. nika:prompt blocks the workflow until someone answers; the drafting is automated, the sending decision never is.

The job

Friday: open the ledger, find who’s late, write polite-but-firm reminders, don’t send the awkward one to the client who paid this morning. This file filters the CSV deterministically (jq, not an LLM guessing at numbers), drafts every reminder, then stops and asks you.

The shape

The file

invoice-chaser.nika.yaml

How it works

1

Data work is jq work

CSV → JSON via nika:convert, then map(select(.status == "overdue")) in nika:jq. No model touches the numbers: deterministic, free, auditable.
2

size() gates the whole tail

The list crosses the boundary (overdue: ${{ tasks.overdue.output }} in with:), then when: ${{ size(with.overdue) > 0 }} is the canonical empty-check. No overdue invoices → the rest of the file skips (and value edges pass the skip along as null).
3

The approval gate

nika:prompt blocks until answered (CI can use default:). The save binds the verdict (approved: ${{ tasks.approve.output }}) and runs only when: ${{ with.drafts != null && with.approved == true }}.

Constructs you just used

Make it yours

  • Per-client emails instead of one file: for_each over the overdue rows (fan-out tier).
  • Escalate 60-days-late differently: a second jq filter + a sterner prompt.
  • Wire the approved drafts to your mail API with nika:fetch method: POST.

Next · Support triage

Classify a whole queue in one schema-typed call, then let jq slice the urgent ones out.