App-Raider
view release on metacpan or search on metacpan
.claude/skills/perl-ai-langertha/SKILL.md view on Meta::CPAN
<raider>
## Raider â Autonomous Agent
```perl
use Langertha::Raider;
my $raider = Langertha::Raider->new(
engine => $engine, # With MCP servers
mission => 'You are a code reviewer.',
max_iterations => 10, # Max tool rounds per raid
# Optional:
max_context_tokens => 4000,
context_compress_threshold => 0.75,
compression_engine => $cheap_model,
raider_mcp => 1, # Enable self-tools (ask_user, pause, abort)
plugins => ['Langfuse'],
);
# Raid (autonomous tool-calling loop)
my $result = await $raider->raid_f('Review lib/App.pm');
.claude/skills/perl-ai-langertha/SKILL.md view on Meta::CPAN
if ($result->is_question) {
my $next = await $raider->respond_f('Yes, go ahead.');
}
# History management
$raider->add_history('user', $content); # Replay from DB
$raider->clear_history; # Reset
# Metrics
my $m = $raider->metrics;
say "Iterations: $m->{iterations}";
say "Tool calls: $m->{tool_calls}";
```
### Raid Loop (simplified)
1. Auto-compress history if context threshold exceeded
2. Gather tools from MCP servers + inline tools + self-tools
3. Build conversation: mission + history + new messages
4. Call LLM with tools
5. If tool calls: execute via MCP, add results to conversation, loop
6. If no tool calls: extract final text, persist to history, return result
7. Max iterations safety limit
</raider>
<plugins>
## Plugin System
```perl
package Langertha::Plugin::MyGuardrails;
use Langertha qw( Plugin );
async sub plugin_before_tool_call {
.claude/skills/perl-mcp/SKILL.md view on Meta::CPAN
mcp_servers => [$mcp],
);
# 4a. One-shot tool calling
my $response = await $engine->chat_with_tools_f('Add 42 and 17');
# 4b. Or Raider for multi-turn
my $raider = Langertha::Raider->new(
engine => $engine,
mission => 'You are a calculator.',
max_iterations => 10,
);
my $result = await $raider->raid_f('Add 42 and 17');
}
main()->get;
```
</async-integration>
<tool-description-tips>
## Writing Good Tool Descriptions
fails with "invalid to load directly"; now detected via
Term::ReadLine->new + ->ReadLine =~ /Gnu/.
- Readline history now persists across Ctrl-C / SIGTERM via signal
handlers that call WriteHistory before exit.
0.001 2026-04-20 17:44:05Z
- Initial release.
- CLI agent (App::Raider) wrapping Langertha::Raider with a default
viking persona ("Langertha"), caveman-style communication, and
effectively unlimited tool-calling iterations per raid.
- Tools: filesystem (list_files, read_file, write_file, edit_file),
bash (MCP::Server::Run::Bash), web_search and web_fetch
(Net::Async::WebSearch + Net::Async::HTTP).
- Engine auto-detection from available *_API_KEY env var with cheap
per-engine default models (claude-haiku-4-5, gpt-4o-mini, etc.).
- .raider.md for persona customization; hot-reload via /reload;
/prompt spawns a sub-agent prompt-builder that writes .raider.md.
- .raider.yml for engine options (temperature, response_size, ...)
with flat or default/<engine> layers; -o key=value CLI override.
- Skill / profile loading: --claude loads CLAUDE.md and
GetOptions(
'e|engine=s' => \$opt{engine},
'm|model=s' => \$opt{model},
'r|root=s' => \$opt{root},
'M|mission=s' => \$opt{mission},
'k|api-key=s' => \$opt{api_key},
'o|option=s@' => \@raw_engine_opts,
'i|interactive' => \$opt{interactive},
'json' => \$opt{json},
'max-iterations=i' => \$opt{max_iterations},
'no-color' => \$opt{no_color},
'trace!' => \$opt{trace},
'customize-prompt' => \$opt{customize_prompt},
'claude' => \$opt{profile_claude},
'openai|codex' => \$opt{profile_openai},
'skills=s@' => \@{ $opt{skill_dirs} //= [] },
'export-skill:s' => \$opt{export_skill},
'export-claude-skill:s'=> \$opt{export_claude_skill},
'h|help' => \$opt{help},
) or die "Bad options. Try --help.\n";
-k, --api-key KEY API key (overrides *_API_KEY env var)
-o, --option KEY=VALUE Engine attribute (repeatable), e.g.
-o temperature=0.2 -o response_size=4096
Merged over .raider.yml; CLI wins.
-r, --root DIR Working directory (default: cwd). File tools are
confined to this directory.
-M, --mission TEXT System prompt / mission
-i, --interactive REPL mode (default when stdin is a TTY with no
prompt argv and no pipe; forces it otherwise)
--json Emit JSON ({response, metrics, elapsed}) and exit
--max-iterations N Hard safety cap on tool rounds per raid
(default: 10000 â effectively unlimited)
--no-color Disable ANSI colors
--no-trace Hide live tool-call progress output
--customize-prompt Launch the prompt-builder at startup
--claude Load Claude Code layout: CLAUDE.md +
.claude/skills/*/SKILL.md.
--openai / --codex Load AGENTS.md (the OpenAI Codex / cross-tool
convention).
--skills DIR Load *.md files from DIR as skills (repeatable).
--export-skill [PATH]
exit 0;
}
my %args;
$args{engine} = $opt{engine} if defined $opt{engine};
$args{model} = $opt{model} if defined $opt{model};
$args{root} = $opt{root} if defined $opt{root};
$args{mission} = $opt{mission} if defined $opt{mission};
$args{api_key} = $opt{api_key} if defined $opt{api_key};
$args{trace} = $opt{trace} if defined $opt{trace};
$args{max_iterations} = $opt{max_iterations} if defined $opt{max_iterations};
$args{engine_options} = \%engine_opts if %engine_opts;
my @skill_specs;
my @cli_profiles;
if ($opt{profile_claude}) {
push @skill_specs, @{ $App::Raider::AGENT_PROFILES{claude} };
push @cli_profiles, 'claude';
}
if ($opt{profile_openai}) {
push @skill_specs, @{ $App::Raider::AGENT_PROFILES{openai} };
if ($cmd eq 'clear') {
$app->raider->clear_history;
my $t = $app->trace_plugin;
$t->token_stats({ prompt => 0, completion => 0, total => 0, calls => 0 }) if $t;
print_meta("history cleared.");
return;
}
if ($cmd eq 'metrics') {
my $m = $app->raider->metrics;
print_meta(sprintf(
"raids=%d iterations=%d tool_calls=%d time_ms=%d",
$m->{raids}, $m->{iterations}, $m->{tool_calls}, $m->{time_ms},
));
return;
}
if ($cmd eq 'stats') {
my $s = $app->token_stats;
unless ($s) {
print_meta("stats unavailable (trace is off â start without --no-trace).");
return;
}
print_meta(sprintf(
"/done" to return to the main agent (the main agent will auto-reload the
new persona).
- If the user asks you to cancel, do not write anything; just confirm.
- Do NOT call bash or any tool other than read_file / write_file / edit_file
during this session.
EOM
my $builder_raider = Langertha::Raider->new(
engine => $app->_engine,
mission => $meta_mission,
max_iterations => 20,
);
print_meta("entering prompt-builder. /done to return, /cancel to discard.");
my $ps = 'raider:prompt> ';
while (1) {
print $ps;
my $line = <STDIN>;
last unless defined $line;
chomp $line;
lib/App/Raider.pm view on Meta::CPAN
Tools (MCP):
- list_files(path)
- read_file(path)
- write_file(path, content)
- edit_file(path, old_string, new_string)
- bash(command, [working_directory], [timeout])
- web_search(query, [limit])
- web_fetch(url, [as_html])
How you work:
- User turn = task. Pursue with tools until done. Unlimited iterations.
- Read before write. No guessing file contents.
- After write_file / edit_file: verify. Re-read, or run check (perl -c,
tests, etc.).
- Small targeted edits > full rewrites.
- bash is full shell, not sandbox. Use freely.
- Skip irreversible ops (rm -rf, git reset --hard, force pushes) unless
user explicit ask.
Communication (Caveman mode, default):
- Terse. Drop articles (a/an/the), filler (just/really/basically/
lib/App/Raider.pm view on Meta::CPAN
);
has allowed_commands => (
is => 'ro',
isa => 'ArrayRef[Str]',
predicate => 'has_allowed_commands',
);
has max_iterations => (
is => 'ro',
isa => 'Int',
default => 10_000,
);
has trace => (
is => 'ro',
isa => 'Bool',
default => sub { -t STDOUT ? 1 : 0 },
lib/App/Raider.pm view on Meta::CPAN
my @plugins;
if ($self->trace) {
# Pass as name(s) so PluginHost injects `host` (required by
# Langertha::Plugin). The flat-list form is [Name, {args}, Name, ...].
push @plugins, '+App::Raider::Plugin::Trace';
}
push @plugins, '+App::Raider::Plugin::Situation';
return Langertha::Raider->new(
engine => $self->_engine,
mission => $self->mission,
max_iterations => $self->max_iterations,
max_context_tokens => $self->max_context_tokens,
context_compress_threshold => $self->context_compress_threshold,
(@plugins ? (plugins => \@plugins) : ()),
);
}
async sub raid_f {
my ($self, @messages) = @_;
for my $mcp (@{$self->_mcps}) {
lib/App/Raider.pm view on Meta::CPAN
Working directory for tool operations. Defaults to the current process cwd.
File tools are chrooted to this directory; bash commands inherit it as their
default working directory.
=head2 allowed_commands
Optional arrayref restricting which bash commands may run (first word match).
When undef, any command is allowed.
=head2 max_iterations
Maximum tool-calling iterations per raid. Defaults to 10_000 â effectively
unlimited, so a raid only ends when the model itself stops emitting tool
calls. The conversation history is preserved between raids, so the next user
message in the REPL simply continues the same thread.
Set this to a smaller number if you want a hard safety cap.
=head2 trace
Emit live ANSI-colored progress output (iteration markers, tool calls, tool
results) via L<App::Raider::Plugin::Trace>. Defaults to on when STDOUT is a
lib/App/Raider/Skill.pm view on Meta::CPAN
temperature: 0.7
response_size: 8192
```
CLI `-o key=value` overrides the file.
## Context window and rate limits
- `max_context_tokens = 40000`
- `context_compress_threshold = 0.7`
- `max_iterations = 10000`
At 70% of the token budget, `Langertha::Raider` compresses the history
automatically. Each raid prints `history N msgs, X/Y tok (Z%)` so you can
see how close you are.
## Environment variables for API keys
`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `DEEPSEEK_API_KEY`, `GROQ_API_KEY`,
`MISTRAL_API_KEY`, `GEMINI_API_KEY`, `MINIMAX_API_KEY`, `CEREBRAS_API_KEY`,
`OPENROUTER_API_KEY`.
( run in 1.526 second using v1.01-cache-2.11-cpan-96521ef73a4 )