Agent definitions
An agent definition is a markdown file. The YAML frontmatter carries the
config (model, tools, anything else you want), the body is the prompt, and
{placeholders} fill in when you build the agent. The prompt stays editable
prose a reviewer can diff, and code stays out of it.
---role: Trip Plannermodel: claude-opus-4-8tools: - search_flights - book_hotel---You plan trips end to end. Keep the itinerary tight and realistic,and address the traveler as {first_name}.Loading and rendering
Definition.load reads the file; render fills the placeholders and returns
the finished prompt.
definition = Mistri::Definition.load("app/agents/trip_planner.md")
definition.model # => "claude-opus-4-8"definition.role # => "Trip Planner"definition.tool_names # => ["search_flights", "book_hotel"]
agent = Mistri.agent( definition.model, system: definition.render(first_name: traveler.first_name), tools: registry.build(definition.tools, traveler),)The gem reads the file and nothing more. What a tool name means, and what any extra frontmatter key means, stays your vocabulary. Turning the declared names into real, per-user tools is the registry recipe.
Placeholders fail loudly
An unfilled placeholder raises rather than shipping a prompt that addresses
the traveler as the literal string {first_name}:
definition.render# => Mistri::ConfigurationError: no value for {first_name} in the trip_planner definitionA value of nil renders as empty, so optional context collapses cleanly
instead of forcing a branch on the caller:
# body: "Plan the trip.{scope_note}"definition.render(scope_note: nil) # => "Plan the trip."Tools carry options
A bare list declares tool names with no options. The map form attaches per-tool options in your vocabulary, which your registry reads however it likes (a gate, a cache setting, a flag):
---tools: book_hotel: gate: manager-approval search_flights:---definition.tools# => { "book_hotel" => { "gate" => "manager-approval" }, "search_flights" => {} }A tool entry with no options (like search_flights above) comes back as an
empty hash, so definition.tools is always a name-to-options map you can
iterate without a nil check.
Extra keys ride through
Any frontmatter key you add is available on config, so a definition can
carry host settings the gem never interprets:
# frontmatter: itinerary_style: relaxeddefinition.config["itinerary_style"] # => "relaxed"Why files
A definition is prose in version control. Product can rewrite the prompt in a pull request a reviewer diffs like any other change, the model and tool list sit right beside it, and no deploy rewrites a string constant buried in Ruby. A file without frontmatter raises on load, so a half-written definition fails at boot rather than mid-run.
For the identity a definition’s tools act under at runtime, see App context.