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.
File Search
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()