App-karr

 view release on metacpan or  search on metacpan

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

`disabled` leads the flag list and the `disabled:` line carries the reason
(`no reason given` when none was stored). The `agent` flag is suppressed for a
disabled board, because that agent will never run there; otherwise it names
which agent (`agent:minimax`, plus ` failing` when that one is unavailable). A
board the assignment routes to nothing runnable right now gets `agent-waiting`
and a `waiting:` line with the reason — it is an agent board whose agents are
down, not a board nobody configured, and the two are fixed by different things.
The boards are followed by an `Agents` block where the local config defines any
(`ok`, or `failing since … next attempt at …`, with `(coordinator)` beside the
one marked as such; `--verbose` adds each one's kind and description) and by the
hub's open questions where there are any.

## Options

```bash
karr-foundation --config PATH       # custom config file
karr-foundation --force             # run even if no board change / open tasks
karr-foundation --dry-run --verbose # preview without executing
karr-foundation --status            # read-only overview of every board, no runs
```

Agent output streams to the terminal when run interactively (TTY) or with
`--verbose`, and is always appended to `.karr.log`.

## Exit codes

The same contract as `karr` (ADR 0002), because `ask`, `answer` and `chain` are
typed by people and scripted by agents, not only run by cron:

- **0** — the tick finished: boards drained, an overview printed, a question
  asked or answered, the chain worked through. A chain step that **failed**
  does not change this — that is a statement about the plan, not about this
  binary.
- **1** — runtime failure: no repository discovered, a config that does not
  parse, a hub command with no hub, an answer to a question that already had
  one, or `chain` unable to fetch `refs/karr-foundation/*`.
- **2** — usage error: an unknown command, an unknown option, an invalid option
  value, a missing or surplus positional argument.

A run killed by `SIGTERM`, `SIGINT` or `SIGHUP` exits `128 + signal` after
taking its agents down with it.

## Drain loop semantics

Each iteration runs `command` once, then classifies result:

| Outcome | Meaning | Action |
|---------|---------|--------|
| **progress** | board changed | keep draining |
| **stall** | a task *this run's agent engaged* didn't move | bump attempt counter; auto-block after `max_attempts` |
| **common-error** | bad exit, timeout, or an error pattern in a run that moved *nothing* | exponential backoff, no task penalty |
| **idle** | agent did nothing, grabbed nothing | stop |

**What a run did is asked before what it printed.** A run that exited 0 and
moved the board is progress whatever scrolled past it, and is never
reclassified by its own transcript; the output is scanned only for a run that
moved nothing at all — which is what a rate-limited or unauthenticated agent
looks like. A pattern seen in a run that *did* move the board is noted in
`.karr.log` and otherwise ignored. The default patterns are narrow to match: a
symptom word counts next to a failure word on the same line (`network error`,
`invalid credentials`, `quota exceeded`), and an HTTP status only where
something adjacent marks it as one (`API error: 429`, `429 Too Many Requests`)
— not in a diffstat, a byte count or a line number. Before that, an agent
printing its own board tripped the scan on a backlog title and throttled a
healthy board to one run per hour (#160).

### Auto-block

When a task is stuck after `max_attempts`, foundation marks it blocked with:
```
blocked: auto-block: no progress after N attempts (foundation)
```
Agent can override with `karr edit --block "reason"`.

**Engaged** means foundation can prove the agent worked that card during *this*
drain: it runs the command with `KARR_ROLE=agent`, so the agent's `karr` writes
land in the board's activity log under the `agent` identity, and only tasks
named there — unclaimed, or held under a claim name the agent itself wrote
with — can be penalized. A card somebody else holds is never auto-blocked,
nor is one the agent merely left claimed in an earlier run (that is what
`claim_timeout` and `karr unlock` are for). Without that evidence — an agent
command that never calls `karr` — foundation auto-blocks **nothing** rather
than guess (#158).

### Exponential cooldown

On common-error: repo waits `cooldown_base × 2^level` minutes (capped at `cooldown_max`).
Level resets on next clean (non-error) run, which also drops `last_error` from
`.karr.state` — it describes the last run, not a past one.

## 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



( run in 0.898 second using v1.01-cache-2.11-cpan-007c89162af )