NAME
App::karr::Cmd::Pick - Atomically find and claim the next available task
VERSION
version 0.600
SYNOPSIS
karr pick --claim agent-fox
karr pick --claim agent-fox --status todo --move in-progress
karr pick --claim agent-fox --tags backend,urgent --json
karr pick --claim agent-fox --compact
DESCRIPTION
Selects the next available task for an agent, taking class of service, priority, blocked state, and claim expiry into account. When the board lives in a Git repository, the command also uses lock refs so concurrent agents do not pile onto the same candidate.
Which cards qualify and what order they come in are not defined here: they are App::karr::Role::PickRules, which App::karr::Foundation::Picker composes as well, so karr-foundation's ticket mode names the card this command would hand out instead of a second opinion about it (ticket #198). What belongs to this command is everything on top of that ranking -- the claim, the lock and the compare-and-swap below -- and the --status and --tags options, which narrow the shared test rather than replace it.
SELECTION RULES
Eligible statuses
If
--statusis omitted, tasks in the board's terminal statuses are excluded -- its final configured status andarchived, which on the default board meansdoneandarchived.Claim timeout
Already claimed tasks are ignored unless their claim timestamp has expired according to
claim_timeout. Aclaimed_byof the empty string is not a claim; it is how kanban-md spells "unclaimed".Ordering
Candidates are sorted by class of service, then by priority, then by task id. The class and priority lists come from the board's own configuration (
prioritiesandclassesinconfig.yml), not from a hardcoded table -- so a board imported from kanban-md with a longer priorities list ranks according to its own list. Lower class index is more urgent; higher priority index is more urgent (matches kanban-md's pick.go).fixed-dateWhere both candidates carry the
fixed-dateclass, the due date is asked before priority: the card due sooner goes first, and afixed-datecard with no due date sorts behind every dated one. Undated on both sides, or the same date on both, leaves priority to decide. Only that class, and only against itself -- afixed-datecard meeting any other class is ranked by class index alone, whatever the dates say. This too is kanban-md's rule (itssortPickCandidates), and it is where a class of service that exists for a deadline stops being ranked by urgency instead.--moveOptionally updates the picked task to a new status such as
in-progress.
JSON OUTPUT
With --json a successful pick prints the picked task as a JSON object, and picking nothing prints {"picked":null}. Either way the exit status is 0, so a polling agent decodes the payload and tests for a task rather than reading the exit code or the message text.
COMPACT OUTPUT
--compact ends the plaintext output after the assignment line: no Status | Priority | Class line and no body. It is what kanban-md spends a flag of its own on (pick --no-body), and it is the whole of the option here -- --json renders the full task either way, and the dependency warnings are unaffected, because they go to STDERR and answer to --quiet rather than to a flag about how much of STDOUT to print.
EXCLUSIVITY
The board is read once to rank candidates, but nothing is decided on that reading. Every candidate is re-read from its ref after its lock is taken, tested against the same predicate a second time, and written back under a compare-and-swap on the OID it was just read from. An agent that loses that swap has picked nothing and moves to the next candidate.
That belt-and-braces shape is not defensive programming, it is the fix for #86. The lock ref alone cannot make a pick exclusive: its holder identity is the clone's user.email, which every agent on one machine shares, so twelve parallel picks each acquired the lock quite legitimately, each acted on a snapshot taken before any lock existed, and each wrote its claim over the previous one -- nine agents were told they owned task 1, and the card named only the last of them. The lock now only keeps agents off each other's candidates; the compare-and-swap is what binds the claim.
LOCK EXPIRY
The lock is taken, used, and released within one command, and (since #45) it is released before the push rather than after it, so it is never published to the remote on the success path.
An agent that dies in between still leaves one behind, so locks expire: the board's lock_timeout (default 5m) is how long one may be held before another agent takes it over. It is deliberately a separate knob from claim_timeout (default 1h) -- a claim covers a work session, a lock covers the few milliseconds this command spends writing one card, and reusing the claim window would leave a crashed agent's task unpickable for an hour. App::karr::Cmd::Unlock is the manual escape hatch.
SEE ALSO
karr, App::karr, App::karr::Role::PickRules, App::karr::Foundation::Picker, App::karr::Cmd::List, App::karr::Cmd::Move, App::karr::Cmd::Handoff, App::karr::Cmd::AgentName, App::karr::Cmd::Unlock
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/karr/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)