App-karr

 view release on metacpan or  search on metacpan

.claude/skills/karr-foundation-cli/SKILL.md  view on Meta::CPAN

  - /path/to/repo1
  - /path/to/repo2
scan:
  - /path/to/parent-dir   # finds dirs with .karr file

# Per-repo .karr file (in each repo root)
command: claude -p "Use karr-coordinator agent, pick next task"
on_idle: skip
drain: true
max_runtime: 1800
max_attempts: 2

# Run via cron every 5 minutes
*/5 * * * * karr-foundation
```

## Config file

Default: `~/.config/karr-foundation/config.yml`

```yaml
dirs:
  - /path/to/repo1
  - /path/to/repo2

scan:
  - /path/to/parent-dir   # finds direct children with .karr file

concurrent: 4             # boards that may have an agent at once (default: 1)
hub: /path/to/hub-repo    # the repo carrying refs/karr-foundation/* (the chain)
routing: >-               # prose for the coordination agent; karr never parses it
  minimax is cheap and does the routine work. Never hand it a release.
```

## Per-repo .karr file

Place in repo root. All keys optional. Agent execution is opt-in: a board runs
an agent only if one of seven sources names a command, and the first one that
does wins:

```
--command  >  config default_command  >  .karr command  >  .karr agent
           >  the assignment  >  config default_agent  >  claude: true
```

A literal command string is the most specific thing that can be said, a named
agent (see "Named agents") sits below it, and `claude: true` is the oldest and
least specific. The assignment (see "The coordination agent") is the routing
table written for this machine: it is asked only for a board that names no
agent of its own, and it beats `default_agent` because it is per repository
where that is per fleet. A board naming an agent the config does not define is
an error that skips **that board**, not one that silently stops running. With
no agent on any board, `karr-foundation` prints a read-only overview instead of
running anything (see "Overview").

```yaml
claude: true              # synthesize the canonical claude command (opt-in)
claude_bin: claude        # binary for claude: true (default: claude)
claude_max_turns: 30      # --max-turns for claude: true (default: 30)
claude_permission_mode: bypassPermissions   # (default: bypassPermissions)
prompt: >-                # agent instruction, exposed to the command as $PROMPT
  Use the karr-coordinator skill: pick the next actionable task and move it.
# command: claude -p "$PROMPT"   # explicit command; wins over claude: true
# agent: minimax          # a named agent from the config's 'agents:' section
on_idle: skip             # 'skip' (default) | 'always-run'
mode: drain               # drain (default) | single | ticket
drain: true               # older spelling of mode: true=drain, false=single
max_runtime: 1800         # seconds: per-run TERM, then KILL 2s later (0 = off)
max_attempts: 2           # stalls on one task before auto-block (default: 2)
max_iterations: 50        # hard cap on drain iterations / drain budget (default: 50)
cooldown_base: 1          # cooldown minutes at level 0 (default: 1)
cooldown_max: 64          # cooldown ceiling in minutes (default: 64)
error_patterns:           # extra case-insensitive substrings → common-error
  - my custom api error
on_drained: ./release-gate.sh   # run when the board has no work left
on_drained_max_runtime: 1800    # seconds for that command (0 = no limit)
on_drained_max_rounds: 3        # see "The domain hook" (0 = no cap)
```

`claude`, `claude_bin`, the other `claude_*` knobs, `mode` and the three
`on_drained*` keys may also be set in `config.yml` under the same name;
`command`, `prompt` and `agent` have config-wide spellings of their own
(`default_command`, `default_prompt`, `default_agent`). The per-repo `.karr`
value wins in every case — including `on_drained: ""`, which is how one board
opts out of a fleet-wide hook.

## Named agents

A board has one command; a fleet has several agent commands with different
strengths and different failure modes. `config.yml` names them, a `.karr` picks
one with `agent:`:

```yaml
agents:
  minimax:
    command: claude_with_minimax
    kind: claude-code       # the invocation contract; default: shell
    probe_every: 15m        # retry interval once it stops working
    permission_mode: bypassPermissions    # kind: claude-code only
    max_turns: 30                         #   "     "        "
    allowed_tools: [ Bash, Edit ]         #   "     "        "
    concurrent: 2           # runs of THIS agent at once — see "Concurrency"
    description: >-
      Prose. What this agent is good at, where it is weak, what it costs.
  planner:
    command: claude
    kind: claude-code
    role: coordinator       # the fleet's judgement layer — see below

default_agent: minimax    # for boards whose .karr names none
probe_every: 10m          # fleet-wide default for agents that name none
```

`kind` says what karr may append to `command`. `shell` (the default) is a
complete template karr appends **nothing** to — it cannot know what the thing
at the other end understands. `claude-code` gets `-p "$PROMPT"`,
`--output-format stream-json --verbose --include-partial-messages`, and
`--permission-mode` / `--max-turns` / `--allowed-tools` from the definition;
stream-json rather than plain `json` because karr needs the run's own result
object *and* the live output, and plain `json` prints nothing until the run
ends. The ticket of a `mode: ticket` run is never appended — it travels as
`$PROMPT`'s closing sentence and as `$KARR_TASK`.

`description` is never read by karr. It is carried for the agent that routes
work across the fleet: the thing choosing is a language model and reads prose,
so there are no classes and no enums. `--status --verbose` prints it.

`role` marks the one agent that is the fleet's judgement layer (see "The
coordination agent"). `coordinator` is the only value; anything else is a config
error, and two marked definitions are refused rather than guessed between —
"which of these is the judgement layer" has no safe default. `--status` prints
`(coordinator)` beside it.

**Availability.** karr keeps the least it can per agent: `ok`, or `failing`
since a moment with the next attempt due at another. No cost, no tokens, no
quotas — a rate limit and a spent budget look identical from the outside. A
drain ending in `common-error` marks its agent failing; any other outcome says
it works. While an agent is failing, **every** board on it is skipped, and
`--force` does not override that either — the wait is bounded by `probe_every`
and ends by itself. When the next attempt comes round the agent is simply run
again on the work that was waiting: the probe **is** the run, and every
recovery is recorded so a rhythm can be read out later.

.claude/skills/karr-foundation-cli/SKILL.md  view on Meta::CPAN

## The domain hook (`on_drained`)

When a board has **drained** — no actionable task left on it, everything done,
archived or blocked — `on_drained` runs a configured command in it. karr does
not know what that command does and must not: the exit code goes to `.karr.log`
and `.karr.state` and is interpreted by nobody. A hook that fails does not park
the board, does not mark the board's agent failing and is never the run's
`last_error`; it is not an agent run and is not classified as one, so no report
is read out of it, no error pattern is matched against it, no ticket is
assigned to it.

It is told where it is and nothing else: `KARR_REPO`, and `KARR_ROLE=hook` so
its own `karr` writes land in their own activity log instead of counting as the
agent's engagement with a card. `PROMPT` and `KARR_TASK` are empty. It runs in
the board's directory, under the board's own `.karr.lock`, with the same
process-group kill and the same tee to `.karr.log` an agent gets — but on its
own budget, `on_drained_max_runtime` (default 1800), because how long an agent
may take says nothing about how long a release gate may.

A drain that ended in `common-error` does not count as drained: a rate-limited
agent leaves a board that looks exactly like one it worked through, and
foundation does not believe that run. Two guards bound the hook, and `--force`
overrides both:

- **The same board is not asked twice.** The board fingerprint the hook last
  ran at is kept in `.karr.state`; a board that has not moved since gets no
  second run — otherwise a repository nobody touches starts a gate on every
  tick for ever, because a drained board stays drained.
- **A chain that never settles is capped.** Consecutive rounds in which the
  hook itself put work back on the board are counted; a run that leaves the
  board alone — the gate that finally passed — clears the count, and at
  `on_drained_max_rounds` (default 3, `0` disables) the hook is suppressed with
  a line in `.karr.log`.

A hook that files tickets is the point, not a failure mode: the board is no
longer drained, the next tick works them, the board drains again and the hook
is asked again.

## State files (gitignored)

```
.karr.state   # board hash, per-task attempts, cooldown, last error, last
              # report, and the hook's fingerprint / rounds / last exit
.karr.lock    # flock'd lock: one agent per repo, however many ticks knock
.karr.log     # run log
```

Agent availability is not among them — it is not per board and does not live in
the repository at all (see "Named agents"), and neither is the assignment (see
"The coordination agent"). Both sit beside `config.yml`.

## Environment

During agent execution foundation sets:

- `KARR_REPO` — the repo path
- `KARR_ROLE` — the identity nested `karr` calls write under: `agent` for an
  agent run (`refs/karr/log/agent/<email>`), `hook` for `on_drained`, `chain`
  for a `kind: shell` chain step, `coordinator` for the coordination agent; a
  human defaults to `user`
- `PROMPT` — the resolved agent instruction (`prompt` / `default_prompt` /
  built-in default), referenced as `$PROMPT` in the command template; in ticket
  mode it ends with the sentence naming the assigned task, for a hook it is
  empty, and for the coordination agent it is that agent's own instruction
  rather than the board's
- `KARR_TASK` — the id of the task a `mode: ticket` run was given, empty in
  every other mode

## Cron example

```bash
# Every 5 minutes, all repos
*/5 * * * * karr-foundation

# With verbose logging to syslog
*/5 * * * * karr-foundation --verbose 2>&1 | logger -t karr-foundation
```

## Enabling agent runs for a repo fleet

Each repo needs a `.karr` file with a command that invokes an agent on the
next available task. Example:

```yaml
command: claude -p "Use karr CLI to pick next task, implement it fully, hand off or close"
on_idle: skip
drain: true
max_runtime: 900
max_attempts: 2
cooldown_base: 2
cooldown_max: 32
```

To initialize karr in a repo:
```bash
cd /path/to/repo
karr init --name my-project
karr create "Example task" --priority high
```

Then add the `.karr` file and configure foundation to scan the parent dir.

To take a single repo out of a fleet that runs on a global `default_command`,
run `karr disable --reason "why"` in that repo — see "Board-level disable".



( run in 0.629 second using v1.01-cache-2.11-cpan-aadc1410aed )