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

Your First Agent

What you'll learn: How kyvvu init works, what the demo agent does, and how to read the policy evaluation output.


Scaffold a project

kyvvu init my-agent

This scaffolds project files — agent.py, requirements.txt, .env.example, .gitignore, and README.md. No authentication is required.

The output looks like:

✓ Created my-agent/

Next steps:

    cd my-agent

    # Install requirements:
    pip install -r requirements.txt

    # Set your key:
    # (open the dashboard, go to Workspace → API Keys, and create one)
    cp .env.example .env       # add your KV_API_KEY
    # or: export KV_API_KEY=your-key

    # Run the agent:
    python agent.py

Your agent is governed from its first step: Kyvvu applies its
baseline (15 policies, OWASP + EU AI Act) when the agent registers.
No assignment needed — see them on the Agents page, tagged Baseline.

To add more policies, go to the dashboard:
    Manifests page → select a manifest → assign to your agent
Or use the CLI:
    kyvvu list-manifests
    kyvvu assign-manifest --agent-id <id> --repo-id <id> --manifest <path>

Get an API key

The agent needs a KV_API_KEY to register. Create one in the dashboard under Workspace → API Keys.

kyvvu auth logs the CLI in and deliberately cannot mint an agent key — a CLI login must never be able to escalate itself into an agent-scoped credential. See Installation for the full explanation.

Run the demo agent

Set KV_ENVIRONMENT=development for local work. The environment an agent registers under is the environment= argument, then KV_ENVIRONMENT, then production, and the default is production everywhere. The scaffolded agent sets nothing, so without the variable your trial run registers as a production agent. kyvvu init puts KV_ENVIRONMENT=development in .env.example.

That is the whole first run. There is no assignment step before it — see Your agent is governed from its first step below — and there could not be one even if you wanted it: the agent does not exist until register_agent() runs, so there is no agent id to assign a manifest to until after this command.

The full agent.py source:

It starts a task, runs three steps, and the third one is blocked:

HttpExporter: batch accepted is the line that matters for onboarding: the task's steps reached the platform, so they are on the Logs page now. You did not configure that. log_location defaults to auto, which follows the policy source — an agent whose policies come from the platform sends its trace there too. Set KV_LOG_LOCATION=stdout if you would rather keep a run local.

Your agent is governed from its first step

Registering an agent applies Kyvvu's baseline to it automatically — 15 policies from two bundled manifests, the OWASP Top 10 for Agentic Applications default security template and EU AI Act minimal/limited-risk compliance. There is no manual assignment step, no repository to connect, and nothing to configure. The dashboard shows them on the agent's row, tagged Baseline, with the version of each bundled manifest.

The SDK ships the same 15 policies offline, so an agent that cannot reach the platform on its first run is still governed by them rather than by nothing. A successful policy fetch replaces that set with the platform's answer.

The baseline is on by default, not mandatory: you can unassign either manifest from the dashboard. Registering again will not put it back, because the platform honours the choice you made. To restore it, use the dashboard's Manifests page or the agent's page, or POST /api/v1/assignments with the bundled manifest path; see Removing and restoring the baseline.

Why the demo agent shows a compliance warning

With the default store_metadata=False, the platform stores no name, purpose or owner for your agent — so the baseline's EU AI Act documentation policies have nothing to check and report a warning (not a block; the agent runs normally). That is the honest answer: Kyvvu is reporting what it actually holds about the agent, and a governance tool that said "compliant" about data it does not have would be worse than one that says so.

Pass store_metadata=True to record the agent's documentation on the platform. owner_id is then filled in automatically from the email of the API key's owner if you do not supply one, since that is the accountable party (EU AI Act Article 16).

Your first run also opens incidents

Every policy violation the engine reports is sent to the platform as an incident, so the Incidents page is not empty after one python agent.py either. This is deliberate — a governance product whose violations reach only a terminal is the same failure as one whose traces do — but it is worth knowing what you are looking at. A first run of the unmodified demo opens seven:

incidents
why
do they pile up?

5 × EU AI Act / OWASP documentation policies

artifacts of store_metadata=False, exactly the warnings explained above

no — agent_registration incidents upsert per (agent, policy, environment)

"Code execution requires a preceding gate"

the demo's punchline

yes - step_execution incidents are one per violating step and policy, with no deduplication, by design

"AI-generated content must be marked before delivery"

the model call is not followed by a disclosure step

yes, same reason

To make the five documentation incidents stop, pass store_metadata=True so the platform holds the fields those policies check. To stop the other two, give the demo a passing gate and a disclosure step — or resolve them, which is what the Incidents page is for.

Whether a registration-metadata violation should become an incident at all when you have deliberately chosen not to store that metadata is an open product question, tracked as #456.

Incidents follow the trace sink: KV_INCIDENT_LOCATION=none turns them off without touching the trace, and KV_INCIDENT_LOCATION takes the same vocabulary as KV_LOG_LOCATION (including auto).

Caching: register_agent() results are cached locally at ~/.kyvvu/registrations/. If you call it on every startup with the same parameters, the SDK reuses the cached registration without a network call. You can safely call register_agent() every time your agent starts.

Data minimization: By default, only your agent's opaque ID and operational fields (risk classification, environment, declared tools) are stored in Kyvvu's cloud. Descriptive metadata (name, purpose, owner) is not persisted server-side. Pass store_metadata=True to opt in to storing metadata on the platform.

What happened

The @kv.step decorator wraps each function call in a three-phase cycle:

  1. Evaluate — the engine checks the intended behaviour against all applicable policies. It reads the task's history to make path-dependent decisions.

  2. Execute — if the evaluation returns allow or warn, the function runs.

  3. Record — the completed step (with output) is appended to the task's history for future evaluations.

Steps 1 and 2 passed for call_llm and fetch_user_data. When run_script was evaluated:

  • The engine identified it as step.exec (code execution).

  • The OWASP policy "Code execution requires a preceding gate" searched the task history for a step.gate.

  • None was found. The policy has severity critical, so the engine returned block.

  • The decorator raised KyvvuBlockedError instead of executing the function.

Clearing the block

A bare step.gate is not enough. step_requires_gate matches on the properties recorded on the gate step — specifically properties.guard.result against the policy's gate_result parameter, which defaults to "pass". A gate recorded with no properties, or with "approved", leaves the block exactly where it was:

properties recorded on the gate step
verdict

none

block (1.00)

step.gate with no properties

block (1.00)

step.gate, properties={"guard": {}}

block (1.00)

step.gate, properties={"guard": {"result": "approved"}}

block (1.00)

step.gate, properties={"guard": {"result": "pass"}}

allow (0.00)

kv.add_step() records exactly the properties you hand it, so that table is what you get. The working remedy — the one the demo prints — records the result:

See kv.add_step().

A decorated gate passes unless it raises GateDenied.

@kv.step(step_type="step.gate") does not read the wrapped function's return value. The decorator records the gate's result itself, after the function finishes: pass when it returns normally, fail when it raises kyvvu.GateDenied (or any other exception). Measured against the bundled baseline:

gate recorded by the decorator
verdict

no gate at all

block (1.00)

@kv.step(StepType.step_gate) whose function raises GateDenied

block (1.00)

@kv.step(StepType.step_gate) whose function returns normally

allow (0.00)

So a human-approval function must deny by raising:

A function that only prints "denied" or returns False records a pass, because a gate passes unless it raises GateDenied. GateDenied is not a KyvvuBlockedError: a block is a policy's verdict on a step, a denial is the gate function's own decision. When you would rather state the result yourself, kv.add_step() records exactly the properties you hand it, as above.

"Preceding", not "immediately preceding"

step_requires_gate scans the whole task history. A passing gate recorded at the start of the task still satisfies it at the end, with any number of other steps in between.

Immediate precedence is a different bundled policy: "External content taint: high-impact actions require a fresh gate", built on the step_directly_preceded_by rule. Once a step.resource GET has pulled in externally-trusted content, that policy requires the gate to be the step immediately before the high-impact action — an approval given before the untrusted data arrived does not count.

The two answer different questions: "was this approved at some point?" and "was this approved after we saw the untrusted input?" Both ship in the baseline, and both are what "policies on paths" means.

Inspect the policies

Because of the baseline, this is not empty on a fresh agent — 15 policies, before you assign anything:

(A SOURCE column, omitted here for width, names the bundled manifest each policy came from.)

Each policy is an instance of a rule with specific parameters. For example, "Code execution requires a preceding gate" uses the step_requires_gate rule with target_step_types: ["step.exec"]. The same rule backs "Destructive resource operations require a preceding gate" with different parameters (target_step_types: ["step.resource"], target_verb: "DELETE").

For the full details of each policy, see The Bundled Baseline and OWASP Default Manifest.

Add more policies

This step is optional, and it comes after the first run — your agent has an id only once it has registered, and kyvvu assign-manifest needs one.

The baseline is a floor to build on, not the whole story. Additional policies are managed via manifests — YAML files stored in a GitHub repository:

  1. Go to the Manifests page in the dashboard

  2. Kyvvu/manifests is already connected — every new workspace starts with the public library, so the page is never empty and there is nothing to set up. Connect your own repository as well if you keep manifests in one.

  3. Click Assign on a manifest and select your agent

Or use the CLI — kyvvu list-agents prints the id the platform assigned:

See Manifests for details on manifest structure and available policy sets.


Next steps

Last updated