NAME

raider - Autonomous CLI agent with filesystem and bash access

VERSION

version 0.504

SYNOPSIS

raider "Find all TODO comments in lib/ and summarize them"
raider -e openai -m gpt-4o "Run the test suite and fix any trivial failures"
raider -i -r ~/dev/myproject
raider --perl --pack testing-fu "Run prove -l t and explain failures"
raider --json "Summarize README.md" > result.json
raider --stream-json "Run the tests" | jq -c 'select(.type == "tool.call")'
raider config explain -m gpt-4o
raider config migrate --dry-run
raider session list
raider --continue "Now fix the first one"
raider provider inspect provider.example --json
raider --provider provider.example -m example-model -k "$KEY" "Summarize README.md"
raider provider add acme provider.example --credential-env ACME_API_KEY
raider provider list
raider --provider acme "Summarize README.md"
raider hall start --acp-port 38421
raider acp prompt 127.0.0.1:38421 --raider bjorn "Summarize status"

DESCRIPTION

raider is the command line of Langertha::Raider::CLI: it runs Langertha::Raider::CLI::Main, which parses the options below and builds a Langertha::Raider::CLI for the working directory. That wires an LLM engine (via Langertha) to the file tools, bash (Langertha::Raider::BashTools) and the web tools, then runs the Langertha::Raider multi-turn agent loop on your prompt. Optional profile, pack, and Perl-native tool flags let the same CLI load project agent instructions, persona/power bundles, and perl_eval / perl_check / perl_cpanm tools.

Settings of a project live in its config file: .raider/config.yml in the working directory, or else the legacy .raider.yml there. Both take the same keys; where this page names .raider.yml, the file in use is meant. When both exist, only .raider/config.yml is read: raider warns on standard error that it ignores .raider.yml, and config explain names the file in use and the ignored one. What raider saves (/model, --claude, --openai, --skills) goes to the file in use, and to .raider.yml when there is none.

Under the project file lies the home file ~/.raider/config.yml, with the same keys: built-in defaults, then the home file, then the project file, then the command line. The project replaces a home value, except that skills and no_detect add up and detect: rules are replaced per pack. raider never writes the home file. Run in the home directory itself, ~/.raider/config.yml is the project file and is read once. A home file that does not parse stops raider like a broken project file.

project_tools in ~/.raider/config.yml maps a workspace selector (*, a path glob starting with / or ~/, or a workspace name) to the home tools a project gets. Only the home file grants: in a project file it is ignored, and config explain says so. For now config explain only shows which selectors match the project and which tools they grant; nothing is mounted from it yet, and workspace names never match. An invalid project_tools stops raider with exit status 3.

The project instructions follow the same rule: .raider/instructions.md in the working directory, or else the legacy .raider.md there, is added to the default persona. Where this page names .raider.md, the file in use is meant. When both exist, only .raider/instructions.md is read, raider warns that it ignores .raider.md, and config explain names the ignored file under instructions:. The prompt-builder (/prompt) writes the file in use, and .raider.md when there is none.

raider config migrate [--dry-run] [-r DIR] moves the legacy files of a project to the new layout: .raider.yml to .raider/config.yml and .raider.md to .raider/instructions.md, each only when it exists. It first shows what it does -- per file the new file, the backup and whether the content changes -- and with --dry-run stops there. Each new file is written atomically with the legacy file's permissions, then the legacy file is renamed to .raider.yml.bak or .raider.md.bak, so afterwards the same settings are read from the new files; .raider/ is created with its .gitignore. A step that fails leaves its legacy file in use. An api_key (top level, default: or an engine section) is not copied into .raider/config.yml, which is meant to be shared: it is reported with where it was, never printed, and belongs in ~/.raider/config.yml or the engine's *_API_KEY variable; its lines are dropped (comments stay), and where that would change the meaning of the file the file is rewritten from its parsed content, which drops comments and says so. Nothing is merged: when a new file or a backup already exists, when .raider is no directory, or in the home directory itself (where .raider/config.yml is the home file), nothing is written and the exit status is 1. Only config migrate in front of the options is the subcommand. Its report is for humans only; there is no --json form.

Interactive mode (-i) opens a REPL with conversation history retained across turns. With Term::ReadLine::Gnu installed, line editing and persistent input history (~/.raider_history) are enabled automatically — including across Ctrl-C interrupts. Slash commands available in the REPL: /help, /clear, /metrics, /stats, /reload, /prompt, /skill, /skill-claude, /config, /model, /packs, /pack, /quit.

Two more line prefixes run shell commands, with $SHELL -c (/bin/sh without SHELL) in the root (--root); Ctrl-C while one runs ends the command, not raider. !CMD runs CMD on the terminal (less and vim work), prints a non-zero exit status, and sends nothing to the model and records nothing in the session. ?CMD runs CMD with its output shown as it comes and captured (standard input is /dev/null), then sends the command, its exit status and its output -- standard output and standard error, cut to its first and last 10000 characters when longer than 20000 -- to the model as the next prompt, recorded in the session like any other. A ?CMD ended by Ctrl-C sends nothing. A line that is only ! or ? is a prompt.

Subcommands are dispatched before normal raider startup: raider hall ... manages a Raider Hall daemon, and raider acp ... runs the bundled ACP client. raider config explain [options] takes the normal options and prints every effective setting with its source -- a command-line flag, a layer of the config file, an environment variable or the built-in default -- without writing the config file or contacting a model; /config prints the same inside the REPL. The options may also come first (raider -e openai config explain); there only the exact words config explain are the subcommand, any other prompt starting with config is sent as a prompt. raider provider inspect HOST fetches, validates and shows a provider's manifest, and raider --provider HOST runs on the endpoint it declares; see "PROVIDERS".

With --json, --msgpack or --yaml, raider prints one document describing the run instead of the human-oriented output, which is useful for scripting; --stream-json, --stream-msgpack and --stream-yaml follow the run as it happens. See "MACHINE OUTPUT".

When standard input is not a terminal, the REPL (-i) reads its lines from it and ends at its end.

TOOLS

The model gets one tool set per run, and the tool list in its prompt is generated from exactly the tools the engine is offered:

  • list_files, read_file, write_file, edit_file, multi_edit (Langertha::Raider::FileTools), answering as Claude Code's Read, Write, Edit and MultiEdit (ADR 0023): read_file in cat -n form with offset / limit; an existing file must have been read in the run before it is written or edited. Confined to -r and the additional directories; in the Claude layout a path outside them is asked about instead (allow rules such as Read(//tmp/**) open it).

  • bash (Langertha::Raider::BashTools), a real shell started in -r, 120 seconds per command by default (at most 600); a command still running then goes on in the background, as does one started with run_in_background; bash_output reads it and kill_shell stops it. It is not confined to -r. A heavy command (prove, make test, dzil test, cpanm, podman run, ...) is refused with the reason while the host is under memory pressure or its heavy slots are taken (Langertha::Raider::Loadguard, the LOADGUARD_* variables of the loadguard plugin: LOADGUARD_PSI_FULL, LOADGUARD_SWAP_USED, LOADGUARD_HEAVY_SLOTS, LOADGUARD_THROTTLE=0 for off). LOADGUARD_CONFINE=1 starts raider in a memory-limited systemd user scope; off by default.

  • web_search, web_fetch (Langertha::Raider::WebTools).

  • glob, grep (Langertha::Raider::SearchTools), confined to -r; grep uses ripgrep when it is installed.

  • task_create, task_update, task_list, task_get (Langertha::Raider::TodoTools), the task list of the conversation -- todo_write, the task list of the run, instead with RAIDER_ENABLE_TASKS=0 -- and ask_user (Langertha::Raider::AskTools), a question to the person at the REPL -- refused in a run nobody can answer.

  • enter_plan_mode (Langertha::Raider::PlanTools), which switches to the plan permission mode once the person agrees, and exit_plan_mode once the run has been in that mode (see "PERMISSIONS").

  • perl_eval, perl_check, perl_cpanm (Langertha::Raider::PerlTools) when granted: by --perl, by perl: in .raider.yml (perl: false denies them), or else when an active pack requests them -- the bundled perl pack, detected in a Perl workspace. Switching such a pack with /pack or re-detecting it with /reload mounts or unmounts them for the next raid.

  • telegram_reply, hall_status, hall_spawn (Langertha::Raider::HallTools) only for a raider a Hall started (see "RAIDER_HALL_SOCKET").

raider config explain shows whether the Perl tools are mounted and why; --export-skill writes the same tool list as a table.

PERMISSIONS

Every tool call passes a permission policy as in Claude Code (ADR 0021, Langertha::Raider::Permissions). Without a matching rule the mode decides: default runs reading tools and asks before writing, bash, the network and messages; acceptEdits also writes files inside -r and the additional directories; auto (Claude Code's defaultMode: "auto") is that plus bash commands from a fixed read-only list (ls, git status, grep, ... where every part of the command is on it, no substitution, no redirection into a file); plan reads only, refuses changes even with an allow rule, and offers exit_plan_mode; bypassPermissions runs everything a deny rule does not name. The mode comes from --permission-mode (or --dangerously-skip-permissions), else permissions.default_mode of .raider/config.yml and ~/.raider/config.yml, else defaultMode of the Claude settings, else default.

Rules in Claude Code syntax (Bash(npm run test:*), Edit(src/**), Read(./.env), WebFetch(domain:example.com), mcp__server__tool) come from the Claude settings (in the Claude layout) and from permissions: (allow, ask, deny, default_mode, additional_directories) of the config files; deny before ask before allow, whatever the source.

In the REPL a call that asks is put to you: yes once, yes for this session (the rule it adds is shown), or no with a reason the model gets. With nobody to ask -- a one-shot run, a machine output flag, the Hall, ACP -- such a call is refused, and the model is told which rule or mode would allow it. raider config explain and /permissions show the policy.

OPTIONS

-e, --engine NAME

The engine: anthropic, openai, deepseek, groq, mistral, gemini, minimax, cerebras, openrouter or ollama. Without it, -o engine=, then engine: in .raider.yml, then the first API key set in the environment (anthropic, openai, deepseek, groq, mistral, gemini, in that order; see "ENVIRONMENT") decide, else anthropic.

-m, --model NAME

The model. Without it, -o model=, then model: in .raider.yml, then a cheap per-engine default (claude-haiku-4-5, gpt-4o-mini, ...), else the engine's own default. openrouter needs one.

-k, --api-key KEY

The API key, over api_key: in .raider.yml and the engine's environment variable. With --provider the only source of the key, together with -o api_key=.

--provider HOST[:PORT] | https://HOST[:PORT] | ALIAS

Runs on the model endpoint a provider's manifest declares, for this one run; see "Using a provider". A name without dot, port or https:// is always an alias ("Stored aliases"); one that is not stored is a usage error and nothing is fetched. Not with -e, -o engine=, -o url= or config explain.

--provider-endpoint ID

With --provider: the manifest endpoint to run on, when the model is offered on several; see "Several endpoints and failures".

--allow-internal

With --provider (and provider inspect): releases loopback, private, link-local and reserved provider addresses; see "PROVIDERS".

-o, --option KEY=VALUE

An engine attribute (repeatable), e.g. -o temperature=0.2 -o response_size=4096, merged over .raider.yml; the command line wins. Integers, decimals and true / false become numbers. raider's own .raider.yml keys (engine, perl, packs=a,b, skills=a,b, detect, no_detect=a,b, preferred_lib_target, web_fetch_model) configure raider instead and never reach the engine.

-r, --root DIR

The working directory, default the current one. The file tools are confined to it, bash starts in it, and .raider/config.yml (or .raider.yml), .raider/instructions.md (or .raider.md) and .raider/ are read from it.

-M, --mission TEXT

The instructions: replaces the default persona and .raider.md for this run. Skills, packs, the Claude memory and the tool list still apply.

--bare

An isolated context: no .raider.md, no Claude Code layout, no skills, no packs:, no default packs, no detection. --pack NAME and /pack NAME still work.

-i, --interactive

The REPL. It is the default when standard input is a terminal and no prompt is given; -i also forces it otherwise.

--json[=N], --msgpack[=N], --yaml[=N]

Print one document for the run, see "MACHINE OUTPUT".

--stream-json[=N], --stream-msgpack[=N], --stream-yaml[=N]

Print the run as events while it happens, ending with the document; see "Streams".

--no-session

Record nothing; see "SESSIONS".

--session ID

Continue session ID: its conversation is replayed and the run (or the REPL) is recorded in it.

--continue

The same with the newest session of the project.

--max-iterations N

Hard cap on tool rounds per raid. Default 10000, effectively unlimited.

--no-color

No ANSI colors, as with ANSI_COLORS_DISABLED.

--trace, --no-trace

Show or hide the live progress of tool calls. On by default when standard output is a terminal; off with a machine output flag, where --trace sends it to standard error.

--perl

Mount the Perl tools (perl_eval, perl_check, perl_cpanm), see "TOOLS".

--pack NAME

Enable a pack (repeatable). Given at all, the listed packs replace packs: of .raider.yml and the default packs; detection still adds.

--no-pack NAME

Switch a pack off (repeatable), also a default or detected one.

--no-detect, --detect

Switch pack detection off, or on over detect: false in .raider.yml.

--claude

Read the Claude Code layout also in a raider project (one with .raider/): the CLAUDE.md hierarchy (user, repository top down to the working directory, CLAUDE.local.md, @imports, nested files on first touch), .claude/settings*.json (its env is set for the tools), the skills of ~/.claude/skills, .claude/skills, .agents/skills and of the enabled plugins. Saved as claude: true in .raider.yml on first use (the older skills: [claude] means the same). A project with only Claude Code files reads that layout without the flag, and nothing is saved there.

--openai, --codex

Load AGENTS.md as instructions. Saved to skills: in .raider.yml on first use.

--skills DIR

Offer the skills in DIR (repeatable): every */SKILL.md and every *.md in it. The prompt lists them by name and description; the model loads one with the skill tool. Saved like --openai.

--permission-mode MODE

The permission mode of the run: default, acceptEdits, auto, plan or bypassPermissions (see "PERMISSIONS"). It wins over the mode of the config files and the Claude settings. Any other name is a usage error.

--permission-channel PATH

Not for people: the hall (raider hall) sets it on a run that belongs to an ACP session. PATH is a Unix socket on which a tool call the permission policy asks about is put to the hall, which asks the ACP client with session/request_permission (ADR 0021). No answer, a closed socket, a cancelled run or any error is no; it never allows by itself. Without the option (and without a terminal) such a call is refused. Not shown by --help; ignored in the REPL (-i).

--dangerously-skip-permissions

The same as --permission-mode bypassPermissions: every tool call runs unless a deny rule names it. Not together with --permission-mode.

--customize-prompt

Start the REPL with the prompt-builder, which writes .raider.md with you (/prompt in the REPL).

--export-skill [PATH]

Write a plain-markdown "how to use raider" document for the current configuration (default RAIDER-SKILL.md in -r) and exit.

--export-claude-skill [PATH]

Write the same as a Claude Code SKILL.md with frontmatter (default .claude/skills/raider/SKILL.md in -r) and exit.

--version

Print the version of raider (Langertha::Raider) and of Langertha core on one line, raider VERSION (Langertha VERSION), and exit.

-h, --help

Print the option summary and exit.

MACHINE OUTPUT

--json, --msgpack and --yaml print one document for the run, once, when it has ended. --stream-json, --stream-msgpack and --stream-yaml print events while the run happens, the last of which carries that same document (see "Streams"). Nothing else goes to standard output: the live trace is off unless --trace asks for it, and then goes to standard error like every diagnostic. The six flags exclude each other; giving two is a usage error. A usage or configuration error (exit status 2 or 3) prints no document and no events, only its message on standard error.

The flags write the same model in three encodings:

--json -- a pretty-printed JSON object with sorted keys, UTF-8. --stream-json writes JSON Lines: one compact object per event and line.
--msgpack -- one MessagePack map; text is written as UTF-8 str, never bin. Standard output is binary. --stream-msgpack writes one map per event, back to back (MessagePack objects delimit themselves).
--yaml -- one YAML document, starting with ---, UTF-8. --stream-yaml writes one YAML document per event, each starting with ---.

Every field and type is the same in all three encodings.

The document

A run that completed:

{
   "elapsed" : 2.731,
   "metrics" : { "iterations" : 2, "raids" : 1, "time_ms" : 2710.4, "tool_calls" : 1 },
   "response" : "The README describes ...",
   "status" : "completed",
   "version" : 1
}

A run that failed:

{ "elapsed" : 0.42, "error" : "...", "reason" : "engine_error", "status" : "failed", "version" : 1 }

A run cancelled by SIGINT (Ctrl-C; see "EXIT STATUS"):

{ "elapsed" : 3.912, "reason" : "signal", "status" : "cancelled", "version" : 1 }

A run stopped by SIGTERM, by a second SIGINT while it is being cancelled, or by either signal before it started:

{ "elapsed" : 5.107, "reason" : "signal", "signal" : "TERM", "status" : "interrupted", "version" : 1 }
version -- integer, the format version (see below).
status -- how the run ended: completed, failed, cancelled or interrupted. These are the run states of the redesign (ADR 0009) that end a run and that the CLI can produce today; refused is reserved for when it can.
response -- the agent's final answer (completed only).
metrics -- the raider's cumulative metrics: raids, iterations, tool_calls, time_ms, and usage -- the token counts of every model call so far, subagents and web_fetch prompts included: input_tokens, output_tokens, cache_read_tokens, cache_write_tokens (both cache counts are part of the input), calls, and the same per model under models (completed only; ADR 0026).
reason -- a short code for why the run ended, whenever status is not completed (ADR 0034); error stays the text for a person. New codes may come within a version, so a consumer must treat one it does not know like error:
max_iterations -- the raid used up --max-iterations (failed).
engine_error -- a model request failed: the provider could not be reached, answered with an error or with something raider cannot read (failed).
hook_blocked -- a UserPromptSubmit hook blocked the prompt (failed).
hook_stopped -- a hook answered continue: false: a SessionStart or UserPromptSubmit hook (failed), a tool hook (cancelled).
run_setup -- the run could not be set up, for example the engine for the model of a /name command (failed).
provider -- the --provider could not be activated (failed or refused).
user_cancel -- cancelled by the person or the client: the REPL's Ctrl-C, an ACP session/cancel (cancelled).
signal -- ended by a signal: the first SIGINT of a one-shot run (cancelled), SIGTERM or a second SIGINT (interrupted, see signal).
error -- any other failure (failed).
refused -- a refused document that names no other code.
error -- the error message (failed only).
signal -- the signal that stopped the run, INT or TERM (interrupted only).
elapsed -- seconds the run took, to the millisecond.
session -- the session the run is recorded in (see "SESSIONS"): id and path of its journal. Missing with --no-session and for a signal during startup.

Streams

A stream is a sequence of events, each flushed as soon as it happens. Every event has these fields:

version -- the format version, as in the document.
type -- what happened (below).
seq -- 1, 2, 3, ... within the run.
time -- when, in epoch seconds with fractions.

The types of version 1, with the fields each adds:

run.started -- the run begins: engine and, when there is one, model; session (the session id) and run (the run id within it, r1, r2, ...), both missing with --no-session; cwd, the working directory (inside a worktree, the worktree); permission_mode; tools, the names of the tools the model is offered, in the order it gets them -- missing while a configured MCP server has not connected yet, since those start with the run's first model request (see mcp.server); and raider_version. A run interrupted during startup (reading the configuration or the prompt) has none: its stream is the interrupted state change and run.finished.
run.state -- state: running when the run starts, then the status it ends with (see "The document").
tool.call -- name and arguments of a tool call, before it runs; call, its id within the run (c1, c2, ...); status, dispatched.
tool.result -- call and name of the call; status (succeeded, failed when the tool reported an error, or cancelled for a call a cancelled run cut off); ok (the same as a boolean); size, the length of the result text in characters; content, its first 1000 characters; and truncated (a boolean, true when content was cut). The cut limits how much of a tool's output reaches the stream, but it is no filter: a secret in a tool's arguments or output can still show up.
message -- a finished assistant message: role (assistant) and content. The final answer of the run; also an answer a Stop hook sent back with feedback (the run goes on), and an answer after which the run goes on with the notifications of its background agents. The person's own input is never a message of the stream.
model.usage -- after every model request that reports tokens, and after a tool's own model call: purpose (chat, or web_fetch), model, input_tokens, output_tokens and, when the provider reports them, cache_read_tokens and cache_write_tokens (both part of input_tokens).
todo.updated -- the model changed its task list: todos, the whole list, each item with content, status and the fields of its tool (see "TOOLS").
permission.asked -- the policy asks about a tool call (see "PERMISSIONS"): call, tool, tool_source, mode, reason, the rule and its source when a rule asks, suggested_rule when one would allow it. Also when exit_plan_mode puts the plan to the person (reason review of the plan).
permission.granted -- the person allowed the call: call, tool, scope (once, or session with the rule added), mode.
permission.denied -- the call does not run: call, tool, tool_source, mode, reason, rule and source of a deny rule, and by -- who refused it: rule, mode, hook, worktree, user, or noninteractive when nobody could be asked.
permission.mode -- the mode changed: from, to, why (the command or tool that changed it, such as enter_plan_mode or /permissions), and call when a tool call changed it.
hook.ran -- one hook of the project's Claude Code hooks ran or was skipped: hook_event (PreToolUse, Stop, ...), source (the settings file or plugin:NAME), command, outcome (success, block, error, timeout, skipped, running for an async hook, cancelled), and when known plugin, matcher, args, exit, duration_ms, message, stdout and stderr (each cut at 2000 characters). A tool hook (PreToolUse, PostToolUse, PostToolUseFailure) carries the call it ran for.
hook.message -- what hooks say to the person: hook_event, kind (system for a systemMessage, error, stopped for a continue: false) and text.
mcp.server -- a configured MCP server changed its state: name, scope, transport, status (starting, connected, failed, stopped), reason of a failure, tools (how many tools it offers). The servers start with the run's first model request.
mcp.tools_changed -- a server announced a new tool list: name, scope. The next model request offers the new tools.
agent.started -- the agent tool started a subagent: call (the agent call), agent, agent_id, background, description, session (its own journal), model, and resumed for a continued agent.
agent.finished -- the subagent ended: call, agent, agent_id, session, status.
agent.warning -- a subagent starts with a problem worth a look, such as a briefing over 64 kB of skill text: call, agent, message. Before its agent.started.
worktree.entered -- the session moved into a git worktree: path, branch, created (whether enter_worktree created it).
worktree.exited -- it left it: path, branch, removed, reason (exit_worktree, clear, session_end).
run.finished -- always the last event, also when the run failed. Its fields are exactly the document of "The document"; a consumer that only wants the result reads the last event.

call is the id of a tool call within the raid (c1, c2, ...): the events of one call share it, also when calls run at the same time.

A run that calls one tool, as --stream-json:

{"cwd":"/src/app","engine":"openai","model":"gpt-4o-mini","permission_mode":"default","raider_version":"0.504","run":"r1","seq":1,"session":"20261025-091500-3f2a","time":1790000000.1,"tools":["list_files","read_file","bash"],"type":"run.started","version":1}
{"seq":2,"state":"running","time":1790000000.1,"type":"run.state","version":1}
{"arguments":{"command":"ls"},"call":"c1","name":"bash","seq":3,"status":"dispatched","time":1790000001.2,"type":"tool.call","version":1}
{"call":"c1","content":"...","name":"bash","ok":true,"seq":4,"size":42,"status":"succeeded","time":1790000001.3,"truncated":false,"type":"tool.result","version":1}
{"input_tokens":1200,"model":"gpt-4o-mini","output_tokens":40,"purpose":"chat","seq":5,"time":1790000002.4,"type":"model.usage","version":1}
{"content":"There are ...","role":"assistant","seq":6,"time":1790000002.4,"type":"message","version":1}
{"seq":7,"state":"completed","time":1790000002.4,"type":"run.state","version":1}
{"elapsed":2.312,"metrics":{...},"response":"There are ...","seq":8,"session":{...},"status":"completed","time":1790000002.4,"type":"run.finished","version":1}

Token deltas are not part of version 1. New event types may appear within a version, so a consumer must skip types it does not know.

Versions

--json=N (--msgpack=N, --yaml=N, and the --stream-* flags) asks for format version N; the version belongs to the model, so it means the same in every encoding and for documents and streams alike. The only version is 1, which is also what the plain flags write; any other N is a usage error. Only the =N form carries a version -- in raider --json 2 ... the 2 is part of the prompt.

Within a version, fields are only ever added, never renamed, removed or changed in meaning, so a consumer must ignore fields it does not know. A breaking change is a new version, chosen with --json=2; the plain flag stays on the old version for at least one release after the new one ships.

SESSIONS

Every run is recorded in a session journal (ADR 0015): .raider/sessions/<id>.jsonl under the working directory (-r), one JSON object per line. A one-shot run starts a new session and names it on standard error (with a machine output flag, in the document's session instead); the REPL starts one with its first prompt and records every prompt of it there. --no-session records nothing.

Creating .raider/ also writes .raider/.gitignore excluding sessions/ and lib/, unless that file exists: a journal holds the whole output of every tool, so it is not committed by default. API keys never enter it.

A run is recorded as run.started, the input as message, every tool.call and tool.result (with the whole result text, where the stream cuts it), the answer as message, and run.finished with how it ended -- also when it failed or was interrupted. The permission policy's decisions are recorded as permission.asked, permission.granted, permission.denied and permission.mode. A /clear in the REPL is recorded as history.cleared. While a raider writes a session it holds a lock on it; a second writer fails at once instead of waiting. A journal that cannot be written in the middle of a run (a full disk) is a warning on standard error; the run goes on.

Wherever a command takes a session ID, a shorter form names it too: a unique start of the id (20260925-08) or its four hex digits at the end (3f2a). A form that fits more than one session is refused with the candidates listed.

raider session list                  # the project's sessions, newest first
raider session show ID               # one session, event by event
raider session show ID --json        # the whole journal as one document
raider session resume ID             # the REPL, continuing session ID
raider --session ID "and now ..."    # one more run in session ID
raider --continue "and now ..."      # the same in the newest session
raider -i --continue                 # the REPL on the newest session
raider session fork ID               # a new session with ID's history
raider session rm ID                 # delete session ID

session list shows id, number of runs, the state of the last run and the first prompt of each session; session show prints every event, then what the crash rules found. Both read without the lock, take -r for the project and a document format (--json, --msgpack, --yaml), as do session fork and session rm. As for config explain, the options may come first; there only session list and session show|resume|fork|rm followed by a session id are the subcommand, any other prompt starting with session is sent as a prompt.

session fork ID starts a new session in the same project whose session.created names the original in forked_from, and copies what a resume would replay as the conversation into it, as message events outside any run. The original is only read, not locked or changed; the fork has its own journal from then on and is continued like any session (session resume, --session). session rm ID deletes the journal and its lock file; it takes the lock first, so a session another raider has open is refused with exit status 4. Raider never deletes a session on its own.

Continuing a session (--session ID, --continue, session resume) takes its lock, replays the conversation -- the input and answer of every run that got an answer, and all recorded events into the full session history; after a history.cleared only what came later -- and records the new runs in the same journal. Nothing recorded is executed again. It reports what it found on standard error (in the REPL, under the banner): lines that could not be read, runs that never ended (they count as interrupted), and tool calls without a result, whose outcome is unknown. The context (.raider.md, packs, configuration) is built fresh, not replayed. A session another raider has open ends raider with exit status 4.

PROVIDERS

raider provider inspect HOST[:PORT] | https://HOST[:PORT] [--json] [--allow-internal]
raider provider add ALIAS HOST[:PORT] | https://HOST[:PORT] [--credential-env NAME] [--allow-internal]
raider provider list [--json]
raider provider show ALIAS [--json]
raider provider remove ALIAS [--yes]

provider inspect fetches https://HOST[:PORT]/.well-known/langertha.json, the provider manifest of ADR 0007, validates it with Langertha::Manifest and shows what it declares: the provider id, the issuer, every endpoint (id, dialect, base URL, auth reference), every auth entry (id, type) and every model (id, endpoint, the capabilities it claims). Warnings follow for what this raider cannot use -- an endpoint dialect it has no adapter for, an auth type it cannot supply, a capability name Langertha does not know (treated as absent) -- and for an issuer that is not the origin the manifest came from or an endpoint on plain http or an internal address. extensions are shown as inert: kept as published, never interpreted, loaded or run. A manifest with a command-, code-, secret- or prompt-shaped field (command, exec, api_key, system_prompt, ...), an unknown field or another schema version is not valid.

Inspecting stores nothing, binds no credential and trusts nothing: the manifest states what the provider claims. The fetch sends no API key, no Authorization header and no cookie, and is bounded:

  • https only; a target with userinfo, a query, a fragment or another path than the well-known one is a usage error. Without https:// a host needs a dot (host.tld), a port (host:PORT) or brackets (an IPv6 literal such as [::1], with or without a port); a bare name such as acme or localhost is a provider alias, not a host (localhost:8443 or https://localhost is one).

  • at most 1 MiB of body, 10 seconds from resolving the host to the last byte, and 3 redirects, each within the origin (scheme, host and port). A redirect to another origin is not followed; it is reported with its target and the manifest counts as refused.

  • the host is resolved once and every address it resolves to is checked; the request goes to a checked address, never to a second lookup of the name, while TLS still verifies the certificate against the name. Loopback, private (RFC 1918, shared address space, IPv6 unique local), link-local and reserved addresses are refused, unless --allow-internal releases them. Cloud metadata addresses (169.254.169.254, fd00:ec2::254, ...), the unspecified address, multicast and 240.0.0.0/4 are refused always. A host with one refused address among its addresses is refused.

--allow-internal releases loopback, private, link-local and reserved target addresses for this one inspection -- for a knarr or skeid you deliberately run on an internal address. Nothing remembers it.

Using a provider

raider --provider HOST[:PORT] | https://HOST[:PORT] [-m MODEL] [-k KEY] [--allow-internal] [options] [prompt...]

fetches and validates the manifest as provider inspect does (same limits and address rules) and runs on the endpoint of one of its models: one-shot, machine output and the REPL work as with -e. Nothing is stored and nothing is remembered: naming the host on the command line is the whole release, as with -o url=. A name without dot, port or https:// is an alias (see "Stored aliases"), never a host. The rules are provisional:

  • The model. -m (or -o model=) must be a model id of the manifest. Without it the manifest must list exactly one model id; several are a usage error that lists them. model: in .raider.yml does not apply. A model listed on several endpoints runs on the one whose dialect raider has an adapter for; with more than one such endpoint raider does not choose and the run is refused.

  • The engine follows from the endpoint's dialect: openai-chat runs as openai, anthropic as anthropic, gemini as gemini, ollama as ollama (native API), responses, perplexity-agent, anthropic-compat and aki on their own Langertha engines -- anthropic-compat without native structured output, through a synthetic tool and a forced tool choice instead (ADR 0007). The engine's url is the endpoint's base_url, whatever .raider.yml says; the other engine options of .raider.yml and -o apply as with -e. A dialect this raider has no adapter for is an error, and so is lmstudio: Langertha's native LM Studio engine has no tool calling.

  • The key comes only from -k or -o api_key=. Neither api_key: in .raider.yml nor any environment variable is used: a key for another provider never reaches this one. An endpoint with an auth reference needs a key (a usage error without one); an auth type other than api_key is an error. An endpoint without auth gets no key.

  • The origin. The endpoint's base_url must be https and of the same origin (scheme, host, port) as the manifest; anything else is refused, so the key goes nowhere but the origin named on the command line. The endpoint's host is resolved and checked again before the run, under the rules of provider inspect (--allow-internal releases it the same way). The engine then connects to the first of the checked addresses, never to a new lookup of the name (TLS still verifies the certificate against the host name), so a DNS answer that changes after the check cannot send the key elsewhere. A redirect to another host is never followed; the model requests are POSTs, which are not redirected at all, and the engine's synchronous requests (the REPL's /model) follow no redirect.

  • A model that does not declare tools_native is a warning on stderr, not an error: raider works through tool calls.

Several endpoints and failures

A model that the manifest lists on several endpoints raider can use is refused, not guessed: the error names the endpoints and --provider-endpoint ID chooses one (also for an alias).

Failures are reported on stderr before anything runs: a usage error exits with 2, a provider that cannot be used as it is (refused, not fetched, not valid, an unknown dialect or auth type) with 1. With a machine output flag the same failure is also one document on stdout, status refused (an address, redirect, origin or stored binding the rules do not allow) or failed, with error and the provider as given; the exit status is the same with and without the flag.

Stored aliases

raider provider add ALIAS HOST[:PORT] | https://HOST[:PORT] [--credential-env NAME] [--allow-internal]
raider --provider ALIAS [-m MODEL] [-k KEY] [options] [prompt...]

provider add fetches and validates the manifest as provider inspect does, shows its endpoints and auth and what will be pinned, and asks [y/N] on a terminal. Anything but y stores nothing, and without a terminal (stdin or stdout not a TTY) nothing is fetched or stored at all. The binding goes under providers: in ~/.raider/config.yml through the one home-file writer (a providers: in a project file is ignored):

providers:
  acme:
    origin: https://provider.example:443
    issuer: https://provider.example
    auth: [api_key]
    credential_env: ACME_API_KEY
    allow_internal: 0
    pinned_at: 2026-10-15T09:30:00Z
    fingerprint: 3f2a...

fingerprint is a field beyond the origin, issuer and auth types: the digest of what was accepted (below), which is what --provider ALIAS compares, so an endpoint that moved is seen too.

  • The credential is the name of an environment variable (--credential-env), required when the endpoints ask for auth and never guessed; its value is read at run time and goes to the pinned origin only. -k overrides it for one run. No other *_API_KEY ever selects or feeds an alias. The name is pinned too: another variable (edited by hand, or given to provider add again) is another secret sent to the same origin, so it needs the renewed acceptance of provider add; until then --provider ALIAS stops.

  • What is pinned is the origin, the issuer and the auth types, the credential variable and allow_internal, and through the fingerprint every endpoint's origin and dialect and the provider id (no TLS pin). A change of the model list, capabilities or an endpoint path needs nothing. Anything pinned that changed -- or an entry edited by hand, or without a fingerprint -- stops --provider ALIAS (exit 1, or 3 for an entry that is not well-formed), naming what differs, until provider add is run again and accepted.

  • --allow-internal belongs to the alias: given to add it is stored and covered by the pin; it is a usage error next to a stored alias.

  • The name of an alias is lowercase letters, digits, - and _, at most 32: it can never look like a host. A --provider value without dot, port or https:// is always an alias; one that is not stored is a usage error (exit 2; with --json the failed document) that points to provider add, and nothing is fetched -- a typo in an alias never turns into a network request. Any other --provider value is a HOST.

  • provider list and provider show ALIAS print what is stored (origin, issuer, auth types, the name of the credential variable, whether an internal address is allowed, when it was accepted; show also the fingerprint) and fetch nothing; an entry that cannot be used says why. --json gives one document (providers, or provider). The aliases are also in raider config explain and the REPL banner.

  • provider remove ALIAS deletes the entry through the same home-file writer, after asking on a terminal. Without a terminal it needs --yes and is an error otherwise; nothing is removed then.

The provider document

--json, --msgpack and --yaml write one document (no stream):

{
   "address" : "93.184.216.34",
   "elapsed" : 0.214,
   "final_url" : "https://provider.example/.well-known/langertha.json",
   "manifest" : { "schema_version" : 1, "kind" : "langertha-provider", ... },
   "notes" : [],
   "redirects" : [],
   "status" : "completed",
   "url" : "https://provider.example/.well-known/langertha.json",
   "version" : 1,
   "warnings" : []
}

status is completed for a valid manifest, refused for a target address or redirect the rules above do not allow, and failed for anything else: the network, TLS, an HTTP status, a limit, invalid JSON or an invalid manifest. manifest is the validated manifest as "to_hash" in Langertha::Manifest writes it; warnings and notes are one sentence each; url is the manifest URL, final_url the one the body came from after redirects, address the address connected to. refused and failed carry error instead of manifest, and a redirect that was not followed its target in location. The version and compatibility rules are those of "Versions".

EXIT STATUS

0 -- success, including --help, --version, the exports, config explain, config migrate (also with nothing to migrate, and --dry-run), session list, session show, session fork, session rm, provider inspect of a valid manifest, provider add that stored its binding, provider list, provider show and provider remove that removed its alias, and leaving the REPL.
1 -- the run failed: the engine, a tool or the network raised an error. With a machine output flag, the document says failed. For config migrate: the migration was refused, also with --dry-run, or a step failed. For provider inspect: the manifest was refused, could not be fetched or is not valid (the document says refused or failed). For --provider also: its endpoint cannot be used (another origin, not https, a refused address, a dialect or auth type this raider has no adapter for, no models, a model on several endpoints) or a stored alias no longer matches what was accepted; see "Using a provider" and "Stored aliases". For provider add: no terminal, an answer other than y, a manifest that was refused, not fetched or not valid. For provider remove: no terminal and no --yes, or an answer other than y.
2 -- usage error: unknown option, a -o that is not KEY=VALUE, an unknown config subcommand, no prompt, more than one machine output flag, a machine output flag with -i, or an unknown format version; for config migrate a word after it or a machine output flag; for provider inspect also no target or more than one, a target that is no host or https origin, or a --stream-* flag; for --provider a target that is none, -e, -o engine=, -o url= or config explain with it, a model the manifest does not list, no -m where it lists several, or no -k (nor the alias's variable) where the endpoint needs a key; for provider add not two words, an alias or variable name that is none, auth asked for without --credential-env or a document format; for provider show and provider remove not one word or an alias that is not stored; and --allow-internal or --provider-endpoint without --provider, or --allow-internal with a stored alias.
3 -- configuration error: the config file (project or home; for config migrate the .raider.yml to migrate) cannot be read, the engine is unknown, or a pack detection rule is invalid.
4 -- the session to continue (--session, --continue, session resume) or to remove (session rm) is in use by another raider. A usage error (2) is also an unknown or ambiguous session, a --session that is no session id, --continue without any session, or more than one of --session, --continue and --no-session.
130, 143 -- a one-shot run was cancelled by SIGINT or interrupted by SIGTERM. On SIGINT (Ctrl-C) raider cancels the run: it ends the tool commands still running, abandons a model request in flight, lets the run end at its next safe point and writes its document -- cancelled, or what the run reached if it ended anyway -- or its events, or a note. A second SIGINT while it cancels, a SIGTERM, and with a machine format either signal during startup end the tool commands and write the interrupted document at once. Either way raider then dies of that same signal, so a shell reports 128 plus the signal number and a parent that waits for it sees a process killed by the signal. A model or embedding request that blocks the process (not an asynchronous one) holds a cancel up until it returns; the second SIGINT does not wait.

The hall and acp subcommands keep their own exit statuses.

ENVIRONMENT

API keys come from the environment variables below only. Claude Code subscription/OAuth credentials are not consumed by this CLI; Anthropic usage goes through the API key path.

ANTHROPIC_API_KEY

The key of the anthropic engine. Set, it also makes anthropic the engine when nothing names one; so do the next five, in this order.

OPENAI_API_KEY

The key of openai.

DEEPSEEK_API_KEY

The key of deepseek.

GROQ_API_KEY

The key of groq.

MISTRAL_API_KEY

The key of mistral.

GEMINI_API_KEY

The key of gemini.

MINIMAX_API_KEY

The key of minimax. This one and the next two are only read for their engine; they never pick it.

CEREBRAS_API_KEY

The key of cerebras.

OPENROUTER_API_KEY

The key of openrouter.

BRAVE_API_KEY

Adds Brave to web_search, next to the keyless DuckDuckGo.

SERPER_API_KEY

Adds Serper to web_search.

GOOGLE_API_KEY

With "GOOGLE_CSE_ID", adds Google Custom Search to web_search.

GOOGLE_CSE_ID

The Custom Search engine id for "GOOGLE_API_KEY".

RAIDER_PACK_DIRS

Colon-separated directories searched for packs after the project's .raider/packs/, ~/.raider/packs/ and the bundled ones; see "build_packs" in Langertha::Raider::Packs.

RAIDER_ENABLE_TASKS

0 (or false, no, off) mounts todo_write instead of the task tools task_create, task_update, task_list and task_get, like Claude Code's CLAUDE_CODE_ENABLE_TASKS; see "task_tools" in Langertha::Raider::Application.

HOME

Where ~/.raider/packs/ and the REPL history ~/.raider_history are.

ANSI_COLORS_DISABLED

Set, the output and the trace have no colors (--no-color and the machine output flags set it themselves).

RAIDER_HALL_SOCKET

The control socket of the Hall that started this raider. When it names a socket, the Hall tools telegram_reply, hall_status and hall_spawn are mounted; the Hall sets it, together with RAIDER_HALL_TELEGRAM_BOT, RAIDER_HALL_TELEGRAM_CHAT_ID and RAIDER_HALL_TELEGRAM_THREAD_ID for a Telegram mission (see Langertha::Raider::HallTools).

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/