App-karr
view release on metacpan or search on metacpan
lib/App/karr/Foundation.pm view on Meta::CPAN
# With a $ticket the built-in default changes (the ordinary one opens by
# telling the agent to pick its own work) and the assignment sentence is
# appended to whatever prompt was resolved. Appending rather than replacing
# keeps a configured prompt doing its job â it is usually about which skill to
# use and how to report â while the last sentence, which is the one that wins
# with a language model, is the one naming the card. Without this the mode
# would be `drain: false` with extra steps: the agent would never learn which
# ticket it was given.
sub _prompt_for {
my ( $self, $karr, $ticket ) = @_;
my $configured = $karr->{prompt} // $self->_config_data->{default_prompt};
return $configured // $DEFAULT_PROMPT unless defined $ticket;
return ( $configured // $DEFAULT_TICKET_PROMPT ) . "\n\n"
. sprintf( $TICKET_ASSIGNMENT, $ticket );
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::karr::Foundation - Single-shot foundation daemon -- periodic agent execution across karr boards
=head1 VERSION
version 0.600
=head1 SYNOPSIS
# Typical cron entry -- run every 5 minutes
*/5 * * * * /path/to/karr-foundation
# Force a run regardless of board state
karr-foundation --force
# Preview what would run
karr-foundation --dry-run --verbose
# Read-only overview of every board (no agent runs)
karr-foundation --status
# Write the fleet's plan into the hub, and execute it out of there
karr-foundation plan < chain.yml
karr-foundation chain
=head1 DESCRIPTION
F<karr-foundation> is a single-shot, idempotent CLI meant to be invoked
periodically (cron, systemd-timer, while-loop). It scans configured karr
boards, detects changes or open work, and B<drains> each board by invoking the
configured agent command repeatedly until no actionable task remains.
B<Using this class as a library.> F<bin/karr-foundation> is what most callers
run, and it is also where karr's character/octet boundary gets set up (see
L<App::karr::Encoding>) before any command code runs: a C<:encoding(UTF-8)>
layer goes on C<STDOUT>/C<STDERR>, and C<@ARGV> is decoded before
C<new_with_options> reads it into option values. This class does not repeat
either step -- both are the program's decision, not one a class it merely
loads should make for it (see L<App::karr::Encoding/enable_std_utf8> and
L<App::karr::Encoding/decode_argv>). A caller that loads
C<App::karr::Foundation> directly, instead of invoking that script, is
responsible for both:
use App::karr::Encoding qw( decode_argv enable_std_utf8 );
enable_std_utf8();
decode_argv();
App::karr::Foundation->new_with_options->run(@ARGV);
Skipping the handles does not fail outright: every fixed message this class
prints or warns is plain ASCII (ticket #214). What it does not cover is data
-- a non-ASCII repo path folded into a C<skip $repo -- $wait> line, or a YAML
error carried through C<clean_error> into a C<warn> -- which still risks
C<Wide character in print>/C<warn> the first time it reaches a handle nobody
configured. Skipping C<@ARGV> is quieter, not safer: option values built from
it hold raw UTF-8 octets instead of decoded characters, with no warning to
say so.
B<Config file:> C<~/.config/karr-foundation/config.yml> (or C<--config>).
dirs:
- /path/to/repo1
- /path/to/repo2
scan:
- /path/to/parent-dir # finds all direct subdirs that have a .karr file
concurrent: 4 # boards that may have an agent at once (default: 1)
hub: /path/to/hub-repo # the repository carrying refs/karr-foundation/*
routing: >- # prose for the coordination agent, never parsed
minimax is cheap; never hand it a release.
B<Per-repo .karr file:>
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 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
on_idle: skip # 'skip' (default) | 'always-run'
max_runtime: 1800 # seconds: per-command SIGKILL (0 = no limit)
mode: drain # drain (default) | single | ticket
drain: true # older spelling of mode: true=drain, false=single
max_attempts: 2 # stalls on one task before auto-block (default: 2)
max_iterations: 50 # hard cap on drain iterations (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 # (added to the defaults; matched as written)
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)
agent: minimax # a named agent from the config's 'agents:' section
C<claude>, C<claude_bin>, C<claude_max_turns>, C<claude_permission_mode>,
C<command>, C<mode>, C<on_drained>, C<on_drained_max_runtime>,
C<on_drained_max_rounds> and C<prompt>/C<default_prompt> may also be set
globally in the config file; the per-repo F<.karr> value wins.
B<Named agents.> A board has one C<command>. A fleet has several agent commands
with different strengths and different failure modes, so the config can name
them and a board can pick one:
agents:
minimax:
command: claude_with_minimax
kind: claude-code # the invocation contract; default: shell
probe_every: 15m # optional -- see "Agent availability" below
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: >-
( run in 0.886 second using v1.01-cache-2.11-cpan-364913b4093 )