Inspiration

A photographer comes home from one shoot with hundreds of near-identical frames, many of them bursts. One vendor puts manual culling of thousands of photos at 3 to 5 hours (aftershoot.com/culling); another says 6 to 10 hours (filterpixel.com/culling). Shoot Triage is a first pass: it rates, a person decides. A UGREEN AI NAS already holds the photos and offers a vision model through its photo service, so the culling can start where the files live.

What it does

Shoot Triage takes one folder from one shoot and:

  • lists the images in file name order, 40 per run, and flags exact duplicates; a bigger shoot takes several runs: the Skill names the next start number and asks to rate the next 40 files
  • reads each photo's capture time and camera model, and has the NAS photo expert's vision model score each image from 1 to 5 on sharpness, exposure, composition and subject
  • lets written integer rules, not the model, set every verdict (keeper, maybe or reject) with a one-line reason, so the same scores and bars always give the same verdict
  • after the first report, lets you move the keeper bar (16 by default, 13 to 19) and the reject line (12 by default, 8 to 12); the verdicts are recomputed from the same scores without rating again, the report says which frames moved, and a changed report names its bars
  • groups frames taken within one second of a burst's first frame, keeps at most one keeper per burst, and gives every other strong frame of the burst a reason such as "weaker than IMG_0102.jpg (4.50 vs 4.75)"
  • lists the close calls to check first, such as a frame rejected only for sharpness
  • saves a cull report next to the files once the user confirms

Optional extras, each only after the user confirms the exact file list: apply existing NAS tags to the rated files, or fill a Keepers album.

It never deletes, moves, renames or overwrites a file.

How we built it

Shoot Triage is a Markdown-only Skill: SKILL.md, one rubric file and one test plan, no scripts. Claude Code agents wrote it and checked it with the organizer's Uliya Skill Kit (0.7.3). The parent Skill never calls a CLI itself. It delegates each step to an expert agent:

  • file-agent: ug-filesystem ls, ug-filemgr scan-dedupe, and tag-list / tag-add-file for the optional tags
  • photo-agent: ug-photo meta (capture time and camera model, for bursts) and vision-vlm for the rating, create-album for the optional album
  • knowledge-agent: ug-knowledge save-text for the report

Images are rated one per vision-vlm call, ten per chunk. The model only supplies scores and a short reason. The rubric is written as integer rules: by default a frame is rejected when sharpness is 2 or less or the four scores total under 12, and it is a keeper at a total of 16 with no score below 3; the user can move those two totals within stated limits. The Skill applies the rules itself.

We also wrote the same rules as a small offline reference model with 51 tests on invented data: every possible score set, burst windows, ties, cameras, RAW and JPEG pairs, 60-file shoots split over two runs, and moved keeper bars and reject lines. The model is not part of the Skill package. Every CLI, action and parameter the Skill uses is checked by a script against the kit's capability catalog, and the package passes the kit's static checker with 0 errors and 0 warnings. Nothing has been run on a NAS yet.

AI disclosure: built by Kylne700 with an AI coding assistant (Claude). Claude Code agents (Anthropic's Claude models) wrote the Skill text, the rubric, the proposal and the check scripts. Drex Max, a small judging model, only answered yes/no questions, such as whether a claim is supported by its source or which of two package versions to keep; it wrote no text. No third-party components are included.

Challenges we ran into

  • The kit's checker is stricter than its template (exact heading names, no README), so we followed the checker.
  • The catalog does not confirm that a Skill can create a named tag, so the Skill only reuses tags that already exist and skips tagging cleanly if there are none. The cull report never depends on an unverified step.
  • Bursts without a picture model: the Skill compares capture times in whole seconds, not picture content, so a long continuous burst can split in two groups, each with its own keeper. We chose a rule we can test over a cleverer one we cannot.
  • Our first rubric could call one frame both a maybe and a reject. We replaced it with ordered rules and tested all 625 possible score sets.

Accomplishments that we're proud of

  • Explainable: every verdict has a reason, and a frame that loses its burst names the winner with both averages.
  • Repeatable: the model supplies scores and the written rules decide, so a verdict can be re-checked by hand.
  • Honest: a "check these first" list shows the fragile calls instead of hiding them.
  • Safe: read-mostly, with a yes required for every write and no delete path at all.
  • Careful with photos: the Skill adds no outside service and names no outside model, so images go only to the NAS photo expert.

What we learned

A good Skill is mostly careful wording: clear inputs, explicit confirmations, and an honest message for every state (completed, failed, cancelled, waiting for the user, partial results). Writing the rules as code as well as prose found gaps in the prose.

What's next for Shoot Triage (plans, not built)

  • Validate runtime behaviour on a real UGREEN AI NAS: vision model speed, the EXIF output format, approval prompts, tag and album writes, and where the vision model runs.
  • Compare a burst across the 40-file run boundary instead of within each run.

Built With

  • claude-code
  • markdown
  • ugos
  • ugreen-ai-nas
  • uliya-skill-kit
  • vision-vlm
Share this project:

Updates

Submission history