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 )