Kyno
Menu

Integrating Kyno with CrewAI

Adapter overview · LangGraph guide

Kyno refreshes direction in CrewAI's model messages before each model call. You keep your existing agents, tasks, and crew.

Install and connect

pip install "kyno[crewai]"

The example below uses your default remote profile. See connection configuration for named profiles and explicit credentials.

Required integration

Create a CrewAiKyno adapter and register its hook while your crew runs:

import kyno
from kyno.adapters.crewai import CrewAiKyno

with kyno.connect() as connection:
    binder = connection.binder("customer-support")
    adapter = CrewAiKyno(binder)
    adapter.register()
    try:
        result = crew.kickoff()
    finally:
        adapter.unregister()

crew is your application's configured CrewAI crew, not an object Kyno creates. You do not need to override a method, write a model-calling wrapper, or manually insert direction into its messages.

Registration uses CrewAI's global before-call hook registry. Keep its lifetime limited to the calls you intend to bind to this constitution; it is not scoped to the crew variable. Unregistering removes this adapter's hook without clearing unrelated hooks.

What Kyno provides

Before each model call, the hook pulls direction, applies the binder's failure policy, and replaces the previous Kyno direction block in the messages. The block names the constitution and version and includes the mission and principles. Other messages are preserved. Read change notes and deltas through the binding callback.

Use connection.binder("customer-support", detail="full") if the model also needs the declaration and principle descriptions. No callback, receipt storage, run ID, or step ID is required for direction injection.

Failure behavior

By default, a failed pull uses cached direction, or empty version-0 direction if no value was cached. The binder logs a warning through Python's kyno.sdk.binder logger. To raise instead of supplying fallback direction:

from kyno.sdk import PullPolicy

binder = connection.binder("customer-support", policy=PullPolicy(fail_closed=True))

Pass this binder to CrewAiKyno before registering the hook. See the shared failure and status reference.

Optional recording

Pass on_direction only if you want to observe the direction supplied before each model call. The callback receives an immutable DirectionBinding after the hook has inserted its rendered direction into the messages:

observed_bindings = []
adapter = CrewAiKyno(
    binder,
    on_direction=observed_bindings.append,
)

Register and unregister this adapter around your crew's execution as in the required integration. In this example, observed_bindings is an application-owned list, not storage Kyno creates. Its append method is the observer; you do not need to override an adapter method.

Each binding contains direction, status, recording, change_notes, and delta. The status is a BindingStatus enum: pulled, cached, or empty, with the shared meanings. Use binding.direction.render() for the exact direction block, including the selected compact/full detail. Read binding.change_notes and binding.delta for delivery metadata. Later pulls do not change previously received bindings.

Read transition metadata from the binding supplied to your observer. The notes cover versions crossed by that read; the delta compares its requested and returned versions. For example:

def observe(binding):
    for note in binding.change_notes:
        print(note)
    for change in binding.delta:
        print(change)

Read binding.direction.constitution_key and binding.direction.version to identify the direction supplied to that call. For example, an observer can append (binding.direction.constitution_key, binding.direction.version) to an application-owned list.

binding.recording is the server's immutable recording receipt, or None when the source supplied no receipt. Its status is a RecordingStatus: recorded, disabled, or failed. A recorded receipt has a record_id identifying a stored Kyno delivery record; the other statuses have no ID. These statuses describe server recording, separately from how direction was obtained. A failed pull using cached direction preserves the original receipt rather than creating a new record ID.

Recording is disabled by default. When recording succeeds, the record describes Core's direction response before the adapter injects it. Its receipt does not prove injection, model completion, or that an answer followed direction. The observer runs after injection, but still before the model call.

If you need durable per-call receipts, replace append with your own synchronous recording function. Your application assigns run/call IDs, captures the time, and chooses storage. The observer receives no task output or framework call ID; it reports supplied context before the model call, not completion or proof that the model followed direction. Do not infer a task/output association from callback order in parallel work.

The observer runs once per successful injection and adds no pull. A failed injection or a fail-closed pull emits no observer callback. If the observer raises an ordinary exception, Kyno logs it and leaves the injected direction in place. Its return value is ignored: it is not an approval gate. This is best-effort observation, not mandatory durable auditing. The callback runs synchronously, so slow recording delays the model call.

To record which direction accompanied a model output, your application must capture that call's input messages and output together. on_direction cannot do this by itself: it runs before the call and receives neither the output nor an ID identifying the call. Once your application has matched an output to its supplied binding, store the output together with binding.recording.record_id when a receipt is available. Kyno stores direction delivery history; your application owns output storage and the association between each call and its receipt. A record ID identifies a server read, not a CrewAI task or model call; cached uses can share an ID. For later inspection, you can use the saved delivery ID to retrieve the served direction version.

You can group delivery reads with an application-chosen correlation ID. For example, replace the binder setup in the required integration with:

binder = connection.binder("customer-support", correlation_id="support-run-123")

Every pull through this binder carries the same correlation ID. It groups recorded reads; it does not identify a model call or its output. The global hook registry still requires the registration scope described above.

Separate application receipt storage is optional. Keep any retained prompts, direction, and outputs in storage appropriate for potentially sensitive content.

Optional verification

Verification belongs to your application. You choose whether to verify, which direction version to assess against, when to run the check, and what to do with its result. Kyno supplies direction and delivery history; it does not call a verifier or control the crew's next action.

For example, suppose you want to review the final answer against the direction you read before starting the crew. Save that Direction object, run the crew, then pass the answer and saved direction to your verifier.

This is an illustrative extension of the required integration, not a prescribed verification workflow. As before, crew is your configured CrewAI crew. Two functions below belong to your application; neither is provided by Kyno:

Replace these placeholders with your own functions to run this example.

with kyno.connect() as connection:
    binder = connection.binder("customer-support")
    assessment_direction = binder.bind()
    adapter = CrewAiKyno(binder)
    adapter.register()
    try:
        result = crew.kickoff()
    finally:
        adapter.unregister()

    assessment = assess_output(
        output=result.raw,
        direction=assessment_direction,
    )
    handle_assessment(result, assessment)

result.raw is the final answer's text. Review runs after the crew finishes, not after every model call. If you keep review records, save the answer, assessment_direction, and the assessment together.

The saved direction does not change when the adapter pulls again. If you save version 1 and an operator publishes version 2 while the crew runs, later model calls can receive version 2. This example still reviews the final answer against version 1. It does not claim every call used that version. Your application chooses which version is relevant to its assessment.

The initial binder.bind() uses the same failure policy as other reads. By default, a failed read returns cached direction, or empty version-0 direction if nothing was cached. If your review requires a successful read, configure the binder as shown in Failure behavior. Also reject version 0 if your application requires a written constitution.

To review individual model calls instead, record each call's input and output under the same ID in your application. Do not match outputs to on_direction callbacks by their order: when two calls run at the same time, the second can finish first. Reading the binder's cache afterward does not identify a call's direction either; another call may already have replaced it with a newer version.

Maintained with Kyno 1.x documentation. View Markdown source

View the demoRead the FAQ