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, the agent call 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, so send_message can 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 -M replaces (ADR 0014). An agent without memory (memory 0: the built-ins Explore and Plan, ADR 0027 §6) gets no Claude memory, no AGENTS.md and no packs, only its body, the skill listing when its set has skill, and its tools.

  • Own tool set ("tool_servers"). The caller's active set, each tool kept when the agent's tools allows it and disallowedTools does 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 are edit_file and multi_edit, and bash brings bash_output and kill_shell. ask_user always goes (nobody can answer a subagent), and so does enter_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. agent and send_message get instances for this depth -- or go, when this run is at "max_agent_depth" in Langertha::Raider::Application. send_message stays exactly when agent does (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 the skill tool.

  • 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, no agent.started, no request. Above 64 kB of skill text the run goes ahead, and the caller's stream and journal get agent.warning (call, agent, message: the size note) before agent.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_mode gets 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's model, 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.started carries 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.created carries parent, parent_call, agent, depth, agent_id and the name given 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 as message, every tool.call, tool.result and todo.updated, a delivered notification as a user message with notification 1, and run.finished. The caller gets only agent.started (call, agent, agent_id, background, description, session, model; resumed for a continued run) and agent.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 SubagentStart fires (matcher on agent_type) with agent_id -- the subagent's session id, or local-agent-PID-ADDR without a session -- and agent_type, the agent's name; its additionalContext goes in front of the briefed prompt as a <system-reminder> block ("sent_prompt"), and a continue: false fails the run before its first request. After a final answer SubagentStop fires as Stop does for the main run ("stop_hook_limit" in Langertha::Raider::Application): a block or additionalContext sends SubagentStop hook feedback: and the reasons back to the subagent as its next message, with stop_hook_active true, 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 the PreToolUse and PostToolUse hooks as the caller's do, with agent_id, agent_type and its own permission_mode in the input; those reports go to its journal, and a continue: false there cancels the subagent only. Its own task list fires TaskCreated and TaskCompleted the 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 no SubagentStart.

  • Worktree isolation (ADR 0032 §2, ADR 0027 §7). The call's isolation: "worktree" or the agent file's isolation: worktree ("isolated") runs the subagent in a git worktree of its own (Langertha::Raider::Worktree): TOP/.claude/worktrees/agent-N in the Claude layout, TOP/.raider/worktrees/agent-N otherwise ("worktrees_dir" in Langertha::Raider::Application), TOP the main checkout of the caller's root, on branch worktree-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 other isolation value 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 as cwd; 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) and agent.finished (worktree_kept; agent.started has worktree and branch). 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_message hands 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). agent and send_message are 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 the agent calls 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 with Concurrent 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 (its caller_call, the tool-use-id of 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 agent call 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's max_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.

https://raudssus.de/