Git-native kanban for shared helper agents, human operators, and downstream repos that want a board without checking a board directory into the work tree.
karr keeps canonical state in refs/karr/*, not in commits, branches, or a
persistent karr/ folder. Tasks, config, logs, snapshots, and helper refs move
through normal Git transport, which makes the tool fit naturally into AI-heavy
and multi-machine workflows.
Most task tools assume a central web service or a checked-in file tree.
karr takes a different route:
- board state lives in Git refs
- mutating commands pull, materialize, write back, and push
- tasks stay separate from branches and commits
- downstream projects can vendor the CLI through Docker and keep the exact same UX
That gives you a shared board with far fewer file-level collisions and without having to bolt on another ticket system just to coordinate agents.
Inside an existing Git repository:
karr init --name "My Project"
karr create "Fix login bug" --priority high
karr list
karr boardClaim and progress work:
NAME=$(karr agentname)
karr pick --claim "$NAME" --status todo --move in-progress
karr handoff 1 --claim "$NAME" --note "Ready for review" --timestampProtect yourself before destructive operations:
karr backup > karr-backup.yml
karr restore --yes < karr-backup.yml
karr destroy --yescpanm App::karrThe published images are:
raudssus/karr:latestraudssus/karr:user
latest is the ergonomic default. It starts as root only long enough to inspect
/work, then drops to the owner of the mounted workspace before running
karr. That keeps host files from becoming root-owned.
user is the fixed-user image. It defaults to 1000:1000 and is the better
base when you want a deterministic downstream derivative.
Minimal smoke test:
docker run --rm -it -w /work -v "$(pwd):/work" raudssus/karr:latest --helpRecommended alias for real use:
alias karr='docker run --rm -it \
-w /work \
-e HOME=/home/karr \
-v "$(pwd):/work" \
-v "$HOME/.gitconfig:/home/karr/.gitconfig:ro" \
-v "$HOME/.claude:/home/karr/.claude" \
-v "$HOME/.codex:/home/karr/.codex" \
-v "$HOME/.cursor:/home/karr/.cursor" \
raudssus/karr:latest'With that alias, all normal commands stay identical:
karr init --name "HandyIntelligence Prototype" --claude-skill
karr skill install --agent codex --global --force
karr create "Document release workflow"If you want a custom fixed-user image in CI or a downstream repo:
docker build --target runtime-user \
--build-arg KARR_UID=1010 \
--build-arg KARR_GID=1010 \
-t raudssus/karr:user1010 .The write path:
pull refs -> change task/config -> push refs
Commands work directly against refs via C. A materialized F view is generated on demand (see C) and is never committed — it is always in F<.gitignore>.
Important refs:
- C<refs/karr/config> holds sparse YAML config overrides
- C<refs/karr/meta/next-id> holds the next numeric task id
- C<refs/karr/tasks/EidE/data> holds task Markdown plus frontmatter
- C<refs/karr/log/EagentE> holds append-style JSON log lines
| Command | Use it for |
|---|---|
karr init |
create the board in refs/karr/* |
karr config |
inspect and change merged board settings |
karr backup |
export the whole board as YAML |
karr restore --yes |
replace the board from a YAML snapshot |
karr destroy --yes |
remove the board completely |
karr sync |
explicitly pull/push board refs |
| Command | Use it for |
|---|---|
karr create |
create a task |
karr list |
filter and search tasks |
karr show |
inspect one task in full |
karr edit |
update body, metadata, claim, or blocked state |
karr move |
change status explicitly or with --next / --prev |
karr archive |
soft-delete into archived |
karr delete |
permanently remove the task ref |
| Command | Use it for |
|---|---|
karr board |
grouped board view |
karr pick |
atomic next-task selection with claim |
karr handoff |
move into review and append a note |
karr context |
generate agent-facing board summary |
karr log |
inspect per-agent or per-task activity |
karr agentname |
generate short claim names |
| Command | Use it for |
|---|---|
karr skill install |
install bundled skills for Claude Code, Codex, or Cursor |
karr skill check |
detect outdated installed skills |
karr skill update |
refresh installed skills |
karr set-refs |
store shared non-task payloads in allowed refs |
karr get-refs |
fetch helper payloads back out |
NAME=$(karr agentname)
# pick the best available task
karr pick --claim "$NAME" --status todo --move in-progress
# inspect board state
karr board
karr list --claimed-by "$NAME"
# hand off to review
karr handoff 1 --claim "$NAME" --note "Implementation complete" --timestamp
# inspect activity trail
karr log --agent "$NAME"pick respects blocked state, claim timeout, and class-of-service ordering:
expeditefixed-datestandardintangible
karr-foundation is a single-shot, idempotent companion binary that drives
agents across one or more boards. Point cron (or a systemd timer, or a tight
loop) at it and it scans configured repos, decides whether there is work, and
drains each board — running the configured agent command repeatedly until
no actionable task remains.
# every 5 minutes
*/5 * * * * karr-foundation
karr-foundation --force # run regardless of board state
karr-foundation --dry-run --verboseIt reads ~/.config/karr-foundation/config.yml (or --config):
dirs:
- /path/to/repo1 # explicit board repos
scan:
- /path/to/parent-dir # auto-discover direct subdirs that have a .karrEach repo carries a .karr file describing how to run its agent:
command: claude -p "Use karr-coordinator agent, pick next task"
on_idle: skip # 'skip' (default) | 'always-run'
max_runtime: 1800 # per-command SIGKILL + total drain budget (seconds)
drain: true # loop until drained (default) | false: single run
max_attempts: 2 # stalls on one task before auto-block
max_iterations: 50 # hard cap on drain iterations
cooldown_base: 1 # cooldown minutes at level 0
cooldown_max: 64 # cooldown ceiling in minutes
error_patterns: # extra case-insensitive substrings → common-error
- my custom api errorAfter every agent run, foundation classifies the outcome from what it can observe — exit code, board ref movement, and the run's captured output:
- progress — the board changed; keep draining.
- stall — a task the agent claimed or left
in-progressdid not move. Its attempt counter is bumped, and atmax_attemptsit is auto-blocked (ablocked:reason is written so it drops out of the actionable set and the drain can finish). The agent may always set a better reason itself withkarr edit --block; the auto-block is only a fallback so the loop always terminates. - common-error — a non-zero/timeout exit, or output matching a known
pattern (rate limit, auth, network, 5xx, plus your
error_patterns). No task is penalized; the repo enters an exponential cooldown (cooldown_base× 2^level minutes, capped atcooldown_max, reset on the next clean run) and is skipped until it expires. - idle — the agent did nothing and grabbed nothing; stop.
Concurrent invocations are safe: a PID lock per repo means a cron tick that
fires while an agent is still running simply skips that repo. All per-repo state
is gitignored: .karr.state (board hash, per-task attempts, cooldown, last
error), .karr.lock, and .karr.log.
Not all shared workflow state belongs in tasks. karr also supports arbitrary
non-protected refs outside refs/karr/*.
karr set-refs superpowers/spec/1234.md draft ready
karr get-refs superpowers/spec/1234.mdUse this for:
- planning blobs
- generated specs
- agent scratch state
- workflow metadata you want synced through Git but not modeled as cards
Protected namespaces such as branches, tags, remotes, stash, and refs/karr/*
are blocked.
The distribution ships a bundled karr skill that can be installed locally in
a repo or globally in the current home directory.
karr skill install
karr skill install --agent claude-code
karr skill install --agent codex --global --force
karr skill check --global
karr skill updateSupported targets:
claude-codecodexcursor
Project-local Claude installation during board setup:
karr init --name "My Project" --claude-skillBackups are full YAML snapshots of refs/karr/*:
karr backup > karr-backup.ymlRestore is intentionally destructive:
karr restore --yes < karr-backup.ymlIt deletes current refs/karr/* refs first and then replays the snapshot.
Full board removal is explicit too:
karr destroy --yesIf a remote exists, restore and destroy also prune the remote board state
to match.
Tasks live in refs/karr/tasks/*/data, but the payload itself is ordinary
Markdown with YAML frontmatter:
---
id: 1
title: Fix login bug
status: in-progress
priority: high
class: standard
claimed_by: agent-fox
created: 2026-03-12T10:00:00Z
updated: 2026-03-12T10:00:00Z
---
Task description here.That makes the format easy to inspect, script, and reuse from Perl code.
karr is primarily a CLI, but the lower-level modules are usable from Perl:
use App::karr::Git;
use App::karr::BoardStore;
my $git = App::karr::Git->new(dir => '.');
my $store = App::karr::BoardStore->new(git => $git);
my $config = $store->effective_config;
my @tasks = $store->load_tasks;Or create a task directly:
use App::karr::Task;
my $task = App::karr::Task->new(
id => $store->allocate_next_id,
title => 'Write release notes',
status => 'backlog',
priority => 'high',
);
$store->save_task($task);
$git->push;Perl installation is the normal local path, but Docker is equally valid when a
downstream repo wants to vendor karr instead of adding a direct Perl tool
dependency. That keeps the command surface identical across:
- local Perl installs
- Codex/Claude/Cursor-heavy repos
- CI or ops environments that prefer containerized tooling
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.