Revision history for Langertha-Raider
0.504 2026-10-27 15:45:56Z
[Before you upgrade]
- INCOMPATIBLE: every tool call passes a permission policy, always on
(ADR 0021). In the default mode a run nobody can approve -- a one-shot
`raider "..."`, --json and the --stream-* flags, a Hall raider, ACP --
refuses writing files, bash, the network and messages unless a rule
allows them. See [Permissions] for the remedies.
- INCOMPATIBLE: bash refuses heavy commands (prove, make test, dzil
test|build|release, cpanm, docker build, ...) while the host is under
memory pressure or all heavy slots are taken -- the loadguard gate is
on by default (ADR 0018). LOADGUARD_THROTTLE=0 turns it off. See
[Tools].
- INCOMPATIBLE: a project with only CLAUDE.md, CLAUDE.local.md or
.claude/ is read in Claude mode (ADR 0019): the CLAUDE.md hierarchy,
the Claude settings (their permissions and env apply, their hooks
run), Claude skills and agents -- without --claude. Skills are no
longer glued into the system prompt in full: the prompt lists them,
the model loads one with the skill tool. See [Claude Code layout and
skills].
- INCOMPATIBLE: a prompt that starts with /NAME is a slash command, also
in a one-shot run (ADR 0030): `raider "/foo"` with no command or skill
foo is "unknown command" with exit 2 instead of a prompt to the model;
a path such as `/etc/hosts is broken` stays a prompt. In command
bodies $N counts from 0 as in Claude Code ($0 is the first argument).
See [Slash commands like Claude Code].
- INCOMPATIBLE: subagents nest up to three levels by default
(max_agent_depth 3, was 1; max_agent_depth: 1 restores it), the result
of an agent call ends with an agentId line and a <usage> block, and a
one-shot run waits for the background agents it started before it ends
(ADR 0027). See [Claude Code layout and skills].
[File tools]
- read_file asks the engine whether images and PDFs may go back as
content: supports('tool_result_images') and supports('tool_result_pdf')
(Langertha 0.504), replacing the guess from the wire name. Two separate
answers, so a model that takes images but no PDFs (or the reverse) gets
the one and a refusal for the other. FileTools::build_file_tools_server
has reads_pdf next to sees_images; neither falls back on the other.
- INCOMPATIBLE: read_file, write_file and edit_file answer like Claude
Code's Read, Write and Edit (ADR 0023): "File created successfully at:
P", "The file P has been updated successfully.", "String to replace
not found in file.", "Found N matches of the string to replace, but
replace_all is false. ...", "File does not exist. Note: your current
working directory is ROOT.", "EISDIR: ...", with Claude Code's error
flags and <tool_use_error> frame. The old "Wrote N bytes" / "Edited" /
"Error: not a file" texts are gone.
- INCOMPATIBLE: read before write. write_file, edit_file and multi_edit
refuse an existing file that was not read in the run ("File has not
been read yet. Read it first before writing to it.") or that changed
on disk since ("File has been modified since read, ..."). New files
need no read; what the tools wrote counts as read; a read with
offset/limit counts. The record is per run (begin_run empties it) and
holds in every layout. The CLAUDE.md / AGENTS.md files in the prompt
count as read, so they can be edited without a read_file first.
- INCOMPATIBLE: read_file returns cat -n lines ("N<TAB>line") and takes
offset (1-based) and limit; at most 2000 lines by default with a note
where the rest is, lines over 2000 characters cut, files over 256 KB
need offset/limit. Empty files and an offset behind the end give
Claude Code's system reminders. Binary files, devices and directories
are refused with a message. Images (PNG, JPEG, GIF, WebP) and PDFs go
back as image/document content when the model sees images and its wire
carries them in a tool result, else a clear refusal; Jupyter notebooks
come back as their cells with outputs. A read over 25000 tokens (bytes
/ 4) is refused as in Claude Code; pages ("1-5", at most 20) reads
PDF pages as text through pdftotext (poppler, looked up on PATH; a
PDF of more than 10 pages needs it) (ADR 0023 addendum).
- New tool multi_edit(path, edits) like Claude Code's MultiEdit: edits
applied in order on the content in memory, atomic, the failing edit
named by its number. Edit/Write/MultiEdit rules, acceptEdits and the
plan-mode refusal cover it as they cover edit_file.
- INCOMPATIBLE: in the Claude layout the file and search tools are no
longer confined to the root and the additional directories: a path
outside them asks (refused with how to allow it when nobody can
answer), allow rules such as Read(//tmp/**) open it, acceptEdits does
not cover edits outside, bypassPermissions reaches it, deny rules hold.
A symbolic link out of the root counts as outside. Every other layout
stays confined ("Path escapes root"), also in bypassPermissions.
FileTools and SearchTools share one confinement
(Langertha::Raider::PathConfine).
[Shell tools]
- INCOMPATIBLE: bash answers like Claude Code's Bash (ADR 0024): on exit
0 only the output -- stdout and stderr as one stream in the order
written -- or "(Bash completed with no output)"; otherwise an error
result "Exit code N" followed by the output. The "Exit code: 0" line and
the === STDOUT === / === STDERR === blocks are gone, and so is MCP-Run's
`compress` argument. Output over 30000 characters keeps its start and
its end around "... [N characters truncated] ...".
- INCOMPATIBLE: a command still running after its timeout (seconds,
default 120, at most 600) is no longer killed: it is moved to the
background with an ID, as in Claude Code.
- run_in_background: true starts a command in the background and answers
at once with its ID (bash_1, bash_2, ...). New tools bash_output
(bash_id, filter, block, timeout: new output, status
running/completed/killed, exit code) and kill_shell (shell_id: SIGTERM
to the process group, SIGKILL after 2 s). Background shells are
supervised by the application's event loop, live across the runs of a
session, survive a cancelled turn and are stopped when raider ends.
bash_output is a read tool, kill_shell has no effect class; both run in
every permission mode. The loadguard gate and allowed_commands are asked
before a background command starts, too.
- bash runs through Langertha::Raider::BashTools instead of MCP::Run::Bash;
the MCP::Run::Bash requirement is dropped. A working_directory is entered
with chdir: with MCP-Run's "cd DIR && COMMAND", a command such as
`x & y` ran y in raider's own directory.
[Task tools, plan mode and Claude Code tool names]
- INCOMPATIBLE: the task list is kept with task_create, task_update,
task_list and task_get, shaped after Claude Code's TaskCreate,
TaskUpdate, TaskList and TaskGet (ADR 0025): one task per call, ids
"1", "2", ..., statuses pending/in_progress/completed/deleted, owner,
metadata, blocks/blockedBy, and Claude Code's answers ("Task #1 created
successfully: ...", "Updated task #1 status", "Task not found"). They
replace todo_write in the default tool set, as in Claude Code;
RAIDER_ENABLE_TASKS=0 mounts todo_write instead. The task list lasts for
the conversation (todo_write's stays run state); /clear empties it; both
report it as todo.updated.
- New tool enter_plan_mode (Claude Code's EnterPlanMode): switches the
session to plan mode and answers Claude Code's instructions. The
permission gate asks the person first in default, acceptEdits and auto
mode ("for this session" adds the rule EnterPlanMode); it runs without a
question in bypassPermissions and is refused when nobody can agree.
- INCOMPATIBLE: MultiEdit now means multi_edit, not edit_file, in rules,
agent files and hook matchers; a hook gets it as MultiEdit with an
absolute file_path. TaskStop, KillShell and KillBash mean kill_shell
(task_id <-> shell_id); TaskOutput and BashOutput mean bash_output
(task_id <-> bash_id, timeout ms <-> s, TaskOutput's default block
true written down); TaskCreate & co. and EnterPlanMode have their
raider tools. ADR 0025 lists every known Claude Code tool name and why
the others (NotebookEdit, ToolSearch, SendMessage, Monitor,
EnterWorktree, ...) stay unknown.
- TaskCreated and TaskCompleted hooks (Claude layout, ADR 0022
addendum): task_create and task_update run them before the change, with
task_id, task_subject and task_description; exit 2 undoes the change
and the model reads "TaskCreated hook error: STDERR". todo_write fires
none. A subagent's own task list fires them too, with its agent_id,
agent_type and permission_mode; their reports go to its journal.
- A resumed (--continue, --resume) or forked session gets its task list
back from the journal's last todo.updated (not after /clear; ids
continue). Only for the task tools.
[Refusals, web tools and skills like Claude Code]
- INCOMPATIBLE: a PreToolUse hook's denial reads as in Claude Code: exit
2 gives "PreToolUse:Bash hook error: STDERR" (tool in Claude's name), a
JSON permissionDecisionReason is handed through as it stands, no reason
"PreToolUse:Bash hook blocked the tool call". A call to a tool that is
not offered (never, or since /reload or a mode change) is answered
at once, before the hook, the permission gate and any approval, with
"<tool_use_error>Error: No such tool available: X</tool_use_error>"
and " Tool names are case-sensitive: call Y instead." when only the
case differs. The old "denied: PreToolUse hook: ..." and "unknown tool
X" are gone.
- INCOMPATIBLE: refusals read as in Claude Code (ADR 0026). The person's
no: "The user doesn't want to proceed with this tool use. The tool use
was rejected ... STOP what you are doing ..." or, with a reason, "... To
tell you how to proceed, the user said:\n<reason>". A deny rule, plan
mode and an ask nobody can approve: "Permission to use bash with command
CMD has been denied." (other tools by name), then raider's reason and,
for an ask, how to allow the call. "The user refused this call" and the
application's "Tool call 'X' was denied: ..." are gone; the events keep
their reasons. The gate's verdict may carry `message`, the whole text
the model gets (Langertha::Raider).
- INCOMPATIBLE: exit_plan_mode answers in Claude Code's words: "User has
approved your plan. You can now start coding. ...", the mode and
"## Approved Plan:" with the plan; a refused plan is an error result with
the rejection text and the feedback; with nobody to review it is an error
result ("Permission to use exit_plan_mode has been denied. ..."); outside
plan mode "<tool_use_error>You are not in plan mode. ...".
- INCOMPATIBLE: the skill tool answers "Launching skill: NAME"; the skill
itself (base directory, body, arguments) follows the tool results as a
user message of its own, as Claude Code adds it. It stays when in-raid
compaction drops its exchange, is kept in history (the next raid no
longer loads it again), and is journalled as a user message right after
its tool.result, so a resumed session has it in the same place. An
unknown name is "<tool_use_error>Unknown skill: NAME. Available skills:
...</tool_use_error>".
- A tool result may hand the raid user messages under _meta
'raider/messages' (Langertha::Raider, TOOL RESULTS THAT ADD MESSAGES):
added after the batch's tool results, from raider's own tool sources
only -- a catalog MCP server's result loses the key.
- web_fetch takes prompt (Claude Code's WebFetch(url, prompt)): the page
text and the question go to a model call of its own, the tool returns
its answer; the page stays out of the conversation. The run's engine
answers, or the model web_fetch_model names (a new raider config key,
also -o web_fetch_model=). The call's tokens are the new journal event
model.usage (purpose web_fetch).
- INCOMPATIBLE: web_fetch follows a redirect only on the same host (www.
aside), scheme and port; one to another host is not fetched, the result
says where it points (Claude Code's "REDIRECT DETECTED" text), so domain
rules see the new host. A response that is not a success is "The server
returned HTTP <status>. ..." and, as in Claude Code, no error result.
- web_search takes allowed_domains and blocked_domains (subdomains
included): the results are filtered, site: / -site: go into the query,
both at once is an error.
- Token statistics like Claude Code's /cost: every model call of every
raider (the main one, subagents) and web_fetch's prompt is a
model.usage event (input, output, cache read/write tokens, model);
the application keeps them per model. /stats shows the total and one
line per model (also with --no-trace), the metrics of a run gain
"usage". ADR 0026 addendum.
[Slash commands like Claude Code]
- Custom commands (ADR 0030): .raider/commands and ~/.raider/commands;
in the Claude layout also ~/.claude/commands, .claude/commands and
the commands of enabled plugins (commands/ and the manifest's
"commands"). They join the skill catalogue: subdirectories make
namespaces (frontend/component.md is /frontend:component), plugin
entries are /plugin:name, a skill beats a command of the same name.
The model sees the commands it may invoke in the skill listing and
loads them with the skill tool, as skills.
- /NAME [ARGS] runs a skill or custom command in the REPL and in a
one-shot run; an entry of the catalogue replaces a built-in of the
same name, as in Claude Code. Frontmatter: description,
argument-hint, allowed-tools (extra allow rules for that run only;
deny and ask rules still win), model (that run only, not saved),
disable-model-invocation, user-invocable: false (not runnable as
/NAME); booleans take yes/no/on/off/1/0.
- Rendering like Claude Code (Langertha::Raider::SlashCommand, also
for the skill tool): $ARGUMENTS, $ARGUMENTS[N] and $N (0-based,
shell-style quoting), named `arguments`, ${CLAUDE_SKILL_DIR},
${CLAUDE_PROJECT_DIR}, ${CLAUDE_SESSION_ID}, ${CLAUDE_PLUGIN_ROOT},
\$ for a literal dollar, "ARGUMENTS: ..." appended when no
placeholder takes them. !`cmd` and ```! blocks run before the text
goes to the model -- only when the permission policy allows them
(allowed-tools counts), never asked; deny, ask or a failing command
aborts the invocation and nothing is sent. The loadguard gate
applies; disableSkillShellExecution in the Claude settings turns it
off. @path in a /NAME command attaches the file (counted as read) or
directory listing.
- New -p / --print: a one-shot run even on a terminal, as claude -p.
Report commands (/help, /context, /skills, /agents, /hooks, /memory,
/permissions, /config, /stats, /metrics, /packs) print and exit 0,
/init and /compact run; the rest is a usage error in a one-shot run.
- New built-in commands: /skills, /compact [INSTRUCTIONS], /context,
/memory [N] (opens a memory file in $VISUAL / $EDITOR), /agents,
/hooks, /init (asks the model to write CLAUDE.md, or the native
instructions file), /reset and /new (aliases of /clear). /help lists
the skills and custom commands with their argument hints.
- Langertha::Raider compress_history(_f) takes focus instructions, and
forgets the last prompt size afterwards, so the next raid does not
compress the summary again. New Langertha::Raider override_engine:
an engine for one run, returns the code that puts the previous one
back (the `model:` of a /NAME run).
- Langertha::Raider::Permissions has run rules (add_run_rules,
clear_run_rules): allow rules for the current run, cleared when it
ends.
[MCP servers like Claude Code]
- ADR 0029 decides how raider connects to the MCP servers a person
configures, as Claude Code does: sources, precedence, ${VAR}
expansion, tool names mcp__<server>__<tool>, approval of a
repository's servers, timeouts, statuses.
- The configured servers run: stdio (own session, in the root, with the
entry's env and CLAUDE_PROJECT_DIR; stderr kept for the status, never
on the terminal) and streamable HTTP (session id, protocol header,
JSON and event-stream answers). All start in parallel before the
first raid, each within MCP_TIMEOUT (30 s); a server that fails,
hangs or exits is reported (mcp.server event) and never stops the
run. Tools reach the model as mcp__<server>__<tool>; calls are bounded
by the server's timeout or MCP_TOOL_TIMEOUT. Stopped when the
one-shot run or the REPL ends.
- Raider speaks the initialize handshake of the MCP revisions
2024-11-05 to 2025-11-25 itself (Langertha::Raider::MCP::Server,
::Stdio, ::HTTP): the installed Net::Async::MCP 0.006 speaks only
2026-07-28 and cannot reach a server of an earlier revision.
- The tools of a configured server are foreign to the permission gate:
the mode asks, rules name them mcp__<server>__<tool> or mcp__<server>,
hooks match the mcp__ name. A result cannot hand raider user messages
(_meta raider/ keys are removed). Subagents use the same connections,
limited to the mcp__ tools their agent file allows.
- Sources, the first entry of a name winning whole: mcp: of
.raider/config.yml, mcp: of ~/.raider/config.yml, then in the Claude
layout the local servers of ~/.claude.json (projects.<dir>),
.mcp.json, the user servers of ~/.claude.json and the servers of
enabled plugins (plugin:<plugin>:<server>). --bare has none. The
entries are Claude Code's .mcp.json format (stdio command/args/env,
http url/headers, timeout); sse, ws, sdk, headersHelper and oauth are
reported as unsupported.
- .mcp.json servers and mcp: of a project config start only when
approved: enabledMcpjsonServers / enableAllProjectMcpServers /
disabledMcpjsonServers of the Claude settings -- a repository's own
.claude/settings.json only in a folder Claude Code trusts
(hasTrustDialogAccepted) -- or the new mcp_approvals: of
~/.raider/config.yml. A disabled entry rejects from any file.
- Langertha::Raider::MCP::Config (internal);
Langertha::Raider::Config->mcp_servers / mcp_approvals,
Langertha::Raider::Context::Settings->mcpjson_approvals,
Langertha::Raider::Context::Plugins->mcp_servers. mcp: and
mcp_approvals: are raider keys, never engine options; inside a
section, or mcp_approvals: in a project file, they are reported by
config explain.
- The REPL asks once about every .mcp.json (or project mcp:) server that
waits for an approval, with Claude Code's three choices (use it / use
all project servers / continue without it), before the first prompt.
The answer holds at once and is saved where it counts next time:
.claude/settings.local.json in a folder Claude Code trusts (as Claude
Code saves it), else mcp_approvals of ~/.raider/config.yml. A run
nobody can ask in (one-shot, --json, Hall, ACP, piped REPL) keeps them
off and says so on stderr; machine output is unchanged.
- REPL /mcp shows every configured server: status, scope, transport,
source, reason, approval, tools, the last stderr lines of a failed
one; /mcp reconnect [NAME|all] restarts servers. /mcp connects the
servers first; a one-shot raider -p /mcp prints the same report and
exits 0 (/mcp reconnect is REPL-only). config explain and /config list
the servers too.
- /reload reads the MCP configuration again: an unchanged server keeps
running, changed and removed ones are stopped, new ones start with the
next prompt. A cancelled turn leaves the servers running; SIGTERM, the
end of the REPL and the end of a one-shot run stop them.
[MCP follow-ups: parent .mcp.json, list_changed, output limit, prompts, resources]
- ADR 0029 Addendum 1. A project's .mcp.json is read from the project
directory and every directory above it, nearest first (a server named
in several wins whole from the nearest): a ~/.mcp.json now serves
every project below the home directory, as in Claude Code. They are
all scope project and need the same approval.
- A session started in bypassPermissions (--permission-mode or
--dangerously-skip-permissions) with skipDangerousModePermissionPrompt
in ~/.claude/settings.json approves the project servers without
asking, as Claude Code does; disabledMcpjsonServers still blocks.
- notifications/tools/list_changed: the server's tools are listed again
and the next model request offers them.
- MAX_MCP_OUTPUT_TOKENS (default 25000): a tool result's text over the
limit is cut with a note that says how to raise it; an error text
over about 11000 characters keeps its first and last 5000. Claude
Code saves the whole result to a file instead; raider keeps none.
- MCP prompts are slash commands: /mcp__server__prompt ARGS (arguments
by the prompt's declared order), listed in /help, also in a one-shot
run.
- MCP resources: the tools list_mcp_resources and read_mcp_resource
(Claude Code's ListMcpResourcesTool / ReadMcpResourceTool) are
mounted when servers are configured, and @server:scheme://path in a
prompt attaches the resource.
- The session journal records mcp.server (starting, connected, failed,
stopped, with reason and tool count) and the new mcp.tools_changed;
a stop outside a run reaches only the event stream.
[Monitor and cron jobs like Claude Code]
- ADR 0031 decides how raider streams events and schedules prompts as
Claude Code does, on the task registry of ADR 0027.
- New tool monitor (Claude Code's Monitor; description, command,
timeout_ms): runs a command in the background and delivers every
stdout line as a <task-notification> ("Monitor event: ...", the lines
of one 0.2 s batch in one <event>). The end, the deadline (5 minutes,
at most 30, at most 10 in a run nobody can answer) and a stop each
send one notice; a monitor that sends more than 20 notifications in
10 seconds, or 500 in all, is stopped with a hint. kill_shell
(TaskStop) stops it, bash_output shows its status; it ends with the
subagent that started it, and a piped or -p run waits for its
monitors. IDs monitor_1, monitor_2, ...
- monitor is a shell tool: allowed_commands and the loadguard gate
apply, a Bash(...) permission rule covers it (Monitor(...) names it
alone), and its effect class is code.
- New tools cron_create, cron_delete and cron_list (CronCreate,
CronDelete, CronList) in the REPL: session-scoped jobs with a
five-field cron expression in local time, recurring or one-shot, at
most 50, run as a turn of their own when the REPL is idle -- never in
a turn in progress, once after a long turn (no catch-up) -- with
Claude Code's jitter and the 7-day expiry of recurring jobs (one last
fire). Jobs are not durable and not restored on resume; a run with a
single prompt, a Hall raider and ACP are not offered the tools.
CLAUDE_CODE_DISABLE_CRON turns them off.
- No ScheduleWakeup: it belongs to the self-paced /loop raider does not
have; a reminder is a one-shot cron_create.
- ToolNames maps Monitor, CronCreate, CronDelete and CronList.
[Engines]
- `-e mock` (alias `virtual`) selects Langertha::Engine::Virtual, the rule-driven
engine without network or key (`-o rules_file=rules.yml`), so raider can be run
by hand and in tests against a scripted "model". It gets the prompt as typed:
the [situation] line is left out, because rules match word for word.
[Distribution]
- The raudssus/raider image is built and pushed by the bundle's
Dist::Zilla::Plugin::Docker::API over the Engine HTTP API, so rootless
Podman works, and is tagged latest, <major> and <version>. Every dzil
build or dzil test builds the image unless DZIL_DOCKER_API_SKIP=1 is
set. runtime-root is the Dockerfile's last stage, so a docker build
without --target yields the published image; runtime-user stays
available by name.
- The GitHub release is published by the bundle's GitHub::CreateRelease
after the tag is pushed, with the Changes section as release notes.
- web_fetch has a request timeout (fetch_timeout, 60 s) and a stall
timeout (stall_timeout, 20 s), both tool errors, and stops reading and
cancels the request at max_fetch_bytes instead of buffering the whole
body first. Its User-Agent carries the dist version.
- web_search ends within fetch_timeout as a tool error when a search
provider never answers, and its default HTTP client stops requests at
fetch_timeout and stall_timeout like web_fetch.
- The Telegram hall adapter no longer polls in a tight loop against error
answers: a non-2xx or ok:false reply is a telegram.poll_error with the
status and description, retried after retry_after (429) or a growing
backoff; 401 stops the bot. A failing update is a telegram.update_error
and no longer stalls polling or holds back the offset.
[Standalone binary]
- The binary no longer packs MCP::Run, since bash runs through
Langertha::Raider::BashTools.
[Claude Code layout and skills]
- A .raider/ directory makes a raider project only when it holds
config.yml, instructions.md, skills/, agents/ or packs/. raider's own
.raider/sessions, .raider/lib and .gitignore turned a Claude Code
project native from its second run on (ADR 0019 addendum 2).
raiders/ is no marker: ADR 0020 replaced raiders/<name>.yml by
agents/<name>.md.
- Raider decides once per run which layout it reads (ADR 0019): a project
with .raider/ (or a legacy .raider.yml / .raider.md) is a raider
project; one with only CLAUDE.md, CLAUDE.local.md or .claude/ (in it or
up to the repository top) is in Claude mode and is read like Claude Code
reads it, without any flag; anything else is native, and so is the home
directory itself. --bare reads no layout. config explain and the REPL
banner show the mode and what decided it.
- In the Claude layout raider reads the CLAUDE.md hierarchy as Claude
Code does: ~/.claude/CLAUDE.md, then CLAUDE.md (or AGENTS.md where a
level has none) and CLAUDE.local.md of every directory from the
repository top down to the working directory, each file its own
prompt item; @path imports are expanded (5 levels, not in code). A
CLAUDE.md below the working directory is appended to the result of
the first file tool that touches its subtree, once per session. -M
replaces only the persona and the raider instructions file, never
the Claude memory.
- The Claude settings are read too (~/.claude/settings.json,
.claude/settings.json, .claude/settings.local.json): their env is set
for the tools and their subprocesses, enabledPlugins picks the
installed Claude Code plugins whose skills are offered; their
permissions apply (see [Permissions]) and their hooks run (see
below). A broken settings file
is reported, never fatal; config explain lists the files, rule counts,
env names (never values), hook events and unused keys.
- Changed: --claude is now the switch for the Claude layout (also in a
raider project) and is saved as claude: true in the project config,
not as skills: [claude] (which still means the same when read). In a
Claude-mode project nothing is saved any more, so no .raider.yml
appears there. --openai / --codex load AGENTS.md as an instructions
item.
- Changed: skills are no longer glued into the system prompt in full.
The prompt lists every skill as "name: description" (frontmatter), and
the model loads one with the new skill tool, which returns its body
with "Base directory for this skill: DIR" and $ARGUMENTS filled in.
Skills come from --skills DIR and skills: directories (*/SKILL.md and
flat *.md), .raider/skills, ~/.raider/skills and, in the Claude layout,
~/.claude/skills, .claude/skills, .agents/skills and enabled plugins
(as plugin:skill; a plugin installed both for the user and for the
project is loaded once, the project install first); the first of a
name wins, the others are reported as shadowed.
disable-model-invocation: true hides a skill from the model. A
frontmatter YAML rejects but Claude Code reads (an unquoted ": " in a
description) is read line by line as key: value, so such a skill keeps
its description.
- Changed: in Claude mode (only Claude files, no .raider/) raider is
Claude Code, not Langertha: its default persona and the packs that are
on only by default (caveman) are off; the instructions are the
CLAUDE.md hierarchy plus a neutral base text. --pack, packs: and
detection still apply, -M still replaces the base text, and a raider
project with claude: true keeps its persona. config explain shows
"instructions: claude base" and the default packs as switched off by
the layout.
- /reload reads the Claude memory, settings (env included) and skills
again, and mounts or drops the skill tool as the skills change.
- Fixed: -o claude=false (and perl=, detect= with false/no/off/0, also from
a config file or the API) is false; the boolean words go through one
parser and anything else croaks.
- Fixed: a skills: entry of type file was silently dropped. It is a
skill now (one flat *.md file, also --skills FILE); a missing file is
shown by config explain, and an entry with an unknown type or no path
croaks.
- Skills can appear while working, like in Claude Code: the first file
tool that touches a subtree reads its .claude/skills, and a skill with
paths: in its frontmatter (globs, a list or comma separated) is listed
only once a matching file is touched. Both are announced in a
second system-reminder block of that tool result. In the Claude layout
the skill tool is always mounted.
- In the Claude layout raider runs Claude Code's hooks (ADR 0022):
PreToolUse before the schema check and the permission gate (exit 2 or
deny refuses the call with the hook's reason, updatedInput replaces the
arguments, an allow spares only the mode's question -- deny and ask
rules always hold, plan mode is unchanged -- and an ask asks even
where a rule allows), PostToolUse (context and feedback appended to the
tool result), UserPromptSubmit (a block keeps the prompt from the model
and the run fails with the reason; context goes with the prompt),
SessionStart (startup, resume after --continue/--resume, clear after
/clear) and Stop (a block sends its reason back and the run goes on,
at most 8 times). systemMessages and hook errors are shown; every hook
run is a hook.ran event in the session journal and the stream,
messages a hook.message. A native or raider project and --bare run no
hooks. transcript_path is the session journal (raider's JSONL).
- New Langertha::Raider::Hooks (internal): runs
the Claude Code hooks of the settings files and of the enabled plugins
(hooks/hooks.json and the manifest's hooks, with ${CLAUDE_PLUGIN_ROOT},
${CLAUDE_PLUGIN_DATA} and ${CLAUDE_PROJECT_DIR}) as Claude Code does:
matchers on the Claude tool name, the session source or the agent
type; exec form with args, else sh -c; each hook in its own session,
all of an event in parallel on the run's loop; the same stdin JSON;
timeouts (600 s, 30 s on UserPromptSubmit), async hooks, onFailure;
stdout JSON, plain-text context, exit code 2. The answers of all hooks
of an event are merged into one Langertha::Raider::Hooks::Result
(deny > ask > allow, updatedInput back in raider form, context,
systemMessage, continue: false) with a report per hook. When raider
loads briefing.skills itself, the briefing plugin's briefing-preload
hook is skipped for Agent calls so skills are not loaded twice.
disableAllHooks in the settings turns them all off.
- Langertha::Raider gained an internal, constructor-only _tool_hook
beside _tool_gate (asked before the schema check; deny, replaced
arguments, an allow/ask for the gate); Langertha::Raider::Permissions
gained rule_matches (a hook's "if" rule).
- Langertha::Raider::ToolNames translates tool arguments too, both ways:
path <-> file_path for read_file, write_file and edit_file, the bash
timeout in seconds <-> milliseconds; every other key passes unchanged.
- Agent files (ADR 0020, first step): raider now reads Claude Code's
subagent definitions -- Markdown with frontmatter -- from
.raider/agents and ~/.raider/agents and, in the Claude layout, from
.claude/agents, ~/.claude/agents ($CLAUDE_CONFIG_DIR respected) and
enabled plugins (as plugin:agent), walked recursively, hidden files
skipped; the first agent of a name wins, a built-in general-purpose
agent comes last. tools / disallowedTools are mapped to raider tool
names (unknown ones such as NotebookEdit are ignored and reported),
skills and briefing.skills are both read, and model (sonnet, opus,
haiku, a claude-* id) maps to a Claude model only under the Anthropic
API engine -- elsewhere the agent inherits the caller's model. config
explain lists every agent with its tools, model, declared skills,
unused keys, shadowed agents and files that are not agents. New config
keys agent_models (model per alias) and max_agent_depth are read. The
lenient frontmatter reader
now keeps one level of nesting (briefing: / skills: / - item).
- The agent tool (ADR 0020, second step) starts a subagent in the same
process: its own context, its own filtered tool set (no ask_user, own
todo list), its own child session, and agent.started / agent.finished
in the caller's journal and stream.
- Subagents (ADR 0020, third step) run on the model their file names
when the caller uses the Anthropic API, and are briefed: their skills
are put in front of the task in the briefing plugin's format; an
unknown skill refuses the spawn, over 64 kB warns with agent.warning,
and config explain flags agents whose skills do not resolve.
- A subagent runs under its caller's permission policy (ADR 0021): the
same mode, rules and additional directories, session rules shared both
ways; its own exit_plan_mode never switches the caller's mode. Starting
an agent needs no approval unless an Agent(name) / Task(name) rule says
so; a call without subagent_type counts as general-purpose. /reload
re-reads agent files.
- A skill an agent declares that is not installed is read from the
sources of skilletor.json (Claude layout; new dependency Skilletor),
without installing it, a source fetched only when it has no cache yet;
a refused spawn and config explain give the reason per skill (e.g. a
template, which the Perl port does not render).
- Hooks and subagents (ADR 0020 section 7): SubagentStart (context in
front of the prompt) and SubagentStop (Stop-style continuation) fire
with agent_id and agent_type, a subagent's tool calls run PreToolUse
and PostToolUse.
- Subagents and the Claude Code tools (ADR 0023, ADR 0025): a subagent
reads a file itself before it writes it (a read state of its own, as
Claude Code gives a subagent with a fresh context), keeps a task list
of its own (task_create & co. or todo_write), and is never offered
enter_plan_mode -- only the main session enters plan mode. In an agent
file's tools / disallowedTools, TodoWrite and the Task* names are one
family, Edit and MultiEdit are one, and Bash brings bash_output and
kill_shell.
- Built-in Explore and Plan agents beside general-purpose (ADR 0027
section 6), with Claude Code's descriptions: read-only by their
instructions and without agent, send_message, write_file, edit_file,
multi_edit and exit_plan_mode; their mission has no CLAUDE.md,
AGENTS.md or packs, only their instructions, the tools and the skill
listing. A file agent of the same name replaces either, as for
general-purpose.
- Subagents nest up to three levels by default (max_agent_depth, ADR
0027 section 8). Without the config key,
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH sets it in the Claude layout
(also from the settings env); config explain shows where the value
comes from and lists a value that does not fit as ignored.
- Background subagents (ADR 0027 sections 1-5): every subagent gets an
agent ID (agent_1, agent_2, ...) in the application's task registry,
and its result ends with Claude Code's trailer -- the agentId line and
<usage> with subagent_tokens, tool_uses and duration_ms; a failed or
cancelled call names the ID. agent takes run_in_background, model and
name: a background agent answers at once ("Async agent launched
successfully."), runs on the loop, cannot ask for permissions, and
its end comes back as a <task-notification> message -- before the
next model request, at the prompt as a turn of its own, or in front
of the next line typed. A run that would end waits for the background
agents it started (subagents, raider -p, the end of piped REPL input);
the first Ctrl-C leaves them running, leaving raider stops them.
agent.started gains agent_id and background, agent.finished agent_id,
session.created of a child session agent_id and name.
- New tool send_message (Claude Code's SendMessage) beside agent: a
running agent gets the message before its next request, a finished one
is resumed in the background with its history (also one of an earlier
process, found in the session's child sessions); Explore and Plan are
one-shot. The agent tool text follows Claude Code's current one.
- The REPL runs the event loop while it waits for input (with
Term::ReadLine::Gnu through its callback interface, else with a prompt
of its own instead of IO::Prompt::Tiny; Ctrl-D now leaves there too).
- The agent calls of one model turn run at the same time (ADR 0027
section 9), each through the gate, the hooks and the plugins, their
results in call order; any other call between them runs on its own.
agent and send_message no longer wait inside tool code: a foreground
agent is a Future the caller's raid awaits, so one started by a
background agent no longer holds up the main run. At most
max_concurrent_agents subagent runs at once (config key; without it
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS in the Claude layout; default 20,
shown by config explain); a spawn or resume beyond it is an error
result telling the model not to retry now. A Ctrl-C cuts off the
agent calls and the run ends once their agents have.
- Langertha::Raider: new attribute async_tools -- a tool whose call is a
handler returning a Future instead of an MCP call, concurrent ones
dispatched together within a turn. plugin_before_tool_call and
plugin_after_tool_call get the call's context as a further argument
(the tool call id; plugins may record keys there), so the Events
plugin gives every tool.result the id of its own tool.call. An
async tool may carry applies, asked with the tool's source in the
raid, so it stands in for one server's tool only.
- kill_shell (Claude Code's TaskStop) and bash_output (TaskOutput) take
agent IDs and agent names too (ADR 0027 section 10): kill_shell stops
a running agent ("Successfully stopped task: agent_1 (DESCRIPTION)",
then its "was stopped" notification), bash_output answers with the
agent's status and, once it has ended, its report in <output>; with
block it waits for the agent's end on the loop.
- Background shells are entries of the task registry (bash_N) and notify
the run that started them -- the main conversation or a subagent --
when they end on their own or are stopped, in Claude Code's form
(<status> completed, failed or killed; "Background command "..."
completed (exit code N)", "failed with exit code N", "was stopped");
bash's texts say "You will be notified when it completes." Nothing
waits for a
background shell: a run, raider -p included, ends while one runs.
bash_output with block lets the loop run while it waits. A subagent's
background shells outlive its run.
- The first Ctrl-C does not end the foreground command of a
background subagent: it belongs to the session, like a background
shell (BashTools background_pids). Leaving raider still ends it.
- A background subagent takes its own copy of the run rules (a
/command's allowed-tools) when it starts: a later command's rules do
not reach it, the end of the one that started it does not take them
away. Foreground subagents share the run's. /clear, /reset and /new
stop the conversation's background subagents without a notification
and drop the notifications that wait; background shells go on and
still notify.
- Subagents in a worktree of their own (ADR 0032 section 2, ADR 0027
section 7): agent takes isolation: "worktree", and an agent file's
isolation: worktree isolates every spawn of it; any other value is an
error result, and outside a git repository nothing starts ("isolation
"worktree" needs a git repository; ROOT is not inside one."). The
agent works in .claude/worktrees/agent-N (Claude layout) or
.raider/worktrees/agent-N under the main checkout, on branch
worktree-agent-N, from origin/HEAD or else HEAD (worktree.baseRef of
the Claude settings, worktree_base_ref of the config: fresh or head),
with the gitignored files .worktreeinclude names copied in; a root in
a subdirectory works in the same subdirectory of the copy. Its file,
search and shell tools, its permission policy and the working
directory of its mission are the worktree's; a write_file, edit_file
or multi_edit into the main checkout is refused before any rule or
mode. The worktree is locked while the agent runs. After every run it
is unlocked, and removed with its branch when nothing changed;
otherwise the result gains worktreePath/worktreeBranch lines, the
notification a <worktree> element, the registry entry worktree and
branch. A failed git command keeps it ("worktree kept: REASON"), a
cancelled run nobody hears of names it in a meta line ("agent agent_1
left its worktree P on branch B"). send_message continues in the
same worktree, checks the branch out again or makes it anew.
agent.started gains worktree and branch, agent.finished worktree_kept.
New internal module Langertha::Raider::Worktree (git command line,
failures as values). Hook inputs of an isolated agent have its
worktree as cwd; its perl_eval, perl_check and perl_cpanm run there
too, with the private lib of the root.
- enter_worktree and exit_worktree, Claude Code's EnterWorktree and
ExitWorktree (ADR 0032 sections 3 to 6), in the main run only (never
in a subagent). enter_worktree makes a worktree under
.claude/worktrees/NAME (Claude layout) or .raider/worktrees/NAME on
branch worktree-NAME, from the same base as an isolated agent's
(worktree.baseRef / worktree_base_ref), and moves the session's
working root into it -- the same subdirectory when raider runs in
one. A name is segments of letters, digits, ".", "_" and "-" joined
by "/", at most 64 characters, no "." or ".." segment; without one it
is session-N; an existing directory or branch of that name is an
error ("Worktree NAME exists; pass path to enter it."). path enters
a worktree git worktree list knows instead (not the main checkout),
also from inside another one, which stays on disk untouched; a path
outside .claude/worktrees/ and .raider/worktrees/ is put to the
person every time, whatever rule or mode allows it -- only
bypassPermissions skips the question, and nobody to ask refuses it.
exit_worktree keep returns to the root; remove deletes worktree and
branch, but only one this session created, and with changes (git
status --porcelain lines, commits beyond the base) only with
discard_changes: true (git worktree remove --force); a removal git
refuses returns to the root anyway ("worktree kept: REASON").
Outside a worktree it is a no-op that says so. From the next call
on, the file, search and shell tools (and the Perl tools when
mounted) work in the working root, the permission policy is rooted
there and refuses edits into the main checkout, hook inputs carry it
as cwd (the hooks themselves still run at the root with
CLAUDE_PROJECT_DIR the root), subagents spawned meanwhile start in
it and the next run's mission names it as working directory.
Context, settings, instructions, skills and the session store stay
at the root. Both tools are write tools: asked in default, allowed
in acceptEdits and auto, refused in plan; rules and hook matchers
name them EnterWorktree and ExitWorktree. A new conversation (/clear,
/reset, /new) and the end of the session (leaving the REPL, SIGTERM,
the end of raider -p) close a worktree enter_worktree made: removed
with its branch when unchanged, kept otherwise with a meta line
("worktree P kept on branch B (has changes)"); one entered by path
is only left. The journal records worktree.entered (path, branch,
created) and worktree.exited (path, branch, removed, reason
exit_worktree, clear or session_end). The main run's tool list grows
by the two tools. New internal module Langertha::Raider::WorktreeTools.
[Permissions]
- INCOMPATIBLE: every tool call of raider now passes a permission policy
(ADR 0021), and in the default mode a run nobody can approve -- a
one-shot `raider "..."`, --json and the --stream-* flags, a Hall
raider, ACP -- refuses writing files, bash, the network and messages
unless a rule allows them. The model gets the reason, the rule that
would allow the call and where to put it. Remedy: allow rules in
.raider/config.yml (`permissions: { allow: [...] }`) or
.claude/settings.json, `permissions.default_mode` /
`defaultMode`, --permission-mode acceptEdits|bypassPermissions or
--dangerously-skip-permissions, or `permission_mode` on a hall raider.
Langertha::Raider used as a library is unchanged: without a gate every
call runs.
- Permission modes like Claude Code: default (reading runs; writing,
code, network and messages ask), acceptEdits (also writes inside the
project and the additional directories), plan (read only; writing,
bash and messages are refused until the plan is accepted) and
bypassPermissions (everything runs -- except what a deny rule names,
unlike Claude Code). New options --permission-mode MODE and
--dangerously-skip-permissions. The mode comes from the flag, else
permissions.default_mode of .raider/config.yml / ~/.raider/config.yml,
else defaultMode of the Claude settings, else default.
- Permission rules in Claude Code syntax from the Claude settings and
from the new `permissions:` key of .raider/config.yml and
~/.raider/config.yml (allow, ask, deny, default_mode,
additional_directories): Tool, Bash(cmd), Bash(prefix:*), Bash with *
globs, Read(path) / Edit(path) with gitignore-style globs (//abs, ~/,
relative to the project), WebFetch(domain:host), Agent(name),
Skill(name), mcp__server[__tool]. Deny before ask before allow across
all sources. An allow rule covers a shell command only when every part
of it (&&, ||, ;, |, newlines) is covered and it has no substitution or
redirection into a file; ../ and symbolic links do not get around a
path rule. Invalid rules are ignored and shown.
- The REPL asks before a call the policy asks about: yes once (an
approval of exactly that call), yes for this session (the rule it
adds is shown first) or no with an optional reason for the model.
The prompt builder asks the same way. /permissions shows the mode,
every rule with its source and the session rules; /permissions mode
NAME switches the mode. /reload reads the rules again and keeps the
session's.
- Plan mode: the new exit_plan_mode tool (after Claude Code's
ExitPlanMode) presents the plan; accepted in the REPL the mode becomes
default, refused the model gets the feedback, and with nobody to ask
it is refused.
- Additional directories (additionalDirectories / additional_directories)
can be read, searched and, as the mode allows, written by the file and
search tools. build_file_tools_server takes `extra_roots`.
- glob and grep leave out the files a Read deny or ask rule names and say
how many ("N paths hidden by permission rules"); a search inside such
a directory is refused.
- Events permission.asked, permission.granted, permission.denied and
permission.mode, recorded in the session journal and in --stream-json.
config explain has a permissions section (mode and source, rules per
kind and source, invalid rules and mode names, additional
directories).
- Hall: a raider entry may set permission_mode, passed as
--permission-mode.
- /reload now reaches the additional directories of the mounted file and
search tools: they read the roots from the policy on every call
(`extra_roots` of build_file_tools_server and SearchTools may be a
CodeRef), so a directory added is reachable and one removed is refused
from the next call on. Symlink escapes stay refused.
- New permission mode `auto`, the raider form of Claude Code's
`defaultMode: "auto"` (until now skipped as an unknown mode): acceptEdits
plus bash commands that only read, from a fixed list (ls, cat, grep, rg,
find without -exec/-delete, git status/diff/log/show/branch, ...) where
every part of the command must be on it and none may hold a
substitution or a redirection into a file; everything else asks. Deny
and ask rules hold, allow rules add. Also --permission-mode auto,
permissions.default_mode: auto and /permissions mode auto. config
explain and /permissions show "claude auto -> raider acceptEdits +
read-only bash allowlist".
- ACP: a tool call the policy asks about, in a run of an ACP session, is
put to the client as `session/request_permission` (options allow_once,
allow_always with the suggested rule as a session rule, reject_once).
The hall opens a private Unix socket per such run (hidden option
`--permission-channel PATH` of raider, set only by the hall). Reject,
cancelled, an unknown option, an error, no answer within
`permission_timeout` (hall config, default 120 s), a closed connection
or session/cancel all refuse the call; nothing allows by default. Runs
without an ACP session are refused as before. `raider acp connect` asks
at the terminal, `raider acp prompt` rejects; the ACP client no longer
mistakes an agent request for the response of its own call. ADR 0021
addendum P2.
[CLI and REPL]
- New raider provider add ALIAS HOST [--credential-env NAME]
[--allow-internal]: fetches and validates the manifest, shows its
endpoints, auth and what is pinned, asks on a terminal and stores the
alias under providers: in ~/.raider/config.yml (origin, issuer, auth
types, the name of the environment variable the credential comes
from, allow_internal, pinned_at, fingerprint -- never a secret).
Without a terminal nothing is fetched or stored. raider --provider
ALIAS runs on the stored binding: its origin and allow_internal, the
key from its credential_env (or -k); when origin, issuer, an
endpoint's origin or dialect, the auth types or the credential_env
changed since it was accepted, or the entry was edited, the run stops
until provider add is
run again. A model-list or capability change does not. API keys in the
environment never select an alias; providers: in a project file is
ignored.
- New raider provider list, provider show ALIAS and provider remove ALIAS
[--yes] manage the stored aliases: list and show print what is pinned
(never a secret) and fetch nothing, remove asks on a terminal and needs
--yes without one. The aliases also appear in raider config explain and
the REPL banner.
- A model offered on several usable endpoints of a provider manifest is
refused with the endpoints named; the new --provider-endpoint ID
chooses one.
- A raider --provider that cannot run is also a refused or failed
document on stdout with --json (exit status unchanged); every such
document carries elapsed to the millisecond.
- raider --provider NAME: a name without dot, port, [IPv6] or https:// is
always a provider alias; one that is not stored is a usage error (exit
2, the failed document with --json) pointing to raider provider add,
and nothing is fetched. provider inspect and provider add no longer
take a bare name as HOST: a host needs a dot, a port, brackets (an IPv6
literal such as [::1]) or https:// (ADR 0007).
- The REPL banner no longer says "no API key required" for a -k key under
--provider, and shows the provider alias.
- run.started names session, run, cwd, permission_mode, tools and
raider_version; run.finished and the --json document carry a reason
code whenever a run did not complete; permission.* and the tool
hooks' hook.ran name their call; a failed, refused or cancelled tool
call runs the PostToolUseFailure hooks;
bin/raider documents every
stream event type (ADR 0034).
[Tools]
- edit_file takes replace_all, as Claude Code's Edit: every occurrence
of old_string is replaced instead of an error when it is not unique.
- New tools, named and shaped after Claude Code's Glob, Grep, TodoWrite
and AskUserQuestion (ADR 0018). glob(pattern, [path]) lists files by
pattern ("**/*.pm", "*.{json,yml}"), newest first, as paths relative to
the root. grep(pattern, [path], [glob], [type], [output_mode], [-i],
[-n], [-A], [-B], [-C], [context], [multiline], [head_limit], [offset])
searches contents: files_with_matches (default), content with context,
or count. grep runs ripgrep when it is installed and plain Perl
otherwise, with the same output; both skip .git, symbolic links, binary
files and files over 10 MB, and neither reads .gitignore. todo_write
keeps the model's task list in the state of the run
(Application->todos) and reports every change as the event
todo.updated (session journal and --stream-json). ask_user puts a
question, optionally with choices, to the person at the REPL; in a run
nobody can answer (one-shot, Hall, cron, ACP) it is refused with an
error result that tells the model to decide itself. glob and grep are
confined to the root like the file tools. Effect classes: glob and
grep read, todo_write none, ask_user message.
- INCOMPATIBLE: bash is gated by a port of the loadguard plugin, on by
default. A heavy command (prove, make test, dzil test|build|release,
cpanm, perlbench, docker|podman build|run, cargo build|test, npm test,
claude -p|--bg, codex exec|review), found in command position, is
refused with the measured figures and the advice that fits while the
host is under memory pressure (PSI memory full avg10 >=
LOADGUARD_PSI_FULL, default 10 %; swap used >= LOADGUARD_SWAP_USED,
default 90 %) or all heavy slots (LOADGUARD_HEAVY_SLOTS, default max(1,
nproc/2)) are held by running commands of raider processes. Light
commands are never refused; without /proc/pressure or swap the signal is
"no pressure"; LOADGUARD_THROTTLE=0 turns the gate off.
LOADGUARD_CONFINE=1 (off by default) makes raider start itself again in
a transient systemd user scope with memory limits
(LOADGUARD_MEMORY_HIGH/MAX/SWAP_MAX in percent of RAM,
LOADGUARD_CPU_WEIGHT). New Langertha::Raider::Loadguard.
- New Langertha::Raider::ToolNames: the table of Claude Code tool names
and the raider tools they stand for (Read -> read_file, Edit ->
edit_file, MultiEdit -> multi_edit, Glob -> glob, Task and Agent ->
agent, ...;
mcp__* pass through) for the Claude-mode surfaces.
[Raid engine]
- The application no longer sends an embedding request per session-history
entry: it mounts no tool that reads them, and a provider without an
embeddings endpoint answered every one with a 404. Embeddings run only
when a history tool is mounted and the engine can embed (ADR 0028);
`raider config explain` shows `session embeddings`. Langertha::Raider
used directly is unchanged.
- A Langertha::Raider that goes away while session-history embeddings
are still in flight lets them finish instead of dropping them. Every
subagent run printed "Suspended async sub
Langertha::Role::Embedding::simple_embedding_f lost its returning
future". Cancelling them instead would leave a half-open
Net::Async::HTTP connection on the loop.
[Hall]
- A client that subscribed to the hall's events on the control socket no
longer makes the next event die: the hall asked its stream for ->handle,
which an IO::Async::Stream does not have, so spawn answered "command
failed" (the raider was started nevertheless) and no event reached the
subscriber.
- A hall raider entry takes provider: ALIAS (raider hall add-raider and
init --provider ALIAS) and is spawned with --provider ALIAS. A host or
origin, an alias that is not stored in ~/.raider/config.yml, and
provider together with engine are refused before writing and at spawn.
- The hall event bus is a versioned public wire format (ADR 0033): every
event carries version 1, seq and a float time (ts stays); events and
control-socket answers are canonical JSON; the hall POD lists every
type.
- A mission the hall refuses (provider entry) emits raider.refused with a
reason code; the spawn answer adds status/reason, the slot log line
reads "[hall] raider NAME refused: WHY; mission not run", Telegram gets
a short reply, cron reports binding cron:ID.
- Fix: an ACP session/prompt whose queued mission was refused when pulled
from the queue waited forever; it now ends with JSON-RPC error -32000.
0.503 2026-10-01 21:25:19Z
[Distribution]
- Langertha-Raider is the merge of the App-Raider distribution with the
agent engine extracted from Langertha core. Langertha::Raider,
Langertha::Raider::Result and the Langertha::Raid orchestration layer
(Raid::Loop, Raid::Parallel, Raid::Sequential) ship here under their
own names. Every App::Raider::* module is renamed to
Langertha::Raider::*, with the CLI entry point App::Raider becoming
Langertha::Raider::CLI; the raider and raider-hall executables keep
their names. Requires Langertha 0.503 and Net::Async::MCP 0.004.
- The six App::Raider packages previously indexed on CPAN (App::Raider,
App::Raider::FileTools, App::Raider::Plugin::Situation,
App::Raider::Plugin::Trace, App::Raider::Skill, App::Raider::WebTools)
ship as empty reserved-namespace stubs pointing to their
Langertha::Raider replacements.
- Langertha::Raider calls only Langertha's public engine methods
(async_request_f, async_loop, langfuse_timestamp,
Langertha::Usage->from_raw) and Role::PluginHost's public plugin hooks
(plugin_instances, plugin_args, plugin_pipeline_tool_call_f).
Langertha::Raider::Result is self-contained.
- IO::Async::SSL is a requirement: every cloud engine and web_search
speak https, and without it every request fails.
- Docker builds install from the Dist::Zilla build directory via cpm and
the MetaCPAN resolver; no cpanfile.snapshot is shipped. GitHub and
Docker release publishing runs from maint/release-after.pl.
[Standalone binary]
- Standalone Linux binaries (x86_64, aarch64) are attached to each GitHub
release by the release-binaries workflow; the build, verify and
runtime-library scripts are in scripts/ and not shipped to CPAN.
- New raider --version: prints raider's and Langertha's version and exits 0.
- The binary's hall runs the binary directly instead of perl <binary>,
which died with "Unrecognized character \x7F"; RAIDER_HALL_RAIDER_BIN
still overrides it.
- perl_eval, perl_check and perl_cpanm run the perl on PATH inside the
binary, whose $^X is no usable interpreter, and return a tool error
saying so when there is none. The lib's perl-version marker records
the version of the perl that uses the lib, not the packed one.
[CLI and REPL]
- bin/raider is a thin wrapper around internal
Langertha::Raider::CLI::* classes (Main, REPL, Runner, Commands,
PromptBuilder, Output). raider exits 0 on success, 1 when the run
failed, 2 on a usage error, 3 on a configuration error and 4 when a
session is open in another raider.
- Machine output is versioned: --json, --msgpack and --yaml print one
document with "version" (1), "status" (completed or failed) and the
run's session; --FORMAT=N selects the version, an unknown one is a
usage error. --json encodes UTF-8 once and keeps the live trace off
unless --trace is given; the document's elapsed has millisecond
resolution.
- New --stream-json, --stream-msgpack and --stream-yaml: run.started,
run.state, tool.call, tool.result, message and run.finished events
as the run happens, run.finished always last; tool.call and
tool.result carry call and status. All six machine flags exclude each
other, and so does -i; with any of them --trace goes to stderr and
stdout carries only the machine output. Requires Data::MessagePack.
raider -i reads piped input to its end.
- A one-shot raider cancels its run on the first SIGINT (Ctrl-C): it
writes the cancelled document or run.finished, then dies of SIGINT
(exit 130); a second SIGINT, or SIGTERM, interrupts at once, writing
the "interrupted" document and dying of the same signal (130/143),
also when the signal arrives during startup. Tool commands still
running (bash, perl_eval, perl_cpanm) are ended first, and a tool call
that ends with an error after a cancel is reported as cancelled.
- The REPL's first Ctrl-C cancels the running turn: tool commands are
ended, the model request is abandoned, the session journal records the
run as cancelled, and the REPL goes on; a second Ctrl-C within 2s
quits, ending tool commands too.
- New Langertha::Raider->cancel stops a raid at its next safe point and
is safe in a signal handler; the raid returns a Result of type
cancelled (is_cancelled), and a tool call it cut off is reported as
cancelled. Langertha::Raid::Parallel passes on a cancelled branch as
the raid's result.
- New raider config explain [options] (the options may also come first)
and REPL /config: every effective setting with its source (flag,
.raider.yml layer, environment, default), the instructions source
(-M, .raider.md, default), bare, where each pack comes from, and
whether the Perl tools are on and why, and each mounted tool with its
source and what it can do (read, write, network, code, message; tools
outside the built-in table show as unknown). Information only, nothing
is enforced.
- Explicit -m and -k beat model:/api_key: from .raider.yml, and the
banner shows the model the engine is actually built with. When neither
-m nor a table default is set (e.g. -e minimax), the engine's own
default_model applies.
- -o with raider's own keys (perl, packs=a,b, skills=a,b,
preferred_lib_target, engine) configures raider like .raider.yml and
overrides it; only engine attributes reach the engine.
- raider reads the project config from .raider/config.yml (same keys as
.raider.yml). When both files exist only .raider/config.yml is loaded,
raider warns on start, and raider config explain names the file in use
and the ignored one. /model and saved skills write to the file in use.
- raider reads ~/.raider/config.yml under the project config (default <
home < project < command line). Project values replace home ones,
skills and no_detect add up, detect: rules are replaced per pack.
raider config explain names home values "home" and prints the home
file. A broken home file stops raider (exit 3); raider never writes it.
- New raider config migrate [--dry-run] moves .raider.yml to
.raider/config.yml and .raider.md to .raider/instructions.md: shows
what it does first, writes atomically, keeps the legacy files as .bak,
creates .raider/.gitignore, and leaves any api_key out of the shareable
.raider/config.yml, reporting where it was. Refuses without writing
when a target or backup already exists.
- ~/.raider/config.yml takes project_tools, a map from workspace selector
("*", a path glob, a workspace name) to tool names. raider config
explain shows which selectors match the project, which tools they grant
and which packs request them; a project file cannot grant. Information
only: nothing is mounted from it yet, and workspace names never match
yet.
- raider reads the project instructions from .raider/instructions.md, else
.raider.md. When both exist only .raider/instructions.md is used, raider
warns on start, and raider config explain names the ignored file under
instructions:. /prompt writes the file in use (.raider.md when there is
none).
- .raider.yml has one reader and writer, Langertha::Raider::Config
(internal). Top-level keys apply next to default: and engine sections,
so /model or a skills: hash no longer drops packs, perl or engine
options; skills: merge across sections and with --claude / --openai /
--skills, which all persist; engine: picks the engine. A file that
does not parse, or a mapping under one of raider's own keys (packs:,
perl:, engine:, ...), stops raider with a one-line error (line, column
and reason for a parse error) instead of being ignored. Raider-only
keys never reach the engine constructor.
- -M now replaces only the instructions (default persona and .raider.md);
skills, packs (configured, flagged or detected) and the tool
description still apply. Anyone who used -M to drop everything else
needs --bare. /reload and /pack keep a -M mission, and /reload, the
banner and the generated raider skill name it as the persona.
- New --bare: isolated context without .raider.md, skills, pack
detection, packs: or default packs; --pack NAME and /pack NAME still
switch packs on. -M TEXT --bare gives TEXT plus the tool description.
- New --pack NAME for one-shot pack activation.
- REPL: a line !CMD runs CMD with $SHELL -c in the root on the terminal
and sends nothing to the model; ?CMD runs it with its output shown and
captured, then sends the command, its exit status and its output (head
and tail past 20000 characters) to the model as the next prompt,
recorded in the session. Ctrl-C ends the command, not raider; a ?CMD
ended by Ctrl-C sends nothing. A line that is only ! or ? is a prompt.
- raider --help lists --trace next to --no-trace.
- New raider provider inspect HOST[:PORT]|https://HOST fetches
/.well-known/langertha.json (https only, 1 MiB, 10 s, up to 3
same-origin redirects, no credentials sent; internal addresses are
refused unless --allow-internal, cloud metadata never), validates it
with Langertha::Manifest and shows provider, issuer, endpoints, auth and
models, with warnings for unknown dialects, auth types and capabilities.
--json, --msgpack and --yaml give one versioned document. Requires
Langertha::Manifest from Langertha core.
- New raider --provider HOST[:PORT] runs one invocation on the endpoint
a provider manifest declares, mapped to the matching Langertha engine;
nothing is stored. -m picks the model (required when the manifest
lists several). The key comes only from -k or -o api_key=, never from
*_API_KEY in the environment or .raider.yml. The endpoint must be https
on the manifest's origin; internal addresses need --allow-internal, as
for provider inspect. The engine connects to the endpoint address
raider checked (Langertha connect_address) instead of looking the name
up again; TLS still verifies the host name. Not combinable with -e,
-o engine= or -o url=.
The REPL banner shows the -k key as set without printing it.
- Saving skills (--skills, --claude, the /skill writers) no longer writes
an empty skills: ~ into an existing default: section of the project
config.
[Packs]
- Packs are also found in <project>/.raider/packs/<name>/ and
~/.raider/packs/<name>/ (same layout as the shipped packs); for the
same name the project beats home, and home beats the shipped pack. A
pack directory with an unreadable pack.yml is skipped and reported in
raider config explain and /packs instead of stopping the start; one
without a SKILL.md loads. The never-used pack keys mcp,
add_allowed_commands and engine_options are ignored.
- Packs can activate by workspace detection: declarative must / may /
must_not rules on files and their content, from a pack's pack.yml or
detect: in .raider.yml; switched off with no_detect:, detect: false,
--no-pack NAME or --no-detect. raider config explain and /packs show
each pack's source (flag, config, default, detected) and the rule
clause that matched; /reload detects packs again.
- New bundled perl pack: in a Perl workspace (cpanfile, dist.ini,
Makefile.PL or lib/**/*.pm) the perl_eval / perl_check / perl_cpanm
tools are on without --perl; perl: false keeps them off. The bundled
git-guru pack drops a pack.yml key the loader never read.
[Tools]
- The tool description in the system prompt and the exported how-to-use
document (/skill, /skill-claude) are generated from the tool servers
mounted for the run (name and parameters from each tool's input
schema, optional parameters in brackets), so the Perl and Hall tools
are described when mounted and only then. -M and --bare keep it.
Switching the perl pack on or off with /pack (or /reload) mounts or
unmounts the perl_* tools; the exported Markdown no longer contains
POD markup.
- MCP servers are named raider-files, raider-web, raider-perl and
raider-hall; the Claude skill export is named raider
(.claude/skills/raider/SKILL.md) and points out a leftover app-raider
skill.
- Perl tools: --perl unlocks perl_eval / perl_check / perl_cpanm with a
private local::lib per raider and auto-recovery on missing modules.
perl_eval and perl_check run the current perl in the working root
with the private lib; perl_check says BEGIN/use run; real errors are
no longer reported as "timeout"; perl_cpanm rejects targets outside
the root, uses the local::lib correctly and records only successful
installs. The tools kill the child process when it times out, and
every timeout (including the retry after an auto-install and cpanm
itself) comes back as a result instead of an exception; new
install_timeout option. A relative preferred_lib_target resolves
against the working root for PERL5LIB and cpanm alike.
- Filesystem confinement rejects symlink escapes for read and write
operations.
- A raid, and the web_fetch / web_search tools, fail at once with the
module's name when IO::Async::Internals::Connector, or for https
IO::Async::SSL (IO::Socket::SSL, Net::SSLeay, libssl), does not load,
instead of the first request failing and every later one to that host
hanging (a Net::Async::HTTP 0.50 connection-slot leak). web_fetch
checks each redirect target too, so an http to https redirect without
IO::Async::SSL is a tool error and does not block later fetches from
that host. WebTools sync wrappers await IO::Async futures through the
loop.
- Plugin self-tools are offered to the model once per raid. A tool name
offered by two tool sources (engine mcp_servers, inline MCP, catalog
MCPs, self-tools) is sent once: the first source wins and a warning
names both. A raider_-prefixed name an MCP source registered is
called on that source instead of failing as "Unknown self-tool".
- A tool call whose arguments do not decode (on a reply that did not
hit its token limit) is answered with an "arguments are not valid
JSON" error result the model can retry, instead of running the tool
on {}.
[Raid engine]
- Inline MCP tools and raider_wait run on the engine's event loop, so an
engine on its own loop no longer hangs a raid. A raider whose engines
(engine, compression_engine, engine_catalog, embedding_engine) sit on
different event loops fails at the start of raid_f and respond_f,
naming the engine, instead of hanging.
- The trace and Langfuse count tokens from Gemini, Ollama and AKI native
responses too. Usage follows Langertha's reading: input_tokens wins
over prompt_tokens when a response has both, and a count missing from
a usage block goes to Langfuse as 0 instead of null.
- Raid tool-loop replies are read via Langertha::Role::Tools
tool_loop_response, so a 200 body carrying an error ends the raid
loudly instead of silently with "". Gemini thoughts and truncated
tool calls are dropped as in core, and an unknown tool returns an
error result instead of aborting the raid. compress_history_f reads
the summary the same way and croaks instead of producing an empty one.
- Raider compresses its context inside a single long raid, dropping the
oldest tool exchanges first, not only between raids. The mission and
system prompt carrying activated skill content is never compacted.
- Session-history embeddings no longer block the event loop: each
message is embedded in the background through simple_embedding_f, a
failed embedding is logged and skipped, and the search tool ranks only
what is embedded so far (falling back to text match if the query
itself cannot be embedded). no_session_embeddings is no longer needed
as a deadlock workaround. An embedding engine without
simple_embedding_f gets no embeddings.
- session_history keeps tool_call and tool_result turns as structured
blocks, so history replayed into a later raid (and the embedding text
derived from it) no longer stringifies or drops them. New
clear_session_history empties session_history and its embeddings
together.
- The session history renderer (session_history tool, history query)
no longer drops or blanks the tool results Langertha sends natively:
a Responses function_call_output whose output is an input_text /
input_image part array, a Gemini functionResponse with inlineData
parts, and an Anthropic tool_result with image blocks keep their
text and show each image as [image], never as base64. A Gemini
functionResponse carrying the tool's structured content is shown
whole, as JSON, instead of as an empty "tool_result:". An Anthropic
text document shows its text (and title) instead of a bare
<document>, a PDF document shows as [document], and a search_result
keeps its title and source URL next to its text.
A custom content document (source type content) shows its text
chunks instead of a bare [document], and a document's context
field is kept on its own [context] line.
A tool-result PDF (Responses input_file, Gemini functionResponse
inlineData) shows as [document] name.pdf or [document]
application/pdf instead of <input_file> or <block>; its base64
payload is never shown.
- A raid paused by an interactive self-tool (raider_ask_user etc.) in
the middle of a parallel-tool batch no longer re-runs the calls that
completed before it or drops the self-tools queued after it:
respond_f resumes with only the calls after the pausing one, so side
effects fire once and every tool_use gets exactly one tool_result
(strict providers such as Anthropic rejected the duplicates with a
400).
- Tool calls resumed after raider_ask_user or raider_pause go
through the same dispatch as any other call: they run
plugin_before_tool_call, so they get their tool.call event and
plugin gates apply to them.
- The answer to a paused raider_ask_user or raider_pause call and the
result of raider_wait go through plugin_after_tool_call, so every
tool.call event gets its tool.result. A cancelled raider_wait gets a
cancelled tool result like a cut-off MCP call.
- A plugin_before_llm_call that returns a fresh conversation also
updates what a paused raid resumes with, so respond_f keeps every
assistant and tool_result message from before the pause.
- Langertha::Raider::Result is always true in boolean context, so
`if (my $r = $raider->raid(...))` no longer skips question, pause and
abort results with empty text. Stringification is unchanged.
- Langertha::Raider checks each tool call's arguments against the tool's
inputSchema before running it: a missing required key or a top-level
property of the wrong type gets an error result the model can correct,
and the tool does not run. The check is lenient: numeric strings, 0/1
booleans and null for an optional property pass. raider_ask_user
called without question returns that error instead of dying with a
Result type-constraint error.
[Sessions]
- Every run is recorded in a session journal,
.raider/sessions/<id>.jsonl in the project: the input, every tool call
with its whole result, the answer and how the run ended, also when it
failed or was interrupted. .raider/.gitignore keeps journals out of
git (also written when .raider/lib is created); --no-session switches
recording off. A journal that cannot be written mid-run is a warning;
the run goes on.
- New raider session list, session show ID [--json], session resume ID,
session fork ID and session rm ID (refused while another raider has
it open), and --session ID / --continue for one-shot and REPL: the
conversation is replayed, nothing recorded is run again, and runs
without an end or tool calls without a result are reported. Session
ids can be shortened to a unique start or their last four hex digits.
/clear in the REPL is recorded and honoured on resume.
[Hall]
- raider hall start/spawn/attach/logs/kill no longer treat options as
positional arguments (e.g. `start --daemon DIR` ignored DIR).
spawn/attach/logs/kill take an optional hall DIR as first argument
without it leaking into NAME, MISSION or ID; a name that is a plain
directory stays a name. start/stop/status/ps/install fail with "Not a
directory" when DIR is not a directory instead of using the current
directory.
- Hall raiders run with --stream-json: stdout goes to a file per run,
stderr to the slot log, and raider.done carries status and response
or error from run.finished; a raider killed before its result is a
clear failure. Only the newest 20 events files per slot are kept
(logs.keep_events in .raider-hall.yml, 0 keeps all).
- raider hall attach ID prints the run's --stream-json events until the
raider exits; raider hall spawn --attach spawns and then attaches (the
run ID line goes to stderr, a queued mission is not attached). The
spawn reply names events_path, and carries queue_depth for a busy
slot.
- The hall ends each run in the slot log with one "[hall] raider ID
STATUS: TEXT" line and marks each run's start, so raider hall logs ID
shows the answer or error and only that run's part, also after the
run has ended; logs --follow prints new output until the raider
exits. A slot log over logs.max_log_size (default 1 MiB, 0 off) moves
to SLOT.log.1 when the slot's next run starts. Run IDs stay unique
(SLOT-TIME.2, .3, ...).
- A plain hall raider name (bjorn) runs its missions in parallel and each
run is tracked and reported on its own; numbered names (1bjorn) stay
singletons with a queue that survives restarts. Missions a previous
hall left waiting run first after a start, and a new mission never
overtakes them.
- Hall runs keep their conversation in a session journal under the hall
root: a Telegram chat (or topic), a cron job, an ACP session and a
numbered slot each continue their own session, a plain-name run gets a
fresh one. raider.done, ps and attach name the session; a run whose
session is in use outside the hall fails at once with a raider.done
saying so. A mission for a binding that is already running waits in
that binding's queue, kept across hall restarts. raider hall session
reset BINDING and Telegram /new start a binding over in a new session
(journals are kept); ACP bindings are forgotten on hall start, and so
are the bindings and waiting missions of cron jobs no longer
configured.
- A hall cron job with coalesce: true drops an occurrence (cron.coalesced)
while its previous run is still running or waiting. Cron scheduling
does not block the IO::Async loop.
- A hall raider's persona: X in .raider-hall.yml starts that raider with
the pack X as well. The never-used raider keys mcp and isolated are
gone (raider hall add-raider --isolated is removed; old configs get a
one-time note in the slot log). preferred_lib_target in
.raider-hall.yml is read (default .raider/lib): each hall raider
installs modules there and gets <target>/lib/perl5 on PERL5LIB.
- Hall raiders without an engine: leave the choice to raider
(.raider.yml, then API-key autodetection) instead of anthropic;
raider hall init and add-raider write engine: only when --engine is
given.
- Hall raiders get telegram_reply, hall_status and hall_spawn tools
(keyed on $RAIDER_HALL_SOCKET). Docker-aware systemd install: inside
a container the unit is written to .raider-hall/systemd/<name>.service
with a cp one-liner printed; --docker uses docker run and publishes
the ACP port.
- Hall Telegram access is fail-closed: an empty or missing allowlist
rejects every message, allowlist holds sender user ids (from.id), and
group chats must also be listed in the new allowed_chats. Rejections
emit telegram.rejected; startup warns about empty allowlists and chat
ids found in allowlist. telegram.in events carry the sender's
first_name and username, also in group chats.
- Telegram replies: a raider spawned for a Telegram message gets its bot
and chat id (RAIDER_HALL_TELEGRAM_BOT/_CHAT_ID), and telegram_reply is
bound to that chat (and forum topic), needing only text. The Telegram
bots stop polling with a warning and a telegram.poll_error event
naming the module, and their replies return an error, instead of
hanging on the Bot API when a connect module does not load.
- Hall fixes: singleton queue replay, --pack propagation, Telegram
future retention, MCP tool result handling, a mission passed as a
single argv instead of split on whitespace, no double-reaping of child
processes, and the shutdown grace-period timer now fires.
[ACP]
- Hall ACP speaks JSON-RPC 2.0 line-framed over TCP (initialize /
session/new / session/prompt / session/cancel); configure via
acp: { port, host } in .raider-hall.yml or --acp-port N on
raider hall start. raider acp ping|prompt|connect HOST:PORT drives any
ACP-conforming agent from the same binary.
- A prompt that has to wait is answered when its run ends (end_turn or
cancelled) instead of at once, also when the hall had to start that
run without its session (hall.session_error names the run);
session/cancel ends waiting prompts as cancelled and cancels the
running hall raider with SIGINT, so its run ends as cancelled. If it
does not end, the hall escalates to SIGTERM, then SIGKILL, each after
cancel_grace in .raider-hall.yml (default 5 seconds, 0 never
escalates). raider hall kill still stops it with SIGTERM.
- A failed raider run ends session/prompt as a JSON-RPC error (-32000)
instead of streaming the error text as an agent_message_chunk with
stopReason end_turn; a killed raider is forwarded as that failure
rather than raw log text.
[Documentation]
- README describes the current CLI, config and Hall/ACP behavior, the
standalone binary and a Roadmap for planned features.
examples/multi-raider-hall/ walks through a minimal multi-raider
village wired for cron and ACP.
- perldoc raider documents every option, the tool set and the
environment variables raider reads (API keys, web-search keys,
RAIDER_PACK_DIRS, RAIDER_HALL_SOCKET, ...); raider-hall has a manual
page of its own; Langertha::Raider::Hall documents
RAIDER_HALL_RAIDER_BIN, RAIDER_HALL_ACP_PORT, RAIDER_HALL_ACP_HOST and
the environment it gives its raiders; Langertha::Raider::Packs
documents the pack.yml keys; Langertha::Raider documents run_f, its
raid plugin hooks, automatic context management and the
session_history archive. The Hall parts and the raider acp dispatcher
are marked internal. engine: is documented as optional for hall
raiders, and the hall MCP adapter (.raider-hall.mcp) as not
implemented. Broken POD links and empty sections are fixed.
[Tests]
- Test suite for packs, Perl tools, Hall, ACP server and client, plus an
opt-in live task suite (t/live, RAIDER_LIVE_TASKS=1) that drives
bin/raider against a fresh workspace and isolated home and checks the
result and session journal; a provider error fails the run. t/42 runs
a real OpenAI API session, gated on OPENAI_API_KEY.
0.003 2026-04-21 01:46:09Z
- Switch bash tool from the retired MCP::Server::Run::Bash to
MCP::Run::Bash (same API, renamed upstream distribution).
- Dockerfile installs App::Raider from the dzil-built tarball
instead of the source tree (no Makefile.PL in Dist::Zilla dists).
- Docker Hub image published as raudssus/raider. dzil release now
runs run_after_release hooks that create the GitHub release,
build the runtime-root image, and push raudssus/raider:VERSION
plus :latest.
- Ship a cpanfile.snapshot for reproducible Carton deployments.
0.002 2026-04-20 20:28:50Z
- /model REPL command: show current engine and model, list available
models from the engine grouped by date-snapshot families (e.g.
gpt-4o [+3 snapshots]); /model list [FILTER] for the full or
substring-filtered list; /model NAME saves the chosen model to
.raider.yml under default.model.
- Model list colours: names in bright-blue, structural labels grey —
consistent with the rest of the banner.
- Fixed _build_skill_sources calling _normalize_skill_spec in scalar
context, silently dropping the CLAUDE.md spec from the claude
profile when loaded from .raider.yml ("seeing CLAUDE.md, ignoring"
even with profiles: claude active).
- Fixed Term::ReadLine::Gnu detection: require Term::ReadLine::Gnu
fails with "invalid to load directly"; now detected via
Term::ReadLine->new + ->ReadLine =~ /Gnu/.
- Readline history now persists across Ctrl-C / SIGTERM via signal
handlers that call WriteHistory before exit.
0.001 2026-04-20 17:44:05Z
- Initial release.
- CLI agent (App::Raider) wrapping Langertha::Raider with a default
viking persona ("Langertha"), caveman-style communication, and
effectively unlimited tool-calling iterations per raid.
- Tools: filesystem (list_files, read_file, write_file, edit_file),
bash (MCP::Server::Run::Bash), web_search and web_fetch
(Net::Async::WebSearch + Net::Async::HTTP).
- Engine auto-detection from available *_API_KEY env var with cheap
per-engine default models (claude-haiku-4-5, gpt-4o-mini, etc.).
- .raider.md for persona customization; hot-reload via /reload;
/prompt spawns a sub-agent prompt-builder that writes .raider.md.
- .raider.yml for engine options (temperature, response_size, ...)
with flat or default/<engine> layers; -o key=value CLI override.
- Skill / profile loading: --claude loads CLAUDE.md and
.claude/skills/*/SKILL.md; --openai/--codex loads AGENTS.md;
--skills DIR adds plain-markdown dirs. Choices persist to
.raider.yml with (saved) banner tag on first use.
- App::Raider::Skill generates self-describing how-to-use-raider
documentation from the live config, as plain markdown or as a
Claude Code SKILL.md with YAML frontmatter; usable via CLI
(--export-skill / --export-claude-skill) or REPL (/skill,
/skill-claude).
- REPL: Term::ReadLine::Gnu with persistent ~/.raider_history,
IO::Prompt::Tiny fallback; plain-ASCII output with blue/green
palette; slash commands /help /clear /metrics /stats /reload
/prompt /skill /skill-claude /quit.
- App::Raider::Plugin::Trace streams per-iteration tool calls,
args, results and cumulative token counts; meta-line after each
raid shows elapsed time, history size vs. context cap, and
cumulative tokens in / out / total.
- App::Raider::Plugin::Situation injects a compact single-line
situation block (local time, timezone, host, user) at the start
of each session.
- Auto-compression at 70% of a 40k-token context window to stay
under typical per-minute rate limits.
- Dockerfile with multi-stage build: runtime-root for root use,
runtime-user with RAIDER_UID/RAIDER_GID build args for host-uid
matching. README includes a shell-alias recipe that mounts $PWD
and forwards API-key env vars.
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
Go to GitHub issues (only if GitHub is preferred repository)