> For the complete documentation index, see [llms.txt](https://docs.kyvvu.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kyvvu.com/integrations/authoring.md).

# Template Authoring Guide

**What you'll learn:** How to create a custom behavioral template that maps your framework's events to Kyvvu's 12 atomic behavior types.

***

## When you need a custom template

The SDK ships four built-in templates:

* **`decorator`** -- for Python agents using `@kv.step` decorators.
* **`langchain`** -- for LangChain agents using the callback handler.
* **`langgraph`** -- for LangGraph graph executions.
* **`crewai`** -- for CrewAI agents.

You need a custom template when you're integrating a framework that none of the built-ins cover and isn't using `@kv.step` decorators. Examples: a custom HTTP-based agent, a Microsoft AutoGen agent, or any framework that emits its own event types. The template **engine** (loading, matching, interpolation) lives in `kyvvu_engine.templates`; the YAML files live with their integration.

## Template YAML anatomy

A template is a YAML file with three top-level keys and an ordered list of rules. Here's an annotated excerpt from the built-in `decorator` template:

```yaml
name: decorator                    # Template identifier
version: "1.0"                     # Version string (for your own tracking)
description: |                     # Optional human-readable description
  Maps Python decorator annotations to v0.05 Kyvvu atomic behaviors.

rules:                             # Ordered list of matching rules

  - id: model_call                 # Unique rule identifier
    description: |                 # What this rule matches (for documentation)
      AI model inference: LLM completion, chat, embedding, vision, etc.
    match:                         # Conditions -- all must be true for this rule to fire
      step_type: "step.model"
    behavior:                      # Output fields when the rule matches
      step_type: "step.model"
      verb: "POST"
      step_name: "{{ function_name }}"
      properties:
        model:
          name: "{{ kwargs.model | default('unknown') }}"
          provider: "{{ kwargs.provider | default('unknown') }}"

  - id: unknown                    # Catch-all fallback rule (should be last)
    match:
      step_type: "step.unknown"
    behavior:
      step_type: "step.unknown"
      step_name: "{{ function_name }}"
```

Each rule has:

| Key           | Required    | Purpose                                                           |
| ------------- | ----------- | ----------------------------------------------------------------- |
| `id`          | Recommended | Unique identifier for the rule (for debugging and documentation). |
| `description` | No          | Human-readable description.                                       |
| `match`       | Yes         | Conditions dict. All conditions must pass (implicit AND).         |
| `behavior`    | Yes         | Output dict with `step_type`, `verb`, `step_name`, `properties`.  |

> **Note:** The `behavior` block sets Behavior fields directly, and `Behavior` forbids any field it does not define. A key outside the set above fails when the behaviour is constructed, so a typo surfaces immediately rather than being silently ignored.

## The context dict

When your integration fires, it builds a **context dict** and passes it to `template.match(context)`. The template's rules match against this context.

The context keys depend on your integration. Here are the two built-in examples:

**Decorator context keys:**

```python
{
    "step_type": "step.resource",       # From @kv.step(step_type=...)
    "verb": "GET",                       # From @kv.step(verb=...)
    "function_name": "fetch_customer",   # The decorated function's __name__
    "kwargs": {"url": "https://...", "customer_id": "C-123"},  # Call arguments
}
```

**LangChain context keys:**

```python
{
    "event": "on_llm_start",            # LangChain callback name
    "name": "ChatOpenAI",               # Run name or model name
    "serialized": {"name": "gpt-4o"},   # LangChain serialized object
    "inputs": {...},                     # Chain inputs (on_chain_start)
}
```

**Designing context keys for a new framework:**

1. Identify what events your framework emits (e.g. `http_request`, `llm_call`, `tool_invoke`).
2. Choose a primary discriminator key (e.g. `event` or `event_type`).
3. Include any metadata your rules need to distinguish sub-types (e.g. HTTP method, model name, tool name).
4. Include data you want to extract into properties (e.g. URL, model parameters).

## Match semantics

Rules are evaluated **top to bottom** in list order. The first rule whose `match` block fully matches the context wins. Evaluation stops immediately -- no further rules are checked.

### Exact string matching

Each key in `match` is compared to the same key in the context dict. All keys must match (implicit AND):

```yaml
match:
  event: "on_tool_start"     # context["event"] == "on_tool_start"
  verb: "GET"                # AND context["verb"] == "GET"
```

### Nested keys (dot-path)

Use dot notation to match nested dict values:

```yaml
match:
  serialized.name: "gpt-4o"  # context["serialized"]["name"] == "gpt-4o"
```

### Regex patterns

Two special condition keys support regex matching:

* `name_pattern` -- regex match against `context["name"]` (case-insensitive)
* `function_name_pattern` -- regex match against `context["function_name"]` (case-insensitive)

```yaml
match:
  event: "on_tool_start"
  name_pattern: "^(search|fetch|read|get).*"   # Tool name starts with a read verb
```

### Why rule order matters

Because of first-match-wins, **specific rules must come before general ones**. For example, in the LangChain template, tool rules are ordered: read-pattern tools, then write-pattern tools, then the catch-all tool default. If the catch-all were first, it would match every tool and the specific patterns would never fire.

### The fallback/default rule

A rule with an empty `match` block matches everything:

```yaml
- id: fallback
  match: {}
  behavior:
    step_type: "step.unknown"
    step_name: "unrecognized"
```

Every template should end with a fallback rule so that unrecognized events produce a `step.unknown` behavior instead of returning `None` (no match).

## Property extraction

String values in the `behavior` block support `{{ }}` interpolation from the context dict.

### Simple lookup

```yaml
step_name: "{{ function_name }}"
```

Resolves `context["function_name"]`. If the key is missing, produces an empty string.

### Default values

```yaml
name: "{{ kwargs.model | default('unknown') }}"
```

If `context["kwargs"]["model"]` is missing or `None`, produces `"unknown"`.

### Nested paths

```yaml
host: "{{ kwargs.url | default('') }}"
```

Traverses `context["kwargs"]["url"]` using dot-path notation.

### Mapping to Kyvvu's property schema

Properties are grouped by purpose. The standard groups (from the [Behaviours reference](/core-concepts/behaviours.md)) are:

| Group     | Used with            | Example keys                                                  |
| --------- | -------------------- | ------------------------------------------------------------- |
| `target`  | `step.resource`      | `host`, `resource_type`, `system`, `table`, `domain`, `trust` |
| `model`   | `step.model`         | `name`, `provider`, `temperature`, `parameters`               |
| `guard`   | `step.gate`          | `check_type`, `result`                                        |
| `message` | `step.message`       | `channel`, `recipient`, `sender`                              |
| `auth`    | `step.credential`    | `credential_name`, `provider`, `access_level`                 |
| `data`    | any                  | `classification`, `fields`                                    |
| `exec`    | `step.exec`          | `runtime`, `isolation`, `side_effect_class`                   |
| `usage`   | `step.model` outputs | `prompt_tokens`, `completion_tokens`, `cost_usd`              |

Example property extraction in a rule:

```yaml
behavior:
  step_type: "step.resource"
  verb: "GET"
  step_name: "{{ function_name }}"
  properties:
    target:
      host: "{{ kwargs.url | default('') }}"
      resource_type: "{{ kwargs.resource_type | default('unknown') }}"
    data:
      classification: "{{ kwargs.data_class | default('') }}"
```

## Testing your template

Use `BehaviorTemplate.from_path()` to load your template and `match()` to verify mappings:

```python
from kyvvu_engine.templates import BehaviorTemplate

t = BehaviorTemplate.from_path("my_template.yaml")
result = t.match({"event": "http_request", "method": "GET", "url": "https://api.example.com"})
print(result)
# {'step_type': 'step.resource', 'verb': 'GET', ...}
```

Test each event type your framework emits. Test edge cases: missing fields, unknown events, events with `None` values. Verify the fallback rule catches unrecognized events.

## Worked example: HTTP agent template

Suppose you have an agent that emits these events:

| Event                | Data              |
| -------------------- | ----------------- |
| `http_request`       | `method`, `url`   |
| `llm_call`           | `model`, `prompt` |
| `approval_requested` | `approver`        |
| `task_started`       | --                |
| `task_completed`     | --                |

### Step 1: Task lifecycle rules

```yaml
name: http-agent
version: "1.0"
description: |
  Maps HTTP agent events to Kyvvu atomic behaviors.

rules:
  - id: task_start
    description: Agent begins a new task
    match:
      event: "task_started"
    behavior:
      step_type: "task.start"
      step_name: "task_started"

  - id: task_end
    description: Agent completes a task
    match:
      event: "task_completed"
    behavior:
      step_type: "task.end"
      step_name: "task_completed"
```

### Step 2: LLM call rule

```yaml
  - id: llm_call
    description: Agent calls an LLM
    match:
      event: "llm_call"
    behavior:
      step_type: "step.model"
      verb: "POST"
      step_name: "llm_call"
      properties:
        model:
          name: "{{ model | default('unknown') }}"
```

### Step 3: HTTP request rule

```yaml
  - id: http_request
    description: Agent makes an HTTP request
    match:
      event: "http_request"
    behavior:
      step_type: "step.resource"
      verb: "{{ method | default('GET') }}"
      step_name: "http_request"
      properties:
        target:
          host: "{{ url | default('') }}"
```

### Step 4: Approval gate

```yaml
  - id: approval
    description: Agent requests human approval
    match:
      event: "approval_requested"
    behavior:
      step_type: "step.gate"
      step_name: "approval_requested"
      properties:
        guard:
          check_type: "human_approval"
          result: "{{ result | default('pending') }}"
```

### Step 5: Fallback rule

```yaml
  - id: fallback
    description: Catch-all for unrecognized events
    match: {}
    behavior:
      step_type: "step.unknown"
      step_name: "{{ event | default('unknown') }}"
```

### Step 6: Test it

```python
from kyvvu_engine.templates import BehaviorTemplate

t = BehaviorTemplate.from_path("http_agent.template.yaml")

# Task lifecycle
r = t.match({"event": "task_started"})
assert r["step_type"] == "task.start"

# LLM call
r = t.match({"event": "llm_call", "model": "gpt-4o", "prompt": "Hello"})
assert r["step_type"] == "step.model"
assert r["properties"]["model"]["name"] == "gpt-4o"

# HTTP request
r = t.match({"event": "http_request", "method": "GET", "url": "https://api.example.com"})
assert r["step_type"] == "step.resource"
assert r["verb"] == "GET"
assert r["properties"]["target"]["host"] == "https://api.example.com"

# Approval gate
r = t.match({"event": "approval_requested", "approver": "admin@co.com", "result": "pass"})
assert r["step_type"] == "step.gate"
assert r["properties"]["guard"]["check_type"] == "human_approval"

# Unknown event falls to default
r = t.match({"event": "something_unexpected"})
assert r["step_type"] == "step.unknown"
```

***

## Next steps

* [Templates overview](/core-concepts/templates.md) -- deep-merge semantics and built-in template details
* [Atomic Behaviours](/core-concepts/behaviours.md) -- the 12 behavior types and property groups
* [Writing a New Integration](/integrations/custom.md) -- the `FrameworkAdapter` base class for hooking into frameworks
* [Pattern templates](https://github.com/Kyvvu/platform/tree/main/kyvvu-sdk/kyvvu/templates/patterns/README.md) -- reference snippets for common agent architectures (RAG, ReAct, human-in-the-loop, multi-agent)
