NAME
Langertha::Raider::Subagent - Internal subagent run of raider: one agent call, its own raider, tools and journal
VERSION
version 0.504
SYNOPSIS
# The agent tools of a run at depth 0 (what the application mounts):
my $agent_tools = Langertha::Raider::Subagent->agent_tools(
app => $app,
depth => 0,
caller => sub { my ( %ctx ) = @_; { # %ctx: the agent call's context
servers => $app->_tool_servers, # the caller's active tool set
session => $session, # or undef
call => $ctx{call}, # the agent call's id (c3)
event => sub { $app->event(@_) }, # the caller's event stream
policy => $app->permissions, # the caller's permission policy
task_id => undef, # the calling subagent's agent ID
detached => 0, # the caller runs in the background
root => $app->root, # the caller's working root
} },
);
my $server = $agent_tools->server; # agent, send_message
my $raider = Langertha::Raider->new( ..., async_tools => $agent_tools->async_tools );
# What one agent call does:
my $sub = Langertha::Raider::Subagent->new(
app => $app, agent => $agent, depth => 1, prompt => 'Find ...',
caller_servers => \@servers, on_caller_event => sub { ... },
caller_policy => $app->permissions,
);
my ( $text, $is_error ) = await $sub->foreground_f; # foreground, on the loop
my ( $text, $is_error ) = $sub->start_background; # 'Async agent launched successfully. ...'
DESCRIPTION
Internal module. Its interface may change without notice.
The subagent run of ADR 0020 §4 and ADR 0027: one call of the agent tool (Langertha::Raider::AgentTools) becomes a new Langertha::Raider in the same process, on the loop of the "app" (ADR 0016). In the foreground the call's result is the subagent's last assistant text with its agent ID and usage ("result_text"); in the background (run_in_background) the call answers at once ("launch_text") and the end of the run is a <task-notification> for the caller ("notification").
Agent ID and registry (ADR 0027 §1). Every run that starts gets an agent ID,
agent_N("task_id"), and an entry in the application's Langertha::Raider::Tasks -- status, description, name, theagentcall id, its caller (the recipient of its notifications:main, or the agent ID of the calling subagent), the report and usage of its last run, and the subagent itself. The entry stays after the run, sosend_messagecan continue the agent ("send_message"). The ID skips the agent IDs of the earlier child sessions of the caller's session (a resumed session), so it never names two agents there.Own context. The raider starts with an empty history and gets only the prompt. Its mission is compiled by the application like the caller's ("compile_mission" in Langertha::Raider::Application), with the agent's body in the place of the persona and the native instructions -- what
-Mreplaces (ADR 0014). An agent without memory (memory0: the built-insExploreandPlan, ADR 0027 §6) gets no Claude memory, no AGENTS.md and no packs, only its body, the skill listing when its set hasskill, and its tools.Own tool set ("tool_servers"). The caller's active set, each tool kept when the agent's
toolsallows it anddisallowedToolsdoes not. A name there stands for its family (ADR 0025): the task-list tools (todo_write,task_create,task_update,task_list,task_get) are one, so areedit_fileandmulti_edit, andbashbringsbash_outputandkill_shell.ask_useralways goes (nobody can answer a subagent), and so doesenter_plan_mode(only the main session enters plan mode). The task-list tools get an instance of their own, so the subagent's list never overwrites the caller's; the file tools get one over the subagent's own "file_read_state", so it reads a file before it writes it, as the caller must (ADR 0023); background shells stay the application's.agentandsend_messageget instances for this depth -- or go, when this run is at "max_agent_depth" in Langertha::Raider::Application.send_messagestays exactly whenagentdoes (and is not disallowed itself). A server whose tools all stay is mounted as it is; otherwise a new MCP::Server carries the tools that stay. Prompt and catalogue derive from this set alone; the skill listing stays only when the set has theskilltool.Briefing ("briefing", ADR 0020 §6). The skills the agent declares are resolved through "skill_resolver" in Langertha::Raider::Context and put in front of the prompt in the briefing plugin's format (Langertha::Raider::Briefing); that is the subagent's prompt ("briefed_prompt"). A skill that does not resolve refuses the call: the result is the error
briefing: agent 'NAME' references unknown skill(s): a, b, and nothing starts -- no agent ID, no session, noagent.started, no request. Above 64 kB of skill text the run goes ahead, and the caller's stream and journal getagent.warning(call,agent,message: the size note) beforeagent.started.Permissions ("permissions", ADR 0021). The caller's policy through "for_subrun" in Langertha::Raider::Permissions: the mode the caller has when the call starts, the same rules and additional directories, and the same session rules. Every tool call of the subagent passes the application's gate ("tool_gate" in Langertha::Raider::Application) with this policy; a call it asks about goes to the same person ("on_permission_request" in Langertha::Raider::Application) or, with nobody to ask, is refused as in the caller. A "detached" subagent -- one in the background, or started by one -- never asks: such a call is refused with a reason that says it runs in the background (ADR 0027 §3). Its
permission.*events go to the subagent's journal.exit_plan_modegets an instance of its own that never switches a mode: a subagent in plan mode gives its plan as its answer, to the caller.Engine. One engine of the caller's class and arguments, built by "build_subagent_engine" in Langertha::Raider::Application over this set's clients, on the same loop. Its model is the call's
model("model"), else the agent'smodel, as "resolve_model" in Langertha::Raider::Context::Agents maps it under the caller's engine and "agent_models" in Langertha::Raider::Application ("model_resolution", ADR 0020 §5): a resolved model replaces the caller's, otherwise it is inherited.agent.startedcarries the model the engine really uses.Journal. When the caller has a session, the subagent gets its own session in the same store, whose
session.createdcarriesparent,parent_call,agent,depth,agent_idand thenamegiven at spawn, and runs one journaled run there per run of the agent --run.started, the briefed prompt (or the message it was continued with) and the answer asmessage, everytool.call,tool.resultandtodo.updated, a delivered notification as a usermessagewithnotification1, andrun.finished. The caller gets onlyagent.started(call,agent,agent_id,background,description,session,model;resumedfor a continued run) andagent.finished(call,agent,agent_id,session,status), through its event stream -- for a subagent of the main run that ended between two runs, straight into the session it was started from ("caller_event" in Langertha::Raider::Application).Hooks (Claude layout, ADR 0022, ADR 0020 §6). Before the first run
SubagentStartfires (matcher onagent_type) withagent_id-- the subagent's session id, orlocal-agent-PID-ADDRwithout a session -- andagent_type, the agent's name; itsadditionalContextgoes in front of the briefed prompt as a<system-reminder>block ("sent_prompt"), and acontinue: falsefails the run before its first request. After a final answerSubagentStopfires asStopdoes for the main run ("stop_hook_limit" in Langertha::Raider::Application): a block oradditionalContextsendsSubagentStop hook feedback:and the reasons back to the subagent as its next message, withstop_hook_activetrue, and the run goes on. The reports of both go to the caller's stream (hook.ran,hook.message). The subagent's own tool calls run thePreToolUseandPostToolUsehooks as the caller's do, withagent_id,agent_typeand its ownpermission_modein the input; those reports go to its journal, and acontinue: falsethere cancels the subagent only. Its own task list firesTaskCreatedandTaskCompletedthe same way, before the change, with the same fields; a block refuses the change with the hook's reason as the tool's error result. A continued run fires noSubagentStart.Worktree isolation (ADR 0032 §2, ADR 0027 §7). The call's
isolation: "worktree"or the agent file'sisolation: worktree("isolated") runs the subagent in a git worktree of its own (Langertha::Raider::Worktree):TOP/.claude/worktrees/agent-Nin the Claude layout,TOP/.raider/worktrees/agent-Notherwise ("worktrees_dir" in Langertha::Raider::Application), TOP the main checkout of the caller's root, on branchworktree-agent-N, from the base of "worktree_base_ref" in Langertha::Raider::Application, with the .worktreeinclude copy. An agent ID whose directory or branch is left from earlier is skipped. A caller root in a subdirectory works in the same subdirectory of the worktree ("working_root"). Not in a git repository:isolation "worktree" needs a git repository; ROOT is not inside one., and nothing starts; an agent file's otherisolationvalue refuses the spawn too ("isolation_refusal"). The file, search and shell tools work in the working root (the same classes and settings, the root replaced), and so do the Perl tools when the caller has them; every hook input of the subagent has the working root ascwd; the policy is the caller's moved there ("permissions"), which refuses edits into the main checkout; the mission is compiled for the caller's context with the working root as its working directory. The worktree is locked (raider agent agent_N) while a run runs. After every run, however it ended, it is unlocked; with no changes (a clean status, untracked files counting, and no commit beyond the base) worktree and branch are removed, else both stay and are named in the result ("result_text"), the notification, the registry entry (worktree,branch) andagent.finished(worktree_kept;agent.startedhasworktreeandbranch). A cancelled run nobody hears of -- a new conversation, leaving raider, a cancelled turn -- names it in a meta line instead ("note_meta" in Langertha::Raider::Application:agent agent_1 left its worktree P on branch B). A git command that fails keeps the worktree (worktree kept: REASON), never failing the answer. A continued run uses the worktree again, checks its branch out into the same path when the worktree is gone, or makes it anew when both are.Messages and children. What waits for the subagent in the registry's queue -- the notifications of its own background subagents, the messages
send_messagehands it -- goes into its raid before its next model request ("on_iteration" in Langertha::Raider). A run that would end with a final answer while background subagents it started still run waits for them on the loop and goes on with their notifications as its next message (ADR 0027 §3); it ends when it answers with nothing left.On the loop (ADR 0027 §9).
agentandsend_messageare asynchronous tools of the caller's raider ("async_tools" in Langertha::Raider): no tool code waits for a subagent, a foreground run is a Future the caller's raid awaits, and theagentcalls of one model turn run at the same time, each with its own agent ID and result. At most "max_concurrent_agents" in Langertha::Raider::Application subagent runs of the application run at once -- all depths, foreground and background, resumed ones too; beyond that a spawn or a resume is refused withConcurrent subagent limit reached (N running). Do not retry this call now; wait for running agents to finish., and nothing starts. The agent call id a subagent knows (itscaller_call, thetool-use-idof its notification) comes from the call's context, not from the last call of the caller's run.End. A completed run answers with the subagent's text; a failed one with an error result giving the reason; a cancelled one -- the application cancels every live foreground subagent with its run ("cancel_run" in Langertha::Raider::Application), and "cancel" reaches the subagent's own children -- with an error result. A caller's cancel that cuts the
agentcall off cancels the subagent too; its run ends on the loop, held by the registry. Failed and cancelled results name the agent ID. A run that ends that way first stops the subagents it started and waits for them. The subagent gets the caller'smax_iterations.
app
The Langertha::Raider::Application of the main run, which builds the engine and the mission and owns the loop, the session store and the registry. Required, held weakly.
agent
The catalogue entry of the agent (Langertha::Raider::Context::Agents). Required.
depth
The depth of this run: 1 for a subagent of the main run. Required.
prompt
The task, the subagent's first user message. Required.
description
The short label of the call, for agent.started and the notification.
name
The name the call gave the agent, by which send_message can address it, if any.
model
The model of the call (sonnet, opus, haiku), which replaces the agent's own, if any.
background
True when the run goes on in the background (run_in_background, and every continued run).
isolation
The isolation of the call (worktree), if any. The agent file's isolation holds as well ("isolated").
caller_root
The caller's working root: where its file, search and shell tools work. Defaults to the application's "working_root" in Langertha::Raider::Application -- a worktree while the main run works in one (ADR 0032 §3); a subagent of an isolated one gets that one's worktree ("working_root").
worktree
The worktree of an "isolated" subagent once it is planned (ADR 0032 §2): top (the main checkout), source (the working tree of the caller's root, where .worktreeinclude is read), path, branch, root ("working_root") and, once this process made it, base (the commit it branched from). Undef otherwise.
isolated
True when the subagent runs in a worktree of its own: the call's "isolation" or the agent file's isolation is worktree (ADR 0032 §2; the call cannot turn the file's off).
isolation_refusal
my $text = $sub->isolation_refusal; # or nothing
The error result when the agent file's isolation is not worktree: Agent 'NAME': isolation "V" is not supported; use "worktree".
working_root
Where the subagent's file, search and shell tools work and what its mission calls the working directory: the worktree's root (the same subdirectory of the worktree as the caller's root is of its working tree) for an "isolated" subagent, else "caller_root".
worktree_name
my $name = $sub->worktree_name('agent_3'); # agent-3
The directory name of an agent's worktree under "worktrees_dir" in Langertha::Raider::Application; its branch is worktree-NAME.
worktree_class
The class doing the git work: Langertha::Raider::Worktree.
task_id
The agent ID (agent_1), once the run is registered: what the model sees and addresses (ADR 0027 §1). Not the same as "agent_id", the id the hooks get.
caller_servers
The caller's active tool set, as MCP::Server objects. Required.
caller_session
The caller's Langertha::Raider::Session, if it has one.
caller_call
The id of the caller's agent tool call (c3), as its journal names it -- for a continued run, the id of the send_message call.
on_caller_event
The caller's event stream, called as ->($type, %payload) with agent.started and agent.finished. Required.
caller_task_id
The agent ID of the calling subagent; undef for a subagent of the main run. See "recipient".
caller_detached
True when the caller runs detached ("detached").
caller_policy
The caller's Langertha::Raider::Permissions: the application's "permissions" in Langertha::Raider::Application for a subagent of the main run, the calling subagent's "permissions" for a nested one. Defaults to the application's.
permissions
The subagent's policy: "for_subrun" in Langertha::Raider::Permissions of "caller_policy", built once -- for an "isolated" one with the "working_root" as root and the worktree as "worktree_isolation" in Langertha::Raider::Permissions, so project rules mean the same files in the copy and edits into the main checkout are refused.
briefing
The Langertha::Raider::Briefing of the agent's declared skills, built once ("briefing_class").
briefed_prompt
What the subagent is asked: "prompt" behind its briefing, or "prompt" itself when the agent declares no skill.
sent_prompt
What the subagent was really sent once the run has started: the additionalContext of SubagentStart hooks in front of "briefed_prompt", or "briefed_prompt" itself.
agent_id
The agent_id of the hook inputs (ADR 0020 §7): the subagent's session id, else local-agent-PID-ADDR, unique while the subagent runs. The model addresses the agent by "task_id".
model_resolution
What the call's "model", else the agent's model, means under the caller's engine ("resolve_model" in Langertha::Raider::Context::Agents): { model => ... } or { inherit => 1, reason => ... }.
tool_servers
The subagent's active tool set (see "DESCRIPTION"), built once.
mission
The subagent's system prompt, built once.
engine
The subagent's engine, built once.
raider
The subagent's Langertha::Raider, built once; it keeps the history from one run of the agent to the next.
session
The subagent's own Langertha::Raider::Session once the run has started, when the caller has one.
cancel_requested
True once "cancel" was called, until the agent is continued.
todos
The subagent's own task list, as its task tools (or todo_write) last left it.
file_read_state
The subagent's own record of the files it has read (Langertha::Raider::FileTools::ReadState, ADR 0023): its write_file, edit_file and multi_edit hold against it, so a file the caller read counts as unread until the subagent reads it itself.
last_outcome
The outcome of the last run that ended (see "run_f").
recipient
Who gets the notifications of this subagent: "caller_task_id", or main for a subagent of the main run.
detached
True when nobody waits for the subagent at the keyboard: it runs in the "background", or its caller does ("caller_detached").
one_shot
True for an agent that cannot be continued (the built-ins Explore and Plan, ADR 0027 §6).
agent_tools
my $agent_tools = Langertha::Raider::Subagent->agent_tools(
app => $app, depth => $depth,
caller => sub { my ( %ctx ) = @_; { servers, session, call, event, policy, task_id, detached, root } });
The Langertha::Raider::AgentTools with the tools agent and send_message for a run at $depth, over the agent catalogue of the application's context: its server for the tool set, its async_tools for the raider of that run. Every call asks caller -- with the call's context ("async_tools" in Langertha::Raider) -- for the state of the calling run at that moment: its active tool set, session, the id of the call (the context's call), its event stream, its permission policy, its agent ID (undef for the main run), whether it runs detached and its working root ("caller_root"; the application's root when not given). An agent call runs a new subagent of the invocant class at $depth + 1 ("spawn_f"), a send_message call goes to "send_message".
agent_tool_server
my $server = Langertha::Raider::Subagent->agent_tool_server(%args);
The server of "agent_tools".
spawn_f
my ( $text, $is_error ) = await Langertha::Raider::Subagent->spawn_f(
app => $app, depth => 0, caller => \%caller, agent => $agent,
call => { description, prompt, model, name, run_in_background },
);
One agent call: a new subagent at $depth + 1; an agent file's isolation other than worktree refuses it ("isolation_refusal"), so do a briefing that does not resolve and the concurrency limit ("limit_refusal"); else it runs in the foreground ("foreground_f") or starts in the background ("start_background"), which make the worktree of an "isolated" one first. A Future of the tool result.
limit_refusal
my $text = Langertha::Raider::Subagent->limit_refusal($app);
The error text when "max_concurrent_agents" in Langertha::Raider::Application subagent runs of the application already run (ADR 0027 §9) -- every agent entry of the registry that is running, at any depth, in the foreground or the background -- else nothing.
foreground_f
my ( $text, $is_error ) = await $sub->foreground_f;
Makes the worktree of an "isolated" subagent (an error result when that fails, nothing started), registers the subagent and runs it in the foreground ("run_f") on the application's loop; a Future of the tool result ("result_text"), which the caller's raider awaits. While it runs, the application knows it as live, so cancelling the main run cancels it -- unless it is "detached"; then only its caller's "cancel" reaches it. Cancelling the returned Future -- a cancel cut the agent call off -- cancels the subagent; its run still ends in order on the loop (the registry holds it).
run
my ( $text, $is_error ) = $sub->run;
"foreground_f", waited for on the application's loop: the synchronous wrapper for a library caller outside the loop. Never call it from code that runs on the loop -- a tool, a hook --: that is what "foreground_f" is for.
start_background
my ( $text, $is_error ) = $sub->start_background;
Makes the worktree of an "isolated" subagent as "foreground_f" does, registers the subagent, starts its run ("run_f") on the application's loop with the next turn of the loop, and returns at once with "launch_text". The registry holds the run; its end becomes a notification for "recipient" ("notification").
send_message
my ( $text, $is_error ) = Langertha::Raider::Subagent->send_message(
app => $app, depth => 0, caller => \%caller, to => 'agent_1', message => '...');
The send_message tool (ADR 0027 §5). to is an agent ID or a name given at spawn ("find" in Langertha::Raider::Tasks); an ID the registry does not know is looked up among the child sessions of the caller's session ("restore"). A running agent gets the message before its next model request (Message queued for delivery to agent_1 at its next tool round.); a finished one is continued in the background ("resume"), unless the concurrency limit refuses it ("limit_refusal"). Error results: No agent found with ID or name: X, Agent agent_1 (Explore) is one-shot and cannot be continued., and for a background shell bash_1 is a background shell, not an agent; read it with bash_output.
restore
my ( $sub, $error ) = Langertha::Raider::Subagent->restore(
app => $app, depth => 0, caller => \%caller, to => 'agent_1');
An agent of an earlier process: the newest child session of the caller's session whose session.created has agent_id (or name) to. Its agent is looked up by name in the current catalogue; its history is replayed into a new subagent (ADR 0015), which takes the session over and is registered under the agent ID, finished. An agent that ran in a worktree (its agent.started names one) or whose file says isolation: worktree is "isolated" again, with the worktree path and branch its agent ID gives. Returns the subagent, an error text (the agent is not defined any more, is one-shot, its session cannot be opened, or its root is no git repository any more), or nothing when there is no such session.
resume
my ( $text, $is_error ) = $sub->resume( $message, \%caller );
Continues a finished subagent (ADR 0027 §5): its caller becomes the given one, and a new run ("resume_f") of it starts in the background with the next turn of the loop, with its history and $message as the next user message, in a new run of its child session. Returns Resuming agent agent_1 in the background. You will be notified when it finishes.
run_f
my $outcome = await $sub->run_f;
The first run: starts the journal and agent.started, raids with the briefed prompt (and goes on as long as notifications and messages come, see "DESCRIPTION"), ends the journal, the registry entry, agent.finished and -- in the background -- the notification. Never fails; returns the outcome as a hash reference: status (completed, failed, cancelled, or refused when a declared skill does not resolve -- then nothing ran), text of a completed run, error of a failed or refused one, and usage ("usage_pairs") of one that ran.
resume_f
my $outcome = await $sub->resume_f($message);
A further run of a finished subagent with $message as the next user message; otherwise as "run_f".
approval_binding
my %binding = $sub->approval_binding; # session_id, run_id
What an approval of one of the subagent's calls is bound to: its own session and run, when it has a session; else nothing, and the application's stand-ins apply.
cancel
Asks the run to stop ("cancel" in Langertha::Raider), and every subagent it started that still runs ("stop" in Langertha::Raider::Tasks); only sets flags, so a signal handler may call it.
event
$sub->event('tool.call', call => 'c1', name => 'read_file', ...);
One event of the subagent's own run: into its journal for the types the application journals ("is_journal_type" in Langertha::Raider::Application), nowhere else. A tool.call counts as one of its "usage_pairs" tool_uses; a model.usage of its own model calls (purpose chat) sets its subagent_tokens.
usage_pairs
my $pairs = $sub->usage_pairs; # [ subagent_tokens => 1200, tool_uses => 4, duration_ms => 5300 ]
The usage of the current (or last) run, in the order the result and the notification show it (ADR 0027 §2): subagent_tokens -- input plus output tokens of the subagent's last model call that reported usage, from its model.usage event (the one the application's token ledger counts, ADR 0026), 0 before any --, tool_uses -- the tool calls of this run --, duration_ms -- its wall clock.
agent_id_line
agentId: agent_1 (use send_message with to: 'agent_1' to continue this agent)
The line that names the agent ID in a result; for a "one_shot" agent only agentId: agent_1.
result_text
my ( $text, $is_error ) = $sub->result_text($outcome);
The tool result of a foreground run (ADR 0027 §2). Completed: the report, then "agent_id_line" (not for a "one_shot" agent) and
<usage>subagent_tokens: N
tool_uses: N
duration_ms: N</usage>
Refused: the refusal. Cancelled: Agent 'NAME' (agent_1) was cancelled., failed: Agent 'NAME' (agent_1) failed: REASON -- both errors, the agent ID left out for a one-shot agent.
A kept worktree (ADR 0032 §2) adds, after the usage (or the error),
worktreePath: /repo/.claude/worktrees/agent-1
worktreeBranch: worktree-agent-1
and worktree kept: REASON when a git command failed.
launch_text
The answer of a background start (ADR 0027 §3):
Async agent launched successfully.
agentId: agent_1 (use send_message with to: 'agent_1' to continue this agent)
The agent is working in the background. You will be notified automatically when it completes. ...
Do not duplicate this agent's work -- ...
notification
my ( $text, $label ) = $sub->notification($outcome);
The <task-notification> of a run that ended (ADR 0027 §4, "notification_text" in Langertha::Raider::Tasks) and the line a surface shows for it (background agent agent_1 finished). completed: summary Agent "DESCRIPTION" finished and the report as result; failed: summary Agent "DESCRIPTION" failed: REASON; killed (a cancelled run): summary Agent "DESCRIPTION" was stopped. Each with the usage, and a kept worktree as the <worktree> element (worktreePath, worktreeBranch; ADR 0032 §2) -- a git command that failed adds worktree kept: REASON to the report of a completed run. Without a description the agent's name stands in.
perl_server_name
The name of the Perl tools' server (Langertha::Raider::PerlTools): raider-perl. When the caller has it, the subagent's Perl tools are its own, working in its "working_root".
briefing_class
The class of "briefing": Langertha::Raider::Briefing.
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.