App context
Tools usually need to know who they are acting for. Mistri.agent(context:)
takes any object you pass and hands it to every tool handler and hook as
context.app, untouched. No globals, and no closure capturing the current
user into each tool.
agent = Mistri.agent("claude-opus-4-8", tools: tools, context: { user: current_user, tenant: current_tenant })Handlers take the run context as an optional second argument and read the slot from it:
Mistri::Tool.define("open_tickets", "Lists the caller's open tickets.") do |args, context| Ticket.for(context.app[:tenant]).open.limit(args["limit"] || 20)endA tool that does not need the context just omits the second parameter; nothing changes for it.
Hooks see the same context
before_tool and after_tool receive the identical context, so
authorization and audit read the acting user from one place:
authorize = lambda do |call, context| "not permitted for this tenant" unless context.app[:user].can?(call.name)end
agent = Mistri.agent("claude-opus-4-8", tools: tools, context: { user: current_user, tenant: current_tenant }, before_tool: authorize)Returning a string from before_tool blocks the call and answers the model in
band, so a tenant that lacks a capability gets a real explanation rather than a
silent drop.
What the slot holds
Whatever you pass, exactly as you passed it, and the same object rather than a
copy. It can be an ActiveRecord user, a plain struct, a tenant id, a request,
a permissions object, or a hash of several of these. When you pass nothing,
context.app is nil.
Why a slot, not a convention
The alternative is a closure per tool that captures the current user, which means rebuilding every tool on every request and leaking request state into tool definitions. A context slot lets you define tools once, at boot, and keep them authorization-aware: the acting identity arrives with the run, not baked into the tool.
The context also carries the run’s session, signal, and emit, which is
how sub-agents and progress-streaming tools are built.
app is the part that is yours. It pairs naturally with
agent definitions, where a file declares the tools and
the context supplies the user they act for.