kyvvu doctor¶
What you'll learn: How to find out, in one command, whether your setup works — and if it doesn't, which part.
kyvvu doctor¶
Runs nine checks, plus one per installed integration plugin, and prints one line each. It never stops at the first failure, because the check that explains a failure is usually the one after it.
The CLI help lists the diagnostic options:
Usage: kyvvu doctor [OPTIONS]
Diagnose this machine: versions, credentials, and what is actually enforced.
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ --api-url <str> Kyvvu API URL every check uses (overrides KV_API_URL) │
│ --agent-key <str> Agent to diagnose. Defaults to KV_AGENT_KEY; without one the policy │
│ and trace-sink checks cannot resolve an API policy set. │
│ --json Emit JSON instead of a report. │
│ --python <path> Project Python interpreter or virtual environment. │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Exit code is 1 when any check failed, 0 when everything passed or only
warned — so it works in a CI step or a support reply.
A healthy setup¶
The project path and installed version values below vary by machine. This example uses a registration cached on September 11 and an installation date of September 10; the report prints the actual local dates.
$ kyvvu doctor --agent-key my-agent
Inspecting Python: <Python interpreter>
✓ Python <Python version> (CPython), minimum 3.11
✓ Versions kyvvu <CLI version>, kyvvu-engine <engine version> (satisfies <engine requirement>)
✓ .env <project>/.env defines 3 KV_* key(s): KV_AGENT_KEY, KV_API_KEY, KV_API_URL
✓ API https://platform.kyvvu.com reachable (200)
✓ Credential API key valid
✓ Registration cache 'my-agent' cached, registered at 2026-09-11T11:27:53+00:00 (agent_id=agent_6442dabc)
✓ Policies 15 policy(ies) in effect from the api source at https://platform.kyvvu.com (7 at step_execution, enforcing)
✓ Trace sink https://platform.kyvvu.com
• Local trial no local trial record (kyvvu installed 2026-09-10)
→ No trial record was written. Pass --record-trial to keep a local note of this run (opt-in, stays on this machine, sent nowhere); --forget-trial deletes it.
All checks passed.
The Versions check compares the installed kyvvu-engine with the range kyvvu-cli declares, read from the installed package metadata. It needs nothing beyond kyvvu-cli's own dependencies: for a >=X,<Y range on release numbers, which is what kyvvu-cli declares, it decides on its own. Only a range or version outside that (a pre-release engine, say) needs packaging, and without it the line reads • Versions … not verified (packaging not installed) rather than guess. With --python <path> (or a detected project .venv) the engine version is read from that interpreter.
The Credential check verifies the CLI session (/auth/me, which reports the account email) and the agent's API key (KV_API_KEY) — both, when both exist, on one line. The API key is checked against /policies/rules, which names no account, so it reports only API key valid, and it is checked against the platform an agent started here would use, which is where its validity matters. A valid session never hides a rejected key: the line fails and names the key.
Pointing it at another environment¶
--api-url decides which platform every check talks to: the API and
Credential probes, the policy fetch and the trace sink. You do not also need
KV_API_URL exported. Against a dev environment with a key minted somewhere
else, every line names the URL you passed. Here governance.example.com
stands for the other platform endpoint. These excerpts omit unrelated checks:
$ kyvvu doctor --api-url https://governance.example.com --agent-key my-agent
✓ API https://governance.example.com reachable (200)
✗ Credential API key rejected by https://governance.example.com
→ Mint a new API key in the dashboard of https://governance.example.com, or point the agent at the platform that issued this one (--api-url / KV_API_URL).
• Registration cache no cached registration for 'my-agent' — the next run will register
✗ Policies 0 policies in effect: the configured api source at https://governance.example.com could not be read and there was no cached set to fall back on — the platform rejected the API key (401)
→ The agent's API key (KV_API_KEY) is not accepted by https://governance.example.com: mint one in that platform's dashboard, or point the agent at the platform that issued it with --api-url / KV_API_URL.
✓ Trace sink https://governance.example.com
With a kyvvu auth session as well, the Credential line reports both, and a
valid session does not turn a rejected key into a tick:
$ kyvvu doctor --api-url https://governance.example.com --agent-key my-agent
✗ Credential CLI session valid — you@example.com; API key rejected by https://governance.example.com
→ Mint a new API key in the dashboard of https://governance.example.com, or point the agent at the platform that issued this one (--api-url / KV_API_URL).
Without --api-url, the Policies and Trace sink checks resolve the platform
exactly as an agent started in the same shell would: KV_API_URL, otherwise
https://platform.kyvvu.com. The API check and the session half of the
Credential check additionally fall back to the URL of a stored kyvvu auth
session, which the CLI reads and the Python client does not; the API key is always
checked where the agent would use it. When the two differ, the report says so
on every line it affects and names the flag that aligns them. After
kyvvu auth --api-url https://governance.example.com, with a dev key in
KV_API_KEY and no KV_API_URL:
$ kyvvu doctor --agent-key my-agent
✓ API https://governance.example.com reachable (200)
✗ Credential CLI session valid — you@example.com; API key rejected by https://platform.kyvvu.com
→ If this key was minted on https://governance.example.com, pass --api-url https://governance.example.com or export KV_API_URL=https://governance.example.com; otherwise mint a new one in the dashboard of https://platform.kyvvu.com.
• Registration cache no cached registration for 'my-agent' — the next run will register
✗ Policies 0 policies in effect: the configured api source at https://platform.kyvvu.com could not be read and there was no cached set to fall back on — the platform rejected the API key (401)
https://platform.kyvvu.com is not the platform your CLI session uses (https://governance.example.com); to diagnose that one, pass --api-url https://governance.example.com or export KV_API_URL=https://governance.example.com
→ The agent's API key (KV_API_KEY) is not accepted by https://platform.kyvvu.com: mint one in that platform's dashboard, or point the agent at the platform that issued it with --api-url / KV_API_URL.
! Trace sink https://platform.kyvvu.com — not the platform your CLI session uses (https://governance.example.com), so its dashboard will not show these traces
→ To send them there, pass --api-url https://governance.example.com or export KV_API_URL=https://governance.example.com.
The report is all kyvvu doctor prints. The policy check runs a real engine
runner, but that runner's own stderr notices (the Policy fetch failed …
warning and the KYVVU: 0 policies in effect banner an agent would print) are
silenced for the probe, because the Policies line already says the same thing
with the reason attached. An agent started in that shell still prints them.
The line that matters¶
Policies is the reason this command exists. A policy count cannot tell apart situations that need completely different fixes — several of them report zero and allow every step:
| What you see | What it means | What to do |
|---|---|---|
15 policy(ies) in effect … (7 at step_execution, enforcing) |
Working. | Nothing. |
0 policies in effect: the api source answered with no policies |
Nothing is assigned to this agent. | Restore the bundled baseline: in the dashboard open Manifests, pick the Kyvvu baseline and Assign it, or POST /api/v1/assignments with the bundled manifest_path (listed by GET /api/v1/baseline) and no repo_id. Re-registering will not bring an unassigned baseline back — that is deliberate. See Removing and restoring the baseline. |
the api source offered a policy set that cannot be loaded, so nothing was loaded (a failing check) |
A policy in the set is unloadable: an unknown rule_type, params that do not match the rule, a duplicate name, a bad regex or the retired scope: key. A set is loaded whole or refused whole, so an agent started here would refuse to start. |
Fix the policies listed under the check — each one names its reason. |
0 policies in effect: the configured api source at <url> could not be read |
The fetch failed and there was no cache to fall back on. The reason follows the dash: a 401 means that platform rejected the agent's API key. |
For a 401 or 403, fix KV_API_KEY for that URL, or point the agent at the platform that issued the key (--api-url / KV_API_URL). Otherwise fix the API check above. |
N policy(ies) in effect, but the set is stale |
Enforcing an older set; the last refresh failed. | Check connectivity — the agent is not enforcing what the platform currently says. |
N policy(ies) in effect, but this client is too old for the platform (state=stale_cache, cause=client_upgrade_required) |
The platform refused this client (426 client_upgrade_required). The agent keeps enforcing the last set it fetched, but receives no policy changes. |
Run the pip install command shown as the remedy, then restart the agent. |
the platform refused this client as too old and no policies are cached (a failing check) |
The same refusal with nothing to fall back on: an agent started here would refuse to start. | Run the pip install command shown as the remedy. |
N policy(ies) in effect … but none at step_execution |
The agent passed one check at registration; every step it takes afterwards is allowed. A fail-open blind spot with a tick on it. | Assign a manifest carrying step_execution policies — the bundled baseline has them. |
An unloadable set prints every refusal with its reason, and kyvvu doctor exits non-zero. For example, save this deliberately misspelled rule as broken-policies.yaml:
name: Broken example
policies:
- name: Never loads
rule_type: tainted_path_blokc
params: {}
severity: high
enforcement_point: step_execution
The Policies section of the report is:
$ KV_POLICY_FILE=broken-policies.yaml kyvvu doctor
✗ Policies the local source offered a policy set that cannot be loaded, so nothing was loaded and an agent started here would refuse to start
refused: policies[0] (Never loads): unknown rule_type 'tainted_path_blokc'. It is not in the engine's rule registry. Did you mean 'tainted_path_block'?
→ Fix the policies listed under this check, then re-run.
A client too old for the platform¶
When the platform no longer serves your installed engine, it answers the policy
fetch with 426 client_upgrade_required (see
Client and platform compatibility). The
Policies check names that state and gives the upgrade as the remedy. With a
policy cache (KV_POLICY_CACHE_PATH) holding the last set fetched, it is a
warning: the agent is still governed, by a set that will not be refreshed.
The examples below use a platform requiring engine 9.0.0 to illustrate this
refusal; installed version values and the cache timestamp vary by machine.
Only the Policies section is shown for the doctor reports.
$ kyvvu doctor --agent-key my-agent
! Policies 15 policy(ies) in effect, but this client is too old for the platform (state=stale_cache, cause=client_upgrade_required): it needs kyvvu-engine>=9.0.0 — the agent keeps enforcing the last set it fetched and will not receive policy changes until it is upgraded
→ Upgrade: pip install -U kyvvu 'kyvvu-engine>=9.0.0'
An agent started in the same shell also prints its one-line notice on stderr (kyvvu doctor silences it for its own probe):
KYVVU: kyvvu <client version> (kyvvu-engine <engine version>) is too old for this platform (needs kyvvu-engine>=9.0.0); still enforcing the policies fetched at <cache timestamp>; pip install -U kyvvu 'kyvvu-engine>=9.0.0' (state=stale_cache, cause=client_upgrade_required for agent_key='my-agent')
With nothing cached, the check fails, and kyvvu doctor exits non-zero. The
line under it is the exact KyvvuClientUpgradeRequired message an agent
started here would raise:
$ kyvvu doctor --agent-key my-agent
✗ Policies the platform refused this client as too old and no policies are cached, so an agent started here would refuse to start
kyvvu <client version> (kyvvu-engine <engine version>) is too old for this platform (needs kyvvu-engine>=9.0.0); no policies are cached, so nothing can be enforced; pip install -U kyvvu 'kyvvu-engine>=9.0.0'
→ Upgrade: pip install -U kyvvu 'kyvvu-engine>=9.0.0'
With --json, the check carries "stale_cause": "client_upgrade_required" and
the platform's refusal under client_upgrade.
Traces going nowhere¶
The Trace sink check is the tell for the commonest silent failure: an agent that registers, appears in the dashboard, and has an empty Logs page. These excerpts show only that section of the report.
$ kyvvu doctor
! Trace sink stdout (auto, because no policy source is set in the environment) — steps are printed, not sent to the platform
→ Set KV_LOG_LOCATION to your API URL, or pass log_location=<api_url>; or set KV_API_KEY and an agent key (KV_AGENT_KEY or --agent-key), so the platform becomes the policy source and 'auto' resolves to it.
The hint names what is actually missing. With KV_POLICY_FILE set, the local
file is the policy source and auto follows it, so the hint says that instead
of promising the platform:
$ KV_POLICY_FILE=policies.yaml kyvvu doctor
! Trace sink stdout (auto, because policies come from a local source, not the platform) — steps are printed, not sent to the platform
→ 'auto' follows the policy source, here the local policy file (KV_POLICY_FILE), so steps are printed. To send them to the platform, set KV_LOG_LOCATION to your API URL; or set an agent key (KV_AGENT_KEY or --agent-key), so the platform becomes the policy source and 'auto' resolves to it.
(That run used the bundled baseline saved as policies.yaml and had
KV_API_KEY set; without it the hint asks for both.)
Working offline¶
kyvvu doctor needs no credentials at all — being logged out is one of the
things it diagnoses. With KV_POLICY_FILE set it reports on a local manifest
instead of the platform, so it works the same for a
kyvvu serve --policy-file setup.
The local trial record¶
If you ran kyvvu try --record-trial, the Local trial check
reads that opt-in marker and reports how long it took you to get from
pip install to a blocked step. This Local trial excerpt illustrates three
recorded runs, with the first block four minutes and twelve seconds after
installation; the date, duration and home directory depend on your machine:
$ kyvvu doctor
• Local trial 3 offline run(s); first 2026-09-11; install to first block: 4m12s
→ Delete it with `kyvvu try --forget-trial` (<home>/.kyvvu/try-marker.json).
That marker is opt-in and never transmitted. Nothing sends it anywhere, and declining costs nothing: with no marker, the check is informational and every other command behaves identically.
doctor vs whoami¶
They sit side by side and answer different questions.
kyvvu whoami |
kyvvu doctor |
|
|---|---|---|
| Question | Who am I, and what would this shell do? | Is anything broken, and what? |
| Needs a session | Yes — exits 1 without one | No |
| On a failure | Stops | Keeps going, reports everything |
| Exit code | 1 when logged out | 1 only when a check failed |
| Output | Six stable lines a script can grep | A report |
doctor can also resolve something whoami deliberately refuses to guess:
whoami only reads this shell's KV_*, so with no KV_AGENT_KEY it qualifies
its answer about where traces go. doctor takes --agent-key, so given one it
resolves the trace sink for real — and, with credentials, asks the platform what
that agent's policy set actually is.
Next steps¶
- kyvvu auth — log in, and what
whoamireports - kyvvu try — enforcement with no account and no network
- kyvvu serve — a local policy evaluation server