NAME

Langertha::Raider - Autonomous agent with conversation history and MCP tools

VERSION

version 0.202

SYNOPSIS

use IO::Async::Loop;
use Future::AsyncAwait;
use Net::Async::MCP;
use MCP::Server;
use Langertha::Engine::Anthropic;
use Langertha::Raider;

# Set up MCP server with tools
my $server = MCP::Server->new(name => 'demo', version => '1.0');
$server->tool(
    name => 'list_files',
    description => 'List files in a directory',
    input_schema => {
        type => 'object',
        properties => { path => { type => 'string' } },
        required => ['path'],
    },
    code => sub { $_[0]->text_result(join("\n", glob("$_[1]->{path}/*"))) },
);

my $loop = IO::Async::Loop->new;
my $mcp = Net::Async::MCP->new(server => $server);
$loop->add($mcp);

async sub main {
    await $mcp->initialize;

    my $engine = Langertha::Engine::Anthropic->new(
        api_key     => $ENV{ANTHROPIC_API_KEY},
        mcp_servers => [$mcp],
    );

    my $raider = Langertha::Raider->new(
        engine  => $engine,
        mission => 'You are a code explorer. Investigate files thoroughly.',
    );

    # First raid — uses tools, builds history
    my $r1 = await $raider->raid_f('What files are in the current directory?');
    say $r1;

    # Second raid — has context from first conversation
    my $r2 = await $raider->raid_f('Tell me more about the first file you found.');
    say $r2;

    # Check metrics
    my $m = $raider->metrics;
    say "Raids: $m->{raids}, Tool calls: $m->{tool_calls}, Time: $m->{time_ms}ms";

    # Reset for a fresh conversation
    $raider->clear_history;
}

main()->get;

DESCRIPTION

Langertha::Raider is an autonomous agent that wraps a Langertha engine with MCP tools. It maintains conversation history across multiple interactions (raids), enabling multi-turn conversations where the LLM can reference prior context.

Key features:

  • Conversation history persisted across raids

  • Mission (system prompt) separate from engine's system_prompt

  • Automatic MCP tool calling loop

  • Cumulative metrics tracking

  • Hermes tool calling support (inherited from engine)

  • Mid-raid context injection via inject() and on_iteration

History management: Only user messages and final assistant text responses are persisted in history. Intermediate tool-call messages (assistant tool requests and tool results) are NOT persisted, preventing token bloat across long conversations.

engine

Required. A Langertha engine instance with MCP servers configured. The engine must compose Langertha::Role::Tools.

mission

Optional system prompt for the Raider. This is separate from the engine's own system_prompt — the Raider's mission takes precedence and is prepended to every conversation.

history

ArrayRef of message hashes representing the conversation history. Automatically managed by raid/raid_f. Can be inspected or manually set.

max_iterations

Maximum number of tool-calling round trips per raid. Defaults to 10.

max_context_tokens

Optional. Enables auto-compression when set. When prompt token usage exceeds context_compress_threshold * max_context_tokens, the working history is summarized via LLM before the next raid.

context_compress_threshold

Fraction of max_context_tokens that triggers compression. Defaults to 0.75 (75%).

compression_prompt

System prompt used for history summarization. Customizable. The default instructs the LLM to preserve key facts, decisions, and context.

compression_engine

Optional separate engine for compression (e.g. a cheaper model). Falls back to engine when not set.

session_history

Full chronological archive of ALL messages including tool calls and results. Never auto-compressed. Persists across clear_history and reset. Only cleared manually via $raider->session_history([]).

on_iteration

Optional CodeRef called before each LLM call (iterations 2+). Receives ($raider, $iteration) and returns an arrayref of messages to inject, or undef/empty to skip.

my $raider = Langertha::Raider->new(
    engine => $engine,
    on_iteration => sub {
        my ($raider, $iteration) = @_;
        return ['Check the error log'] if $iteration == 3;
        return;
    },
);

metrics

HashRef of cumulative metrics across all raids:

{
    raids      => 3,       # Number of completed raids
    iterations => 7,       # Total LLM round trips
    tool_calls => 12,      # Total tool invocations
    time_ms    => 4500.2,  # Total wall-clock time in milliseconds
}

langfuse_trace_name

Name for the Langfuse trace created per raid. Defaults to 'raid'.

langfuse_user_id

Optional user ID passed to the Langfuse trace.

langfuse_session_id

Optional session ID passed to the Langfuse trace. Use this to group multiple raids into a single Langfuse session.

langfuse_tags

Optional tags (ArrayRef[Str]) passed to the Langfuse trace.

langfuse_release

Optional release identifier passed to the Langfuse trace.

langfuse_version

Optional version string passed to the Langfuse trace.

langfuse_metadata

Optional metadata HashRef merged into the Langfuse trace metadata (alongside auto-generated fields like mission and history_length).

raider_mcp

Enables virtual self-tools that the LLM can call to interact with the Raider itself. Set to 1 to enable all self-tools, or pass a HashRef to enable selectively:

raider_mcp => 1                               # all self-tools
raider_mcp => { ask_user => 1, pause => 1 }   # only these

Available self-tools: ask_user, wait, wait_for, pause, abort, session_history, manage_mcps, switch_engine.

on_ask_user

Optional callback for the raider_ask_user self-tool. Receives ($question, $options) and must return an answer string. When not set, the raid pauses and returns a question Result that can be continued with "respond_f".

on_pause

Optional callback for the raider_pause self-tool. Receives ($reason). When not set, the raid pauses and returns a pause Result.

on_wait_for

Callback for the raider_wait_for self-tool. Receives ($condition, $args) and must return a result string. Required when the LLM uses raider_wait_for — will die if not set.

tools

Optional ArrayRef of inline tool definitions. Each entry is a HashRef with name, description, input_schema, and code keys — the same format as "tool" in MCP::Server. An internal MCP server is created automatically.

my $raider = Langertha::Raider->new(
    engine => $engine,
    tools  => [{
        name         => 'greet',
        description  => 'Say hello',
        input_schema => { type => 'object', properties => { name => { type => 'string' } } },
        code         => sub { $_[0]->text_result("Hello $_[1]->{name}!") },
    }],
);

mcp_catalog

HashRef of named MCP servers available for dynamic activation. The LLM can use raider_manage_mcps to list, activate, and deactivate catalog entries.

mcp_catalog => {
    database => { server => $db_mcp, description => 'Database tools', auto => 1 },
    email    => { server => $email_mcp, description => 'Email tools' },
}

Entries with auto => 1 are activated at construction time.

engine_catalog

HashRef of named engines available for runtime switching via switch_engine.

engine_catalog => {
    fast  => { engine => $groq,      description => 'Fast inference' },
    smart => { engine => $anthropic,  description => 'Complex reasoning' },
    code  => { engine => $deepseek,   description => 'Code generation' },
}

Use switch_engine, reset_engine, active_engine, and engine_info to control which engine is used during raids.

embedding_engine

Optional engine with Langertha::Role::Embedding for semantic history search. When not set, auto-detects if the main engine supports embeddings.

clear_history

$raider->clear_history;

Clears conversation history and pending injections while preserving metrics.

inject

$raider->inject('Also check the test files');
$raider->inject({ role => 'user', content => 'Focus on .pm files' });

Queues messages to be injected into the conversation at the next iteration. Strings are automatically wrapped as user messages. The Raider drains the queue before each LLM call (iterations 2+).

reset

$raider->reset;

Clears conversation history, metrics, and resets to the default engine.

active_engine

my $engine = $raider->active_engine;

Returns the currently active engine. If switch_engine was called, returns the catalog engine; otherwise returns the default engine.

active_engine_name

my $name = $raider->active_engine_name;  # 'smart' or undef

Returns the name of the currently active catalog engine, or undef if using the default engine.

switch_engine

$raider->switch_engine('smart');

Switches to a named engine from the engine_catalog. Sets _tools_dirty so the raid loop re-gathers and re-formats tools for the new engine. Croaks if the name is not in the catalog.

reset_engine

$raider->reset_engine;

Switches back to the default engine (the one passed at construction).

engine_info

my $info = $raider->engine_info;
# { name => 'smart', class => 'Langertha::Engine::Anthropic', model => 'claude-sonnet-4-6' }

Returns a hashref with the active engine's name, class, and model.

list_engines

my $engines = $raider->list_engines;

Returns a hashref of all available engines (default + catalog entries), each with engine, description (if from catalog), and active flag.

add_engine

$raider->add_engine('vision', engine => $vision_engine, description => 'Vision model');

Adds a new engine to the catalog at runtime. The LLM will see it in the raider_switch_engine tool after the next tool re-gather.

remove_engine

$raider->remove_engine('vision');

Removes an engine from the catalog. If the removed engine is currently active, automatically resets to the default engine.

compress_history_f

my $summary = await $raider->compress_history_f;

Async. Summarizes the current working history via LLM and replaces it with the summary. Uses compression_engine if set, otherwise falls back to engine. A marker is added to session_history.

compress_history

my $summary = $raider->compress_history;

Synchronous wrapper around compress_history_f.

register_session_history_tool

$raider->register_session_history_tool($mcp_server);

Registers a session_history MCP tool on the given server, allowing the LLM to query its own full session history. Supports query (text filter) and last_n (return last N messages) parameters.

raid

my $response = $raider->raid(@messages);

Synchronous wrapper around raid_f. Sends messages, runs the tool loop, and returns the final text response. Updates history and metrics.

raid_f

my $result = await $raider->raid_f(@messages);

Async tool-calling conversation. Accepts the same message arguments as simple_chat (strings become user messages, hashrefs pass through). Returns a Future resolving to a Langertha::Raider::Result.

The result stringifies to the final text (backward compatible), but also provides type, is_final, is_question, is_pause, is_abort for programmatic handling of interactive self-tools.

respond_f

my $result = await $raider->respond_f($answer);

Continue a paused raid after a question or pause result. The answer is used as the tool result and the raid loop resumes. Returns the next Langertha::Raider::Result.

respond

my $result = $raider->respond($answer);

Synchronous wrapper around respond_f.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudss.us/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.