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.
