NAME

Langertha::Raider::Tasks - Internal registry of raider's background work and its notification queue

VERSION

version 0.504

SYNOPSIS

my $tasks = $app->tasks;

my $id = $tasks->next_id('agent');                    # 'agent_1'
$tasks->add( id => $id, kind => 'agent', recipient => 'main',
  description => 'Find the tests', background => 1,
  stop => sub { $subagent->cancel }, future => $f );

$tasks->finish( $id, 'completed', report => $text );
$tasks->notify( 'main', $tasks->notification_text(
  task_id => $id, tool_use_id => 'c3', status => 'completed',
  summary => 'Agent "Find the tests" finished', result => $text,
  usage   => [ subagent_tokens => 1200, tool_uses => 4, duration_ms => 5300 ],
), id => $id, label => 'background agent '.$id.' finished' );

my @messages = $tasks->take_messages('main');         # due for the main run
my $f = $tasks->wait_f('main');                       # done once one is due

DESCRIPTION

Internal module. Its interface may change without notice.

The application's registry of background work and its notification queue (ADR 0027 §1, §4); one per application ("tasks" in Langertha::Raider::Application), so per REPL session.

  • Entries. Every piece of work the model can address by an ID lives here: every subagent run (agent_N, foreground or background, Langertha::Raider::Subagent) and the background shells (bash_N, Langertha::Raider::BashTools, which also reaches the other entries for bash_output and kill_shell). Nothing waits for a shell: whoever waits for its children waits for those of kind agent. An entry is a hash reference with at least id, kind (agent, bash, ...) and status (running, completed, failed, killed); everything else is the kind's own (an agent's description, name, tool_use_id, recipient, report, usage, subagent; an isolated agent's worktree and branch, set while it runs and after it while its worktree is kept, ADR 0032). Two keys mean the same for every kind: stop, a code reference that asks the work to end ("stop"), and future, the future of the run, which the registry holds until it is ready (so nobody else has to). Entries stay after their run ends, so they can be found again ("find").

  • IDs. "next_id" counts per kind, agent_1, agent_2, ... -- predictable, as scenarios name them. An entry added under an explicit ID moves its kind's counter past it.

  • The queue. Messages wait here per recipient, a string: main for the main conversation of the application, the agent ID of a subagent for that subagent. A message is either a notification ("notify", the <task-notification> of a piece of work that ended) or a plain message ("post", what send_message hands a running agent). The recipient's run takes them ("take_messages") before its next model request, when it waits for its children, or -- for main -- when the REPL starts a turn; "wait_f" wakes whoever waits for one.

loop

The IO::Async::Loop the waiters of "wait_f" are woken on: with the next turn of the loop after a message is queued, so whoever waits goes on outside the code that queued it. Without one they are woken at once.

next_id

my $id = $tasks->next_id('agent');                              # 'agent_1'
my $id = $tasks->next_id('agent', taken => sub { $seen{$_[0]} });

The next free ID of the kind: KIND_N with the kind's counter, skipping IDs the registry has and those taken says are taken (the agent IDs of earlier child sessions of a resumed session).

add

my $entry = $tasks->add( id => 'agent_1', kind => 'agent', ... );

Registers an entry (status running unless given) and returns it. Croaks when the ID is in use. An ID of the form KIND_N moves the kind's counter to at least N.

set

$tasks->set( 'agent_1', status => 'running', future => $f, recipient => 'main' );

Changes fields of an entry (a resumed agent runs again). A new future is held as with "add". Returns the entry, or nothing for an unknown ID.

get

my $entry = $tasks->get('agent_1');

The entry with that ID, or nothing.

find

my $entry = $tasks->find('reviewer');   # an ID, or a name given at spawn

The entry with that ID, else the newest one whose name is that string; nothing when there is neither.

entries

my @agents = $tasks->entries( kind => 'agent', recipient => 'main' );

The entries, oldest first, whose fields equal the given ones (a boolean field such as background compares as true or false).

running

my @children = $tasks->running( recipient => 'agent_2', background => 1 );

"entries" with status running.

finish

$tasks->finish( 'agent_1', 'completed', report => $text, usage => \@usage );

Sets the entry's status (completed, failed, killed) and the given fields. Returns the entry.

stop

$tasks->stop('agent_1');
$tasks->stop( 'agent_1', quiet => 1 );

Asks a running entry to end, through its stop. True when there was one to ask. With quiet the entry is marked quiet: its end notifies nobody (a conversation that was cleared, ADR 0027).

stop_all

my @futures = $tasks->stop_all;

"stop" for every running entry; returns the futures of their runs, which are ready once each has ended.

post

$tasks->post( 'agent_2', 'Also check the tests.' );

Queues a plain message for the recipient and wakes its waiters.

notify

$tasks->notify( 'main', $text, id => 'agent_1', label => 'background agent agent_1 finished' );

Queues a notification (a "notification_text") for the recipient and wakes its waiters. id names the entry it is about, label is the line a surface shows for it.

pending

my $n = $tasks->pending('main');

How many messages wait for the recipient.

peek

The waiting items of the recipient (hash references: text, notification, id, label), without taking them.

take

my @items = $tasks->take('main');

Takes the waiting items of the recipient, oldest first.

take_messages

my @messages = $tasks->take_messages('main');
# ( { content => "<task-notification>...", notification => 1, items => [...] }, ... )

Takes the waiting items as the user messages they make: notifications that are due together form one message (joined by a newline), in the order they finished; a plain message is a message of its own.

discard

Drops what waits for the recipient and wakes its waiters; for a run that ended and will never read it.

wait_f

await $tasks->wait_f('agent_2');

A future that is done once something waits for the recipient -- at once when something does already. Cancelling it is fine.

notification_text

my $text = $tasks->notification_text(
  task_id     => 'agent_1',
  tool_use_id => 'c3',
  status      => 'completed',             # failed, killed
  summary     => 'Agent "Find it" finished',
  event       => $lines,                  # optional: what a monitor printed (ADR 0031)
  result      => $report,                 # optional
  usage       => [ subagent_tokens => 1200, tool_uses => 4, duration_ms => 5300 ],   # optional
  worktree    => { path => $p, branch => $b },                                     # optional
);

The <task-notification> of ADR 0027 §4, in Claude Code's form without output-file and note: the elements in this order, each present only when given; usage as its pairs in the given order. The text of every element has &, < and > escaped ("escape").

escape

$tasks->escape('a < b & c');   # 'a &lt; b &amp; c'

The text with &, < and > escaped.

reminder_text

my $block = $tasks->reminder_text($notification);

A notification as Claude Code delivers it next to a genuine user message in the same turn (ADR 0027 §4): a <system-reminder> block that says it is no user input, then the notification.

SEE ALSO

SUPPORT

Issues

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

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

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

AUTHOR

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

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.

https://raudssus.de/