the workflow library
deep-research-brief
an agent works the web inside hard caps, then the brief. A pack showcase file, shown whole to read; nika run deep-research-brief.nika.yaml is the honest affordance. This site never fabricates a replay for it.
- 5tasksthe plan below walks them
- 4wavesrun together when they can
- invoke · infer · agentverbsthe whole grammar it speaks
- browseread, then runno fabricated replay
the plan
derived from the file, never drawn by handsluginvokeplaninfer
investigateagent
briefinfer
saveinvoke
the file, whole
deep-research-brief.nika.yamldeep-research-brief.nika.yaml
nika: v1workflow: id: deep-research-brief description: "plan → budgeted research agent → thinking synthesis → brief on disk"model: ollama/qwen3.5:4b # local · zero key · `--model mock/echo` rehearses the whole fileinputs: topic: type: string default: "local-first AI runtimes" description: "What to research · free text (--var topic=…), and it never touches a path directly"permits: tools: ["nika:done", "nika:fetch", "nika:jq", "nika:write"] net: # There is no `*` here and there never will be: `net.http` entries are exact # host names. An agent told to « research the web » therefore cannot — the # corpus it may reach is a decision taken HERE, before the loop starts, # rather than discovered afterwards in a trace. Add the sources you actually # trust; the agent's fetch is judged against this list at RUN, and `check` # cannot see it coming because the agent chooses its own URLs. http: - "arxiv.org" - "export.arxiv.org" - "en.wikipedia.org" - "hn.algolia.com" fs: # One brief per topic, written DIRECTLY in ./research/. `*` matches a single # segment and never crosses `/`, so even a slug that somehow kept a slash is # refused rather than steered into a subtree — `./research/**` would let it # through. The slug task below makes that doubly hard; the wall makes it # impossible, and only one of the two is a guarantee. write: ["./research/*.md"]tasks: # The topic is free text — « Local-First AI Runtimes: a survey/2026 » — and it # ends up in a filename. Deriving the slug in jq keeps that arithmetic out of # the model AND out of the path: the expression is total, so there is no input # for which it returns something containing a separator. slug: invoke: tool: "nika:jq" args: input: "${{ inputs.topic }}" expression: 'ascii_downcase | gsub("[^a-z0-9]+"; "-") | gsub("^-|-quot;; "")' plan: infer: max_tokens: 400 # planning is short · cap it and the cost report is a ceiling prompt: "Break '${{ inputs.topic }}' into 4 sharp research queries · no overlap." schema: # `additionalProperties: false` on every object node — without it the # shape is a suggestion and each provider answers with a slightly # different object. `plan.output.queries` is read below, so it has to be # a promise, not a hope. type: object additionalProperties: false required: [queries] properties: queries: type: array items: { type: string } investigate: with: queries: ${{ tasks.plan.output.queries }} agent: system: | You are a rigorous researcher. Work the queries one by one · fetch sources · keep verbatim quotes · note what each source actually supports, and what it does not. If a fetch is refused, record that the source was out of bounds rather than substituting another. Call nika:done when the plan is exhausted. prompt: "Research plan · ${{ with.queries }}" tools: # Every id here must also appear in `permits.tools` above — `check` # refuses the mismatch statically (NIKA-SEC-004 · agent tool … is # outside permits.tools). Note what is NOT granted: `nika:write`. The # agent's product is the typed object it returns, so it never needs a # file of its own, and the workflow keeps the only pen in the room. - "nika:fetch" # the corpus named in permits.net.http - "nika:done" # the loop-completion sentinel max_turns: 25 # the leash · a loop that cannot end is a bill max_tokens_total: 150000 # and the second leash, in tokens schema: type: object additionalProperties: false required: [findings, sources] properties: findings: type: array items: { type: string } sources: type: array items: { type: string } brief: with: findings: ${{ tasks.investigate.output.findings }} sources: ${{ tasks.investigate.output.sources }} infer: max_tokens: 2000 prompt: | Findings · ${{ with.findings }} Sources · ${{ with.sources }} Write the executive brief · what's true · what's contested · what it means · what to do. Two pages max. Anything the sources do not support goes in « contested », never in « true ». thinking: # A reasoning budget, honoured by seats that support extended thinking # and ignored by the ones that do not — declaring it never fails a run. enabled: true budget_tokens: 8000 save: with: slug: ${{ tasks.slug.output }} brief: ${{ tasks.brief.output }} invoke: tool: "nika:write" args: path: "./research/${{ with.slug }}.md" content: "${{ with.brief }}" create_dirs: trueoutputs: brief: ${{ tasks.brief.output }} sources: value: ${{ tasks.investigate.output.sources }} description: "Every source the agent actually used" slug: value: ${{ tasks.slug.output }} description: "The filename stem the brief landed under · ./research/<slug>.md"lines 68–85: agent: two tools granted, caps it cannot exceed. Source: the spec pack · open it in the playground →
Watch the recorded one replay on the home page, walk the showcase gallery, or install and run the file yourself.