WeaveScope

Prebuilt Middleware

BeamWeaver ships common agent middleware as Elixir modules under BeamWeaver.Agent.Middleware. Middleware entries can be modules, structs, or {module, opts} tuples:

defmodule MyApp.SupportAgent do
  use BeamWeaver.Agent

  alias BeamWeaver.Agent.Middleware

  model BeamWeaver.Models.init_chat_model!("openai:gpt-5.4-mini")

  tools do
    tool MyApp.Tools.SearchDocs
  end

  middleware do
    use Middleware.ModelRetry, max_retries: 2, initial_delay: 100
    use Middleware.ToolSelection, deny: ["internal_admin_tool"]
    use Middleware.PII, detectors: [:email], strategy: :redact
  end
end

Runtime-built agents use the same values:

{:ok, agent} =
  BeamWeaver.Agent.build(
    model: BeamWeaver.Models.init_chat_model!("openai:gpt-5.4-mini"),
    tools: [MyApp.Tools.SearchDocs],
    middleware: [
      {BeamWeaver.Agent.Middleware.ToolCallLimit, run_limit: 8}
    ]
  )

Available Middleware

Need BeamWeaver path
Dynamic system prompts BeamWeaver.Agent.Middleware.DynamicPrompt
Tool filtering or model-selected tools BeamWeaver.Agent.Middleware.ToolSelection
Model retries BeamWeaver.Agent.Middleware.ModelRetry
Tool retries BeamWeaver.Agent.Middleware.ToolRetry
Model fallbacks BeamWeaver.Agent.Middleware.ModelFallback
Model call limits BeamWeaver.Agent.Middleware.ModelCallLimit
Tool call limits BeamWeaver.Agent.Middleware.ToolCallLimit
Conversation summarization BeamWeaver.Agent.Middleware.Summarization
Structured-output retry feedback BeamWeaver.Agent.Middleware.StructuredOutputRetry
Human review before tools BeamWeaver.Agent.Middleware.HumanInTheLoop
Context editing BeamWeaver.Agent.Middleware.ContextEditing
PII detection and editing BeamWeaver.Agent.Middleware.PII
TODO planning BeamWeaver.Agent.Middleware.TodoList
Virtual filesystem tools BeamWeaver.Agent.Middleware.Filesystem
Progressive-disclosure skills BeamWeaver.Agent.Middleware.Skills
AGENTS.md memory files BeamWeaver.Agent.Middleware.Memory
Deep Agents subagent task tool BeamWeaver.Agent.Middleware.Subagents
Captured subagent output state channels BeamWeaver.Agent.Middleware.SubagentOutputs
Remote async subagent tools BeamWeaver.Agent.Middleware.AsyncSubagents
Manual conversation compaction BeamWeaver.Agent.Middleware.CompactConversation
Context-overflow recovery BeamWeaver.Agent.Middleware.OverflowRecovery
Provider-aware prompt caching BeamWeaver.Agent.Middleware.PromptCaching
Policy-governed shell session BeamWeaver.Agent.Middleware.ShellTool
Tool-result emulation for tests BeamWeaver.Agent.Middleware.ToolEmulator

Summarization

BeamWeaver.Agent.Middleware.Summarization summarizes older messages before a model call and keeps recent context in the agent state.

middleware do
  use BeamWeaver.Agent.Middleware.Summarization,
   model: BeamWeaver.Models.init_chat_model!("openai:gpt-5.4-mini"),
   trigger: {:tokens, 4_000},
   keep: {:messages, 20}
end

Useful options:

Option Meaning
:model Required chat model used to create summaries.
:trigger {:messages, n}, {:tokens, n}, {:fraction, f}, {:all, triggers}, {:any, triggers}, a list of triggers, or nil.
:keep {:messages, n}, {:tokens, n}, {:fraction, f}, or nil.
:token_counter :approximate, a counting function, or a model/counter accepted by BeamWeaver.Core.LanguageModel.count_tokens/2.
:summary_prompt Prompt template containing {messages}.
:summary_prefix Prefix for the replacement system message.
:trim_tokens_to_summarize Maximum summary-input size before the summary model is called.
middleware do
  use BeamWeaver.Agent.Middleware.Summarization,
   model: summary_model,
   trigger: {:all, [{:tokens, 3_000}, {:messages, 12}]},
   keep: {:tokens, 1_500},
   summary_prompt: "Summarize the relevant facts:\n\n{messages}"
end

Plain trigger lists are treated as OR conditions. Use {:all, triggers} for AND behavior when both token pressure and message count should be true before a summary is inserted.

Human-In-The-Loop

BeamWeaver.Agent.Middleware.HumanInTheLoop interrupts after the model emits configured tool calls and before those tools execute. The interrupted run can be resumed with approve, edit, reject, or respond decisions.

defmodule MyApp.ReviewedAgent do
  use BeamWeaver.Agent

  alias BeamWeaver.Agent.Middleware.HumanInTheLoop

  model BeamWeaver.Models.init_chat_model!("openai:gpt-5.4")

  tools do
    tool MyApp.Tools.ReadEmail
    tool MyApp.Tools.SendEmail
  end

  middleware do
    use HumanInTheLoop,
      interrupt_on: %{
        "send_email" => %{allowed_decisions: [:approve, :edit, :reject]},
        "read_email" => false
      },
      tools: [MyApp.Tools.ReadEmail, MyApp.Tools.SendEmail]
  end
end

Human review requires a checkpointer because the run pauses and resumes from a checkpoint:

alias BeamWeaver.Checkpoint.ETS, as: CheckpointETS
alias BeamWeaver.Core.Message

checkpointer = CheckpointETS.new()
config = %{"configurable" => %{"thread_id" => "email-thread-1"}}

case MyApp.ReviewedAgent.invoke(
       %{messages: [Message.user("Send the update.")]},
       checkpointer: checkpointer,
       config: config
     ) do
  {:interrupted, interrupt} ->
    IO.inspect(interrupt.value.action_requests)

    MyApp.ReviewedAgent.resume(
      %{decisions: [%{type: :approve}]},
      checkpointer: checkpointer,
      config: config
    )

  other ->
    other
end

Useful options:

Option Meaning
:interrupt_on Map of tool names to true, false, or review config.
:interrupt_mode :all to review every matching tool call, or :first to pause on the first matching call.
:description_prefix Default prefix for generated review descriptions.
:tools Tool list used to validate edited arguments against tool schemas.

Review configs can include :when or :predicate with a function of arity 1, 2, or 3. The function receives the tool call, optionally the graph state, and optionally the runtime; returning true enables review for that call.

Model And Tool Call Limits

Use call limits to prevent runaway loops and enforce local cost controls.

middleware do
  use BeamWeaver.Agent.Middleware.ModelCallLimit,
   thread_limit: 20,
   run_limit: 6,
   exit_behavior: :end

  use BeamWeaver.Agent.Middleware.ToolCallLimit,
   tool_name: "search",
   thread_limit: 10,
   run_limit: 3,
   exit_behavior: :continue
end

Model-call options:

Option Meaning
:thread_limit Max model calls across a checkpointed thread. Defaults to 10 when omitted.
:run_limit Max model calls in one invocation.
:exit_behavior :error or :end.
:max_calls Compatibility alias for :thread_limit.

Tool-call options:

Option Meaning
:tool_name Optional tool name to limit. Omit for global limits.
:thread_limit Max matching tool calls across a checkpointed thread. Defaults to 10 when omitted.
:run_limit Max matching tool calls in one invocation.
:exit_behavior :continue, :error, or :end.
:message Tool message content for blocked calls.
:max_calls Compatibility alias for :thread_limit.

Model Fallback

BeamWeaver.Agent.Middleware.ModelFallback tries alternate models when the primary model returns a tagged error.

middleware do
  use BeamWeaver.Agent.Middleware.ModelFallback,
    fallbacks: [
      BeamWeaver.Models.init_chat_model!("openai:gpt-5.4-mini"),
      BeamWeaver.Models.init_chat_model!("anthropic:claude-haiku-4-5")
    ],
    retry_on: [:rate_limit, :timeout, :provider_error]
end

Useful options:

Option Meaning
:fallbacks Models to try in order.
:models Compatibility alias for :fallbacks.
:retry_on :error, :all, an error type atom, a list of error type atoms, or a one-argument predicate.

Retries

Model and tool retry middleware share BeamWeaver.RetryPolicy.

middleware do
  use BeamWeaver.Agent.Middleware.ModelRetry,
    max_retries: 3,
    initial_delay: 100,
    max_delay: 5_000,
    backoff: 2.0,
    jitter: true,
    retry_on: [:rate_limit, :timeout],
    on_failure: :continue

  use BeamWeaver.Agent.Middleware.ToolRetry,
    tools: ["search"],
    max_retries: 2,
    retry_on: :tool_error,
    on_failure: :continue
end

Useful retry options:

ModelRetry defaults :retry_on to :transient so authentication, quota, and account-state provider failures are not retried unless you explicitly broaden the policy. ToolRetry uses the shared BeamWeaver.RetryPolicy default.

Option Meaning
:max_retries Compatibility option translated to max_attempts: max_retries + 1.
:max_attempts Total attempts including the first call.
:initial_delay Delay before first retry. Integers are milliseconds; floats are seconds.
:max_delay Maximum retry delay. Integers are milliseconds; floats are seconds.
:backoff Exponential backoff multiplier. Use 0 for constant delay.
:jitter false, true, or a non-negative integer jitter window in milliseconds.
:retry_on :error, :all, :transient, an error type atom, list of types, predicate, or {module, function, extra_args}.
:on_failure :error, :continue, or a one-argument formatter function.

ToolRetry also accepts :tools, a list of tool names, atoms, or tool structs.

Retry and fallback policies are agent middleware concerns. Provider model values do not wrap themselves with retry or fallback policies; keep resilience at this middleware boundary so tracing, call limits, interrupts, and tool middleware share the same runtime context.

PII Detection

BeamWeaver.Agent.Middleware.PII detects and edits text in user input, model output, and tool results.

middleware do
  use BeamWeaver.Agent.Middleware.PII,
   type: :email,
   strategy: :redact,
   apply_to_input: true

  use BeamWeaver.Agent.Middleware.PII,
   type: :credit_card,
   strategy: :mask,
   apply_to_input: true,
   apply_to_output: true
end

Built-in detector types are :email, :credit_card, :ip, :mac_address, and :url. Strategies are :block, :redact, :mask, and :hash. For typed event streams, pass PII.stream_transform/1 into BeamWeaver.Stream.MessagesTransformer.new(pre_projection: ...) to redact token and message chunks before final message projection.

Custom detectors can be regex strings, one-argument functions, or MFA tuples:

middleware do
  use BeamWeaver.Agent.Middleware.PII,
   type: :api_key,
   detector: ~S/sk-[A-Za-z0-9]{32}/,
   strategy: :block

  use BeamWeaver.Agent.Middleware.PII,
   type: :ssn,
   detector: &MyApp.PII.detect_ssn/1,
   strategy: :hash
end

Detector functions return match maps with :text or :value, :start, and :end byte offsets:

def detect_ssn(content) do
  Regex.scan(~r/\d{3}-\d{2}-\d{4}/, content, return: :index)
  |> Enum.map(fn [{start, length}] ->
    %{text: binary_part(content, start, length), start: start, end: start + length}
  end)
end

Todo List

BeamWeaver.Agent.Middleware.TodoList adds a native todo tool and prompt guidance. The tool updates graph state through commands, and the middleware prevents multiple parallel TODO writes from the same model response.

middleware do
  use BeamWeaver.Agent.Middleware.TodoList,
   state_key: :todos,
   tool_name: "todo",
   tool_description: "Maintain the working TODO list.",
   system_prompt: "Use the todo tool for multi-step tasks."
end

Useful options:

Option Meaning
:state_key Agent state key that stores TODO items.
:tool_name Tool name exposed to the model.
:tool_description Tool description sent in the tool schema.
:system_prompt Prompt text appended to model calls.

Tool Selection

BeamWeaver.Agent.Middleware.ToolSelection can filter tools deterministically or ask a model to select relevant tools for the current request.

middleware do
  use BeamWeaver.Agent.Middleware.ToolSelection,
   allow: ["search_docs", "get_ticket"],
   deny: ["internal_admin_tool"]

  use BeamWeaver.Agent.Middleware.ToolSelection,
   model: BeamWeaver.Models.init_chat_model!("openai:gpt-5.4-mini"),
   max_tools: 3,
   always_include: ["search_docs"]
end

Useful options:

Option Meaning
:allow Names that may be sent to the model.
:deny Names to remove.
:tags Keep tools with matching Tool.tags/1.
:metadata Keep tools whose metadata contains matching key/value pairs.
:predicate Two-argument filter predicate over the tool and model request.
:tools Static or dynamic tools added by middleware.
:model Selection model. Supplying this enables model-based selection.
:system_prompt Selection prompt.
:max_tools Maximum selected tools.
:always_include Tool names always included after selection.

Tool Emulator

BeamWeaver.Agent.Middleware.ToolEmulator replaces selected tool executions with model-generated tool messages. Use it for tests, demos, and early prototypes.

middleware do
  use BeamWeaver.Agent.Middleware.ToolEmulator,
   tools: ["get_weather"],
   model: BeamWeaver.Models.init_chat_model!("openai:gpt-5.4-mini")
end

Useful options:

Option Meaning
:tools Tool names, atoms, or tool structs to emulate. Omit to emulate every tool.
:model Model used to write the emulated tool result. Defaults to a fake model.
:prompt_template Template containing {tool}, {description}, and {args}.

Context Editing

BeamWeaver.Agent.Middleware.ContextEditing edits message history before model calls. The built-in edit clears older tool outputs while preserving recent tool context.

alias BeamWeaver.Agent.Middleware.ContextEditing

middleware do
  use ContextEditing,
   edits: [
     ContextEditing.ClearToolUses.new(
       trigger: 100_000,
       keep: 3,
       clear_at_least: 2_000,
       clear_tool_inputs: false,
       exclude_tools: ["audit_log"],
       placeholder: "[cleared]"
     )
   ],
   token_count_method: :approximate
end

You can also provide a custom editor:

middleware do
  use BeamWeaver.Agent.Middleware.ContextEditing,
    editor: fn messages -> Enum.take(messages, -12) end
end

Useful options:

Option Meaning
:edits List of edit structs or functions. Defaults to ClearToolUses.
:token_count_method :approximate or :model.
:editor Custom editor function or MFA used as a state hook.

ClearToolUses accepts :trigger, :clear_at_least, :keep, :clear_tool_inputs, :exclude_tools, and :placeholder.

Structured Output Retry

BeamWeaver.Agent.Middleware.StructuredOutputRetry adds feedback and retries when a model response cannot be parsed or validated as structured output.

middleware do
  use BeamWeaver.Agent.Middleware.StructuredOutputRetry,
   max_retries: 2,
   feedback: "Fix the structured output. Error: {error}"
end

Useful options:

Option Meaning
:max_retries Number of retry turns after the first structured-output failure.
:feedback Feedback template or one-argument function.
:retry_on Error type atom, list, MapSet, :all, or predicate over error type.

See Structured Output for schemas and model-level structured output.

Shell Tool

BeamWeaver.Agent.Middleware.ShellTool adds a policy-governed shell tool backed by a supervised session process for the agent run.

middleware do
  use BeamWeaver.Agent.Middleware.ShellTool,
   workspace_root: File.cwd!(),
   policy: [
     allow: ["git status", "mix test"],
     deny: [~r/--force/],
     timeout: 10_000,
     max_output_bytes: 20_000,
     redactions: [{~r/sk-[A-Za-z0-9]+/, "[REDACTED_API_KEY]"}]
   ],
   startup_commands: ["pwd"],
   shutdown_commands: [],
   tool_name: "shell"
end

Useful options:

Option Meaning
:workspace_root Base directory for the shell session.
:policy BeamWeaver.ShellPolicy options. Defaults to an allow-all policy only when not provided.
:startup_commands Commands run when the session starts.
:shutdown_commands Commands run before the session shuts down.
:tool_name Shell tool name.
:tool_description Shell tool description.
:state_key Private state key storing the session PID.

BeamWeaver.ShellPolicy supports :allow, :deny, :cwd, :env, :env_allowlist, :timeout, :max_output_bytes, :stderr, :empty_output, :truncation_indicator, :redactions, and a custom :executor.

Python documents file search as middleware that adds glob_search and grep_search tools. BeamWeaver exposes file and document search as a normal tool: BeamWeaver.Tools.FileSearch.

tools do
  tool BeamWeaver.Tools.FileSearch.new(
    roots: ["docs"],
    include: ["**/*.md"],
    exclude: ["**/.git/**"],
    include_hidden?: false,
    max_results: 10,
    max_file_bytes: 1_000_000,
    snippet_bytes: 240,
    query_mode: :literal,
    output_mode: :content
  )
end

Useful options:

Option Meaning
:retriever Optional retriever source instead of filesystem roots.
:roots Filesystem roots. Required when no retriever is provided.
:include / :exclude Glob patterns.
:include_hidden? Whether hidden paths can be searched.
:max_results Maximum results returned.
:max_file_bytes Maximum searchable file size.
:snippet_bytes Bytes included around a content match.
:query_mode :literal or :regex.
:output_mode :content or :count.
:sort :path or :mtime_desc.

Filesystem And Subagents

Deep Agents-style filesystem and subagent capabilities are integrated into normal BeamWeaver agents:

defmodule MyApp.ComposedAgent do
  use BeamWeaver.Agent

  alias BeamWeaver.Agent.Middleware
  alias BeamWeaver.Filesystem

  model BeamWeaver.Models.init_chat_model!("anthropic:claude-sonnet-4-6")
  filesystem Filesystem.State.new()

  subagents do
    subagent MyApp.Agents.Researcher
  end

  middleware do
    use Middleware.TodoList, tool_name: "write_todos"
  end
end

defmodule MyApp.Agents.Researcher do
  use BeamWeaver.Agent

  name "researcher"
  description "Collect evidence without changing files."
  system_prompt "Return concise findings with file paths."
end

BeamWeaver.Agent.Middleware.Filesystem contributes ls, read_file, write_file, edit_file, glob, grep, and, for executable backends, execute. BeamWeaver.Agent.Middleware.Subagents contributes the task tool for explicit synchronous subagent specs. Async subagents expose start_async_task, check_async_task, update_async_task, cancel_async_task, and list_async_tasks.

BeamWeaver.Agent.Middleware.SubagentOutputs contributes no tools and no prompt text. It only declares mergeable :subagent_outputs and :subagent_cache state channels for applications that expose their own named specialist tools and want to capture large outputs outside the supervisor transcript.

Filesystem, TODO planning, and subagent delegation are independent middleware pieces. Compose the ones you need; omitting one removes that behavior.

See Filesystem for the virtual filesystem API and Filesystem Permissions for path allow/deny rules. See Subagents for delegation configuration, structured subagent results, and context propagation. See Async Subagents for background task lifecycle, remote clients, and :async_tasks state.

Provider-Specific Middleware

Provider-specific behavior is intentionally narrow and lives near provider adapters.

For provider prompt caching see Prompt Caching .

OpenAI Moderation

BeamWeaver.OpenAI.ModerationMiddleware calls OpenAI's moderation endpoint at input, output, and optional tool-result boundaries.

middleware do
  use BeamWeaver.OpenAI.ModerationMiddleware,
    model: "omni-moderation-latest",
    check_input: true,
    check_output: true,
    check_tool_results: false,
    exit_behavior: :end,
    violation_message: "I can't comply because this was flagged for {categories}."
end

Useful options:

Option Meaning
:model OpenAI moderation model.
:check_input Moderate latest user input before the model call.
:check_output Moderate latest assistant output after the model call.
:check_tool_results Moderate tool messages before the model call.
:exit_behavior :error, :end, or :replace.
:violation_message Template with {categories}, {category_scores}, and {original_content}.
:client and OpenAI client opts Custom client or client construction options.

Anthropic Helpers

BeamWeaver includes Anthropic call-option helpers:

  • BeamWeaver.Anthropic.Middleware.PromptCaching

  • BeamWeaver.Anthropic.Middleware.Bash

  • BeamWeaver.Anthropic.Middleware.FileSearch

  • BeamWeaver.Anthropic.Middleware.AnthropicTools

These helpers produce provider call options, such as server-tool declarations or cache-control metadata. They are not general BeamWeaver.Agent.Middleware callbacks. For agent-level prompt cache routing across supported providers, use BeamWeaver.Agent.Middleware.PromptCaching.

opts =
  BeamWeaver.Anthropic.Middleware.Bash.new()
  |> BeamWeaver.Anthropic.Middleware.Bash.call_opts()