showcase · tier 3 · business
Resume screener
One local-model rubric per candidate, with enums and required evidence quotes. jq does the ranking.
The whole file
8 tasks · 8 waves · the plan falls out of the bindings, nothing is scheduled by hand.
t3-resume-screener.nika.yamlsource
nika: v1workflow: id: resume-screener description: "glob CVs → local-model rubric per candidate → deterministic shortlist"model: ollama/qwen3.5:4b # PII stays on the machine · the whole screen is offlinepermits: # the file IS the blast radius · no net category at all: fs: # CVs cannot leave this machine even if a prompt is hijacked read: ["./hiring/inbox/**"] write: ["./hiring/out/**", "./hiring/shortlist-brief.md"] tools: ["nika:glob", "nika:read", "nika:jq", "nika:write"]vars: role: "Senior Rust engineer" cv_glob: "./hiring/inbox/*.md" shortlist_size: 5tasks: pool: invoke: tool: "nika:glob" args: { pattern: "${{ vars.cv_glob }}" } cvs: with: paths: ${{ tasks.pool.output }} # the collection crosses the boundary here for_each: ${{ with.paths }} max_parallel: 8 fail_fast: false # one unreadable CV must not stop the batch on_error: recover: null # null keeps the zip aligned · filtered later invoke: tool: "nika:read" args: { path: "${{ item }}" } pairs: with: paths: ${{ tasks.pool.output }} texts: ${{ tasks.cvs.output }} invoke: # zip path + content · order survived the nulls tool: "nika:jq" args: input: ["${{ with.paths }}", "${{ with.texts }}"] expression: "transpose | map(select(.[1] != null)) | map({path: .[0], text: .[1]})" screened: with: pairs: ${{ tasks.pairs.output }} # pre-fan-out import · the collection cv_path: ${{ item.path }} # re-evaluated per iteration for_each: ${{ with.pairs }} max_parallel: 2 # local model · don't thrash the GPU fail_fast: false on_error: recover: null infer: prompt: | Role · ${{ vars.role }} Candidate file · ${{ with.cv_path }} CV · ${{ item.text }} Score this candidate against the role. Quote evidence from the CV for every rating: no rating without a quote. schema: type: object required: [file, fit, strengths, concerns] properties: file: { type: string } fit: { type: string, enum: [strong, possible, weak] } years_relevant: { type: integer } strengths: { type: array, items: { type: string } } concerns: { type: array, items: { type: string } } ranked: with: screened: ${{ tasks.screened.output }} invoke: tool: "nika:jq" args: input: "${{ with.screened }}" expression: 'map(select(. != null)) | map(select(.fit != "weak")) | sort_by(.fit != "strong", -(.years_relevant // 0))' shortlist: with: ranked: ${{ tasks.ranked.output }} invoke: tool: "nika:jq" args: input: "${{ with.ranked }}" expression: ".[:${{ vars.shortlist_size }}]" brief: with: shortlist: ${{ tasks.shortlist.output }} when: ${{ size(with.shortlist) > 0 }} infer: prompt: | Write the screening brief for these shortlisted candidates · ${{ with.shortlist }} One paragraph each · lead with the evidence quotes · end with the suggested interview focus. save: with: brief: ${{ tasks.brief.output }} when: ${{ with.brief != null }} # skip cascades by VALUE · a skipped brief reads null invoke: tool: "nika:write" args: path: "./hiring/shortlist-brief.md" content: "${{ with.brief }}" create_dirs: trueoutputs: shortlist: value: ${{ tasks.shortlist.output }} description: "Ranked candidates · strong first, then by relevant years"nika inspect · engine 0.105.0 · vendored graph, never re-derived
8 declared edges · 8 waves · the plan falls out of the bindings