Runtime Context
Pass trusted request data in context:: the authenticated account, tenant, locale or dependencies used by tools. Context is separate from conversation state. The model sees it only when your code includes it in a prompt or tool result.
Complete Installation . Run the following modules and calls in order. The lookup uses a local dataset so it changes no external records.
Run this page’s examples in order in one IEx session; later blocks reuse the modules and values defined earlier.
Scope a tool to the authenticated caller
defmodule MyApp.CustomerOrder do
use BeamWeaver.Tool
name "customer_order"
description "Get the current customer's order status."
injected :context, :context
schema do
field :order_id, :string
end
@impl true
def invoke(_tool, %{"order_id" => order_id, context: context}, _opts) do
orders = %{{"cus_ada", "A-1001"} => "shipped"}
case Map.fetch(orders, {context.customer_id, order_id}) do
{:ok, status} -> {:ok, %{order_id: order_id, status: status}}
:error -> {:error, "Order not found for this customer"}
end
end
end
defmodule MyApp.CustomerAgent do
use BeamWeaver.Agent
model "openai:gpt-5.4-mini", max_tokens: 256, timeout: 60_000
system_prompt "Call customer_order before answering order questions."
tools [MyApp.CustomerOrder]
context_schema do
field :customer_id, :string, required: true
field :locale, :string
end
end
{:ok, result} =
MyApp.CustomerAgent.invoke(%{messages: ["Check order A-1001."]},
context: %{customer_id: "cus_ada", locale: "en"}
)
tool_result = Enum.find(result.messages, &(&1.role == :tool))
true = BeamWeaver.Core.Message.text(tool_result) =~ "shipped"
{:error, denied} =
BeamWeaver.Core.Tool.invoke(
MyApp.CustomerOrder,
%{"order_id" => "A-1001", context: %{customer_id: "cus_bob"}}
)
:tool_error = denied.type
"Order not found for this customer" = denied.message
The schema exposes only order_id. The atom-keyed context comes from the application. The lookup enforces ownership by querying with both the customer ID and order ID. Injection alone does not authorize an operation; your data access must actually use the trusted identity.
Pass context
invoke/2, stream_events/2, resume/2 and resume_review/2 all accept context:. Supply it on every call, including continuation after human review. Context is not stored in checkpoints. Without it, required context fields fail validation; otherwise tools may receive nil.
Subagents inherit the parent's context. Avoid putting secrets in prompts or returning them from tools merely because they are available in context.
Declare the context an agent needs
Context fields are optional unless required: true is set, unlike tool and response-schema fields. Validation happens before model execution:
{:error, error} = MyApp.CustomerAgent.invoke(%{messages: ["Check my order."]})
:invalid_context = error.type
:customer_id = error.details.field
| Type | Accepted data |
|---|---|
:string, :integer, :boolean, :atom
| The corresponding Elixir value |
:number, :float, :decimal
|
Numbers; :decimal also accepts Decimal
|
:date, :time, :utc_datetime, :naive_datetime
| Corresponding date/time structs |
:map, :list, {:list, type}
| Collections, optionally typed |
{:nullable, type}, {:union, types}
| Nullable or alternative types |
| A struct module | An instance of that struct |
:any
| Any value |
Declared string keys are normalized to their atom field names. Undeclared keys retain their original representation. An unknown field type produces :invalid_agent; invalid caller data produces :invalid_context.
Read context in tools
Use injected :context, :context for the map, or injected :runtime, :tool_runtime for BeamWeaver.Core.ToolRuntime, which also holds the store, state, configuration and call ID.
Tools
lists the available sources.
Read context in middleware
State hooks receive runtime.context; wrapper hooks receive request.runtime.context. Middleware can declare required context fields through context_schema/1. BeamWeaver validates those together with the agent's context schema. See
Middleware
.
Validate a required caller role in a hook before any provider request:
defmodule MyApp.SupportOnly do
use BeamWeaver.Agent.Middleware
@impl true
def before_agent(_middleware, _state, runtime) do
if runtime.context[:role] != :support,
do: {:error, BeamWeaver.Core.Error.new(:forbidden, "Support role required")}
end
end
{:ok, agent} =
BeamWeaver.Agent.build(
model: "openai:gpt-5.4-mini",
model_opts: [max_tokens: 128, timeout: 60_000],
middleware: [MyApp.SupportOnly]
)
{:error, %{type: :forbidden}} = BeamWeaver.Agent.invoke(agent, %{messages: ["Hello"]}, context: %{role: :guest})
{:ok, result} = BeamWeaver.Agent.invoke(agent, %{messages: ["Hello"]}, context: %{role: :support})
true = byte_size(BeamWeaver.Core.Message.text(List.last(result.messages))) > 0
Use context in the prompt
BeamWeaver.Agent.Middleware.DynamicPrompt accepts a function of state and runtime. Use it to derive instructions such as the reply language or service level. A prompt cannot replace authorization inside tools. Return only the context needed for the model's task. See
DynamicPrompt
.
Pass only the language preference to the model, not the whole caller object:
{:ok, agent} =
BeamWeaver.Agent.build(
model: "openai:gpt-5.4-mini",
model_opts: [max_tokens: 128, timeout: 60_000],
middleware: [
{BeamWeaver.Agent.Middleware.DynamicPrompt,
prompt: fn _state, runtime ->
"Greet the user in #{runtime.context.language}."
end}
]
)
{:ok, result} = BeamWeaver.Agent.invoke(agent, %{messages: ["Hello"]}, context: %{language: "Spanish"})
true = result.messages |> List.last() |> BeamWeaver.Core.Message.text() |> String.downcase() |> String.contains?("hola")
Context, state, config and store
| Data | Lifetime | Typical contents |
|---|---|---|
| Context | One call; resend on resume | Caller identity, locale, request dependencies |
| State | A run or checkpointed thread | Messages, task progress, tool updates |
| Config | Run settings and storage lookup | Thread ID and checkpoint selection |
| Store | Across conversations | Namespaced preferences and records |
Per-customer memory
Build memory namespaces from trusted context, for example a customer ID, inside the tool. Do not let the model choose an unrestricted namespace. Apply the same rule when reading, searching, updating and deleting records. Long-Term Memory shows the full storage flow.