For the complete documentation index, see llms.txt. This page is also available as Markdown.

Writing a New Integration

What you'll learn: How to integrate Kyvvu with a framework that isn't covered by the built-in decorator or LangChain handler, by authoring a YAML template and subclassing FrameworkAdapter.


Overview

Integrating a new framework requires two things:

  1. A YAML template — defines how the framework's events map to Kyvvu's atomic Behavior vocabulary.

  2. A FrameworkAdapter subclass — captures the framework's events and passes them through the template system.

Step 1: Author a YAML template

For a comprehensive guide on template authoring -- match semantics, property extraction, and a full worked example -- see the Template Authoring Guide.

The template maps framework events to behaviour fields (step_type, verb, step_name, properties). Rules are evaluated in order; first match wins. Each rule has a match block (conditions) and a behavior block (the emitted behaviour):

name: my-framework-template
version: "1.0"

rules:
  - id: model_call
    match:
      event: "llm_invoke"
    behavior:
      step_type: "step.model"
      verb: "POST"
      step_name: "{{ name }}"
      properties:
        model:
          provider: "{{ provider }}"
          name: "{{ model_name }}"

  - id: tool_call
    match:
      event: "tool_execute"
    behavior:
      step_type: "step.resource"
      verb: "POST"
      step_name: "{{ name }}"

  - id: fallback
    match: {}
    behavior:
      step_type: "step.unknown"

Match conditions support event (exact match) and name_pattern (regex on the name context key). Template variables ({{ provider }}) are interpolated from the event context at match time. See Writing a Custom Template for the full match/behavior field reference.

Loading and testing the template

Step 2: Subclass FrameworkAdapter

The FrameworkAdapter base class provides the machinery between your framework and the SDK. Set the template_name class attribute to the name of your built-in template, then write callback methods that build a context dict and delegate to the base class. The constructor takes an already-constructed Kyvvu instance (the adapter does not manage identity or registration) plus an optional template override:

Base class methods

Method
Purpose

_emit_step(context, input_data, step_run_id)

Match the template, evaluate policies for the intended step, and buffer it. Raises KyvvuBlockedError on block (records the blocked step and flushes the task).

_record_step(step_run_id, output=...)

Complete a step previously started by _emit_step and record it into history.

_emit_task_start(context, task_id=None)

Emit task.start and return the task_id.

_emit_task_end(context, task_id=None)

Emit task.end and flush the task.

_emit_task_error(context, error, task_id=None)

Emit task.error and flush the task.

_resolve_root(run_id, parent_run_id)

Resolve the root task_id for a nested callback.

_safe_end_task(task_id, emit_fn)

Idempotent, crash-safe task flush (re-raises KyvvuBlockedError).

Subclasses may override the hooks _build_properties(matched, context, step_run_id) (inject framework-specific metadata before policy evaluation) and _on_block(task_id, exc) (circuit-breaker behaviour on block — CrewAI uses this because its event bus swallows exceptions).

Integration with the framework

How you hook into the framework depends on the framework. Common patterns:

  • Callback/hook system — register your adapter as a callback (like LangChain's BaseCallbackHandler).

  • Middleware — insert the adapter as middleware in the framework's request pipeline.

  • Monkey-patching — wrap the framework's core methods (least preferred).

Step 3: Test the mapping

Write tests that verify your template produces the correct Behaviors for each framework event:

Test each event type your framework emits, including edge cases (nested events, error events, events with missing fields).

Example: complete adapter

template_name is a class attribute; the constructor takes the Kyvvu instance. Each handler builds a context dict and drives the two-phase _emit_step / _record_step cycle (or the _emit_task_* lifecycle helpers):

The built-in CrewAI integration (KyvvuCrewAIListener, kyvvu.integrations.crewai) is a worked reference implementation of this pattern against the CrewAI event bus.


Next steps

Last updated