Templates¶
Note: This page covers behavior templates — YAML rules that map framework events to Kyvvu Behaviors. For policy manifests (compliance policy sets assigned to agents), see Manifests.
What you'll learn: How YAML templates map framework events to Behaviors, how matching works, and how to customize templates.
What templates do¶
Templates are the translation layer between your framework's events and Kyvvu's canonical vocabulary. When your agent calls an LLM, reads a database, or sends a message, the Python client needs to know which (kind, verb) pair to use, and which properties to set. Templates define this mapping.
Without templates, every integration would need hardcoded mappings. With templates, the same Python client can support any framework — you just write a YAML file.
Built-in templates¶
The template system (loading, matching, interpolation) is the kyvvu-templates package, imported as kyvvu_templates. The kyvvu Python client provides load(name), which finds templates in the client and installed integration packages. Install kyvvu-langchain before loading the LangChain or LangGraph template:
from kyvvu.templates import load
decorator_template = load("decorator")
langchain_template = load("langchain")
The templates currently available across the Python client and integrations are:
decorator— for the@kv.stepdecorator integration. Maps decorator arguments directly to Behavior fields.langchain— shipped bykyvvu-langchainfor the callback handler. Maps LangChain callback events (on_llm_start,on_tool_start, etc.) to Behaviors.langgraph— shipped bykyvvu-langchainfor LangGraph graph executions.crewai— shipped bykyvvu-crewaifor CrewAI agents.
Each integration (and kyvvu-claude) loads its own template by path. To load any template file directly, use the kyvvu_templates loader:
from kyvvu_templates import BehaviorTemplate, load_template
tpl = BehaviorTemplate.from_path("/path/to/template.yaml") # or load_template(...)
How matching works¶
Templates use first-match-wins rule evaluation. Each template contains an ordered list of rules. For each framework event, the Python client walks the rules top to bottom and uses the first match.
Each rule has:
- match — predicates on the event context: an exact-equality check on any context key (event, kind, verb, a dot-path such as serialized.name) and, for names, a name_pattern regex.
- behavior — the Behavior fields to set: kind, step_name, verb, properties and output (for a completed gate's decision).
name_pattern is the one regex key, on every surface, tested against the context's name (the decorated function's name, or a callback's tool, model or chain name). It is a case-insensitive re.search, so it matches anywhere in the name; anchor with ^ and $. A context with no name never matches it.
For LangChain, LangGraph and CrewAI tool callbacks, name_pattern can select a rule's kind and properties, but it does not decide the recorded verb. Tools record POST unless their client declares the exact name in read_only_tools; tools with data.* or target.trust also require a read_only_tools or write_tools declaration.
The template does not invent a fallback. If no rule matches, match() returns nothing and the integration decides: a callback handler records the event as step.unknown (and logs a warning once per event), and the decorator builds the step from its own arguments. The built-in templates end with a match: {} catch-all so that the outcome is visible in the template itself.
Where task boundaries come from¶
An integration's template governs both the steps it records and the task boundaries it opens (task.start, task.end, task.error). The Kyvvu client's own template covers start_task() and friends and the decorator. Each callback handler uses the template passed to it, so a custom template for a handler must contain its boundary rules. See Writing a Custom Template for which boundaries each integration opens.
Deep-merge semantics¶
When the Python client constructs a Behavior, it deep-merges two sources (in order of priority):
- Caller overrides — values passed explicitly via
@kv.step(properties={...})orkv.start_task(properties={...}). - Template output — the matched rule's
behavior.
(A LangChain tool's own metadata["kyvvu"] is a third layer that sits above the template; see the next section.)
Caller overrides win at leaves. If the template sets properties.provider = "openai" and the caller sets properties.model = "gpt-4o", the result is {provider: "openai", model: "gpt-4o"}. If the caller sets properties.provider = "anthropic", the caller's value wins.
Template properties vs. tool metadata¶
For LangChain and LangGraph, a tool can declare its own Kyvvu attributes under metadata["kyvvu"], co-located with the tool definition. Those attributes deep-merge over the template-matched properties: the tool's value wins on any leaf conflict, siblings from the template are preserved, and a runtime warning is logged when a tool overrides a template-set property. In short, code wins over the org baseline. See LangChain / LangGraph — Attaching Kyvvu attributes to tools for the full picture.
Two tiers of developer effort¶
Templates support two levels of detail:
Lazy (generic fallback)¶
Use the built-in templates without customization. Every step gets a correct kind and verb based on the framework event. Properties are sparse — mostly framework-inferred defaults.
This is fine for getting started and for development-time tracing.
Rich (named rules with properties)¶
Write a custom template with rules that match specific functions and set detailed properties — target system, data classification, auth access level, domain. This enables fine-grained policies that check properties like target.table == "customer-data" or target.trust == "external".
This is what production deployments use.
Loading custom templates¶
A custom template replaces the built-in for the integration it is given to. It does not extend it, and there is no fall-through to the built-in. Start from a copy (kyvvu template show langchain > my.yaml) so the boundary rules are already there.
Two ways to use a custom template:
from kyvvu_templates import BehaviorTemplate
from kyvvu_langchain import KyvvuLangChainHandler
# For the decorator and start_task(): the Kyvvu client. This template must
# match all four task.* boundary kinds by `kind`.
client_template = BehaviorTemplate.from_path("/path/to/client-template.yaml")
kv = Kyvvu(api_key="...", agent_key="bot", template=client_template)
# For a framework handler: the handler. This template must carry the handler's
# own boundary rules (a copy of `kyvvu template show langchain` has them).
handler_template = BehaviorTemplate.from_path("/path/to/langchain-template.yaml")
handler = KyvvuLangChainHandler(kv, template=handler_template)
# Environment variable (resolution order: template kwarg > KV_TEMPLATE_LOCATION > built-in)
# export KV_TEMPLATE_LOCATION=/path/to/template.yaml
kv = Kyvvu(api_key="...", agent_key="bot")
The default for every template argument is the sentinel kyvvu.default_template ("the built-in, or KV_TEMPLATE_LOCATION"). template=None means no template: the client accepts it for @kv.step and add_step but refuses start_task(), and the callback handlers refuse it.
Template YAML structure¶
A minimal template:
name: my-agent-template
version: "1.0"
rules:
- id: llm_call
match:
kind: step.model
behavior:
kind: step.model
step_name: "{{ name }}"
properties:
provider: openai
model: gpt-4o
- id: db_read
match:
name_pattern: "^read_customer$"
behavior:
kind: step.resource
verb: GET
step_name: "{{ name }}"
properties:
target:
system: postgres
table: customer-data
trust: internal
data:
classification: pii
- id: fallback
match: {}
behavior:
kind: step.unknown
step_name: "{{ name }}"
A template passed to the Kyvvu client must match the four task boundary types of 1.0 by kind (task.start, task.end, task.error, task.idle). The list is fixed for 1.0: a boundary kind added in a later version will not make a 1.0 template fail to load. The example above omits them to stay short. Every rule has an id (unique), a match and a behavior. The loader rejects a template that breaks this, naming the rule and the fix: a missing or duplicate id, a missing match or behavior, an unknown rule key, an unknown behavior key, or a kind or verb outside the vocabulary. conditions and output are not keys; they are match and behavior. A match key that no integration sets is allowed but logged as a warning, so a typo is visible.
Rules are evaluated in order. The fallback rule with an empty match (match: {}) matches everything and should be last.
Next steps¶
- Python Decorator — how
@kv.stepuses templates - LangChain / LangGraph — the LangChain callback handler and its template
- Writing a New Integration — how to author templates for other frameworks
Further reading¶
- Template Authoring Guide — step-by-step walkthrough for creating a custom behavioral template, including match semantics, property extraction, and a worked example.
- Agent Pattern Templates — reference snippets for common agent architectures (RAG, ReAct, human-in-the-loop, multi-agent) that show how to map these patterns to Kyvvu's behavior vocabulary.