Custom Python Agent

This example walks through a complete Kyvvu integration using the Python SDK decorator pattern. The agent is a customer support ticket router: it fetches a ticket, classifies it with an LLM, routes it down one of three specialized paths, generates a response, requests human approval, and closes the task. Seven steps, real branching logic, all logged.

The full source is in examples/custom-agent/arrow-up-right in the platform repo.


What this example covers

  • Initializing the Kyvvu client and registering an agent

  • Decorating methods with @kv.log_step() to log each step

  • Handling branching workflows (different paths, same audit trail)

  • Graceful handling of policy violations at registration time

  • Running the agent end-to-end


Setup

from kyvvu import Kyvvu
from kyvvu.exceptions import KyvvuPolicyViolationError
import os

kv = Kyvvu(
    api_key=os.getenv("KV_DEMO_API_KEY"),
    api_url=os.getenv("KV_DEMO_API_URL", "http://localhost:8000"),
)

Kyvvu is the entry point for everything. One instance per agent process is the normal pattern. It holds the API connection, tracks the current task, and provides the @kv.log_step decorator.


Agent registration

Agents register themselves at startup by calling kv.register_agent(). This creates (or updates) the agent record in Kyvvu and triggers any agent_registration policies immediately.

A few things to note:

  • agent_key is the stable identifier you choose. Kyvvu hashes it together with your API key to produce a unique agent_id. Re-registering with the same agent_key updates the existing record rather than creating a duplicate.

  • risk_classification accepts minimal, limited, high, or unacceptable. Policies can be scoped to specific risk levels — a high-risk agent may face stricter checks than a limited-risk one.

  • Catching KyvvuPolicyViolationError is optional but recommended in production. If you don't catch it, the exception propagates and your agent won't start.


Logging steps with @kv.log_step

Every meaningful operation in the agent is decorated with @kv.log_step(node_type). The decorator intercepts the function call, wraps it in a logged step, and forwards the return value unchanged. Your agent code does not change.

The decorator records the step's node_type, captures the function's inputs and outputs, timestamps the execution, and chains the log entry's hash to the previous step's hash. None of this requires any change to your function body.


Branching workflows

Real agents don't follow a straight line. This agent routes to one of three specialized paths based on the LLM classification — but the audit trail captures all of it regardless of which branch runs.

Each @kv.log_step call is independent. The SDK tracks which task is running and automatically assigns the correct step number — you don't manage sequencing yourself. Steps that don't run (e.g., create_jira_ticket on a billing ticket) simply aren't logged, which is exactly what you want in the audit trail.


Running the workflow

With the class defined, the execution loop is straightforward:

After this runs, the Kyvvu dashboard will show a complete task with all logged steps in order, their inputs and outputs, and the validated hash chain proving the log hasn't been tampered with.


What the dashboard shows

Once the agent has run, navigate to Logs in the dashboard and find the task. You'll see:

  • Each step listed in sequence with its node_type, timestamp, and duration

  • Input and output data captured for each step

  • Hash chain validation status — every step shows ✓ if the chain is intact

  • Any incidents generated by step_execution or task_execution policies

If you have a policy configured (e.g., "every LLM_CALL must be preceded by a PII_CHECK"), violations appear as incidents linked directly to the offending step.


Key patterns to take away

One Kyvvu instance, module-level. Initialize kv once at the top of your agent module. All decorated methods share the same client and task context.

Register at startup, not per-request. kv.register_agent() is called once when the agent process starts. It's idempotent — safe to call on every restart.

END_NODE closes the task. The SDK tracks an open task from START_NODE until it sees END_NODE. Always end your workflow with an END_NODE-decorated method, even if it just clears state. A task without an END_NODE is flagged as unfinished.

Branching is fine. You don't need to tell Kyvvu about your routing logic. Decorate every meaningful operation and let the audit trail reflect what actually ran.

The decorator is transparent. @kv.log_step does not modify return values and adds negligible latency. What happens if a log call fails (network error, API down) is controlled by the on_error setting on the Kyvvu instance — see SDK Reference for details.


Next steps

  • LangChain agent: If you're using LangChain, the callback handler approach is even less intrusive — see LangChain Agent

  • Policies: Set up a step_execution policy to enforce that HUMAN_APPROVAL always precedes TOOL_CALL in your workflow — any task that skips the approval step will generate an incident automatically

  • Audit reports: After a few runs, generate a PDF report from the dashboard under Reports → Generate

Last updated