Langertha
view release on metacpan or search on metacpan
ex/raider_run.pl view on Meta::CPAN
my $haiku = Langertha::Engine::Anthropic->new(
api_key => $api_key,
model => 'claude-haiku-4-5-20251001',
);
info("default (Sonnet) " . colored('90', $sonnet->chat_model) . " - main engine, active at start");
info("fast (Haiku) " . colored('90', $haiku->chat_model) . " - in engine_catalog");
info("smart (Sonnet) " . colored('90', $sonnet->chat_model) . " - in engine_catalog");
info("");
info("Engines have NO mcp_servers â all tools come from the MCP catalog.");
info("The LLM can switch engines via raider_switch_engine.");
# ââ 5. Background "build" â creates report file after 4 seconds ââ
banner("SETUP: Background Build");
unlink $report_file;
$loop->add(IO::Async::Timer::Countdown->new(
delay => 4,
on_expire => sub {
open my $fh, '>', $report_file or return;
print $fh "Build OK: 42 modules compiled, 0 errors\nTimestamp: " . localtime() . "\n";
close $fh;
printf "\n %s\n", colored('32', "[BACKGROUND] Build report written to $report_file");
},
)->start);
info("Timer started: $report_file will appear in 4 seconds.");
info("The LLM will use raider_wait + raider_wait_for to check for it.");
# ââ 6. Self-tools overview ââ
banner("SETUP: Raider Self-Tools");
info("raider_switch_engine - switch between engine catalog entries");
info("raider_manage_mcps - list/activate/deactivate MCP catalog entries");
info("raider_wait - wait N seconds");
info("raider_wait_for - wait for external condition (file_exists)");
info("raider_ask_user - ask user a question (simulated in demo)");
info("raider_session_history- query the full session history");
info("raider_pause - pause execution (available but not used)");
info("raider_abort - abort the raid (available but not used)");
# ââ 7. Simulated user answers ââ
my @simulated_answers = (
"Yes, run the tests with prove -l t/ and tell me how it went.",
"Wrap it up.",
);
my $answer_idx = 0;
# ââ 8. State tracking for change detection ââ
my $prev_engine = 'default';
my %prev_active_mcps;
# ââ 9. Raider ââ
my $raider = Langertha::Raider->new(
engine => $sonnet,
max_iterations => 25,
engine_catalog => {
fast => { engine => $haiku, description => 'Fast model for quick tasks' },
smart => { engine => $sonnet, description => 'Smart model for analysis' },
},
mcp_catalog => {
shell => { server => $shell_mcp, description => 'Shell command execution (run tool)' },
notes => { server => $notes_mcp, description => 'Notebook for saving findings' },
},
raider_mcp => 1,
mission => join("\n",
'You are a build engineer. You start with NO tools except self-tools.',
'You must activate MCP servers from the catalog to get tools.',
'Follow these phases IN ORDER. Do not skip phases.',
'',
'PHASE 1 â Bootstrap:',
' You have no shell yet! First get your tools:',
' Use raider_manage_mcps action "list" to see available MCP servers.',
' Use raider_manage_mcps action "activate" name "shell" to get the run tool.',
' Use raider_manage_mcps action "activate" name "notes" to get save_note/read_notes.',
'',
'PHASE 2 â Recon:',
' Now you have a shell. Run: uname -a',
' Run: ls',
' Run: wc -l lib/Langertha.pm',
'',
'PHASE 3 â Wait for build:',
' A background build is already running.',
' Use raider_wait for 3 seconds, reason "waiting for background build".',
' Use raider_wait_for condition "file_exists" args {"path":"/tmp/raider_build_report.txt"}.',
' Run: cat /tmp/raider_build_report.txt',
'',
'PHASE 4 â Switch engines and take notes:',
' Use raider_switch_engine name "fast" to switch to the fast model.',
' Use save_note to record the build result.',
' Use raider_switch_engine name "smart" to switch back.',
'',
'PHASE 5 â Ask user:',
' Use raider_ask_user question "Build succeeded. Should I run the tests too?"',
' Do what the user says.',
'',
'PHASE 6 â Review and report:',
' Use raider_session_history with last_n 5 to review recent activity.',
' Use read_notes to review your notebook.',
' Give a concise final status report.',
),
on_ask_user => sub {
my ($question, $options) = @_;
sep();
printf " %s\n", colored('35', '[SELF-TOOL] raider_ask_user');
info(" Q: $question");
if ($options && @$options) {
info(" Options: " . join(', ', @$options));
}
my $answer = $simulated_answers[$answer_idx] // "Wrap it up.";
$answer_idx++;
printf " %s %s\n", colored('32', ' >>> Demo user:'), $answer;
sep();
return $answer;
},
on_wait_for => sub {
ex/raider_run.pl view on Meta::CPAN
$prev_engine = $cur_engine;
}
# Detect MCP catalog changes
my %cur_active;
for my $name (keys %{$raider->mcp_catalog}) {
$cur_active{$name} = 1 if exists $raider->_active_catalog_mcps->{$name};
}
for my $name (keys %cur_active) {
unless ($prev_active_mcps{$name}) {
printf " %s %s activated\n",
colored('35', '[MCP CATALOG]'), colored('32', $name);
}
}
for my $name (keys %prev_active_mcps) {
unless ($cur_active{$name}) {
printf " %s %s deactivated\n",
colored('35', '[MCP CATALOG]'), colored('33', $name);
}
}
%prev_active_mcps = %cur_active;
# Show iteration header with full state
printf "\n %s\n", colored('1', "=== ITERATION $iteration ===");
show_state($raider);
sleep 0.8; # rate limit protection
return;
},
);
# ââ 10. Raid ââ
banner("RAID: Full-Stack Build Engineer");
info("Initial state:");
show_state($raider);
my $result = await $raider->raid_f(
'Start the build engineer workflow. Follow all phases in your mission.'
);
# ââ 11. Results ââ
printf "\n";
banner("FINAL ANSWER");
if ($result->is_final) {
printf "%s\n", $result;
} elsif ($result->is_question) {
info("[RESULT] question: ${\$result->content}");
} elsif ($result->is_pause) {
info("[RESULT] pause: ${\$result->content}");
} elsif ($result->is_abort) {
info("[RESULT] abort: ${\$result->content}");
}
# ââ 12. Metrics ââ
banner("METRICS");
my $m = $raider->metrics;
info(sprintf "Raids: %d", $m->{raids});
info(sprintf "Iterations: %d", $m->{iterations});
info(sprintf "Tool calls: %d", $m->{tool_calls});
info(sprintf "Total time: %.1f s", $m->{time_ms} / 1000);
# ââ 13. Notebook ââ
if (@notebook) {
banner("NOTEBOOK (saved by LLM via notes MCP)");
for my $note (@notebook) {
info("[$note->{title}] $note->{text}");
}
}
# ââ 14. Session history ââ
banner("SESSION HISTORY");
my @hist = @{$raider->session_history};
for my $i (0 .. $#hist) {
my $msg = $hist[$i];
my $role = $msg->{role} // '?';
my $content = $msg->{content} // '';
if (ref $content eq 'ARRAY') {
$content = join(' ', map {
$_->{text} // "[" . ($_->{type} // '?') . "]"
} @$content);
}
$content =~ s/\n/ /g;
$content = substr($content, 0, 120) . '...' if length($content) > 120;
printf " %s %-10s %s\n", colored('90', sprintf('%02d', $i + 1)), $role, $content;
}
info("");
info(sprintf "Session: %d messages | History: %d messages",
scalar @hist, scalar @{$raider->history});
# Cleanup
unlink $report_file;
}
main()->get;
( run in 1.180 second using v1.01-cache-2.11-cpan-71847e10f99 )