Package Layout

The kavai package

ai/pyproject.toml is both the PEP 621 project (kavion-ai-server) and the pixi workspace. Source lives under ai/src/kavai/ (package-dir = {"" = "src"}), installed editable via pixi/uv. A single console script is exposed:

[project.scripts]
kavai = "kavai.main:main"

Two sibling packages in ai/packages/ are pulled in as editable dependencies of the workspace — see DataADK packages.

Subpackages

Path (src/kavai/) Purpose
app.py The ADK Runtime FastAPI app (kavai.app:app), hosting the systems on :50052.
main.py CLI / single-process entry point (kavai.main:main) — serve, chat, auth, api, ifc, dexpi, gcloud.
gateway/ The front-door FastAPI Gateway (kavai.gateway.app:app, :8080) that routes to the runtime and Kawa adapter.
systems/ The AI-engine implementations — argus, orion, kawa, dataadk_package — plus base.py and the SystemRegistry.
router.py SystemType enum + SystemRouter.resolve_system (routes and falls back retired system names).
integrity/ The API 571/584 integrity pipeline — stages, schemas, and reference data.
api/ The HTTP API layer: v1/ routers (equipment, health, integrity, iow) and the integrity contract endpoints.
core/ Cross-cutting concerns: auth.py, config.py, errors.py, metrics.py, feature_flags.py, response_pipeline.py, tool_execution_hooks.py.
models/ AG-UI event/handler models, request/response models, parser, enricher, response builder.
server/ AG-UI server plumbing: app_factory.py, jwt_utils.py, streaming/, telemetry/, api/.
cli/ The Typer/Click CLI command modules (auth, chat, azure, gcloud, ifc, dexpi, findings, server, …) plus api/, which renders the surface below as commands.
web_api/ The web backend’s operations as data, read from swagger.yaml and indexed by operationId. Consumed by cli/api/ today and by engine tool generation next; not to be confused with api/ above, which is this package’s own HTTP layer.
tools/, skills/, datasources/, ingestion/, processing/, routing/, foundation_services/, microvm/, azure/, config/, data/ Supporting subsystems (Supabase tools, skills, ingestion/processing, the GKE MicroVM sandbox, Azure integration, config, and reference data).
Note

The servers/kawa/ Kawa adapter is a sibling to src/kavai/ (not under it), because it runs in its own isolated pixi environment — see Runtime & systems.

kavai api — the web backend from the terminal

src/kavai/cli/api/ turns every operation in web/public/api-docs/swagger.yaml into a command. Nothing in it is hand-maintained per route: kavai.web_api reads the spec and indexes it by operationId, client.py sends the request, and __init__.py builds one Click command per operation, grouped by the operation’s first tag and resolved lazily so kavai --help does not parse the YAML.

The reading of the spec sits in kavai/web_api/ rather than inside the CLI because the CLI is not its only consumer. Engine tool generation needs the same index — the same operationId, the same typed parameters, the same x-agent-tool.safe flag and the same answer to whether a bearer can reach the operation. Keeping one reader means a generated tool and a generated command cannot disagree about what an operation takes.

kavai auth login                                       # writes JWT_TOKEN to ai/.env
kavai api list --tag gas-readings                      # what exists
kavai api show listDatasetGasReadings                  # parameters, auth, safety
kavai api gas-readings list-gas-readings --limit 500   # typed form
kavai api call listGasReadings limit=500 | jq          # generic form, for scripts

The response body goes to stdout and everything else to stderr, so piping to jq works. Non-GET operations prompt before they run unless given --yes. --base-url (or KAVAI_WEB_URL) points the CLI at dev or alpha instead of NEXT_PUBLIC_APP_URL.

Authentication is the bearer JWT that kavai auth login writes, which web/lib/api-auth.ts accepts alongside the browser’s session cookie. Not every route does: the spec marks some operations CookieAuth only, and those warn before running rather than returning a plausible empty result. kavai api coverage reports the split.

That split is a claim, though, not a measurement. security is hand-maintained beside the handlers, so it can name a scheme the handler never reads — in both directions. --probe calls the reads and reports where the spec and the backend disagree:

kavai api coverage --probe --param slug=my-campaign --param id=<image-uuid>

Each result is one of agrees, spec-overstates (declares SupabaseAuth, answers 401), spec-understates (declared CookieAuth-only, answers a bearer), or inconclusive (a 400/404/500 says nothing about authorization). The probe calls reads only, skips anything whose path or required query parameters --param did not supply — a 404 on an invented slug is not evidence — and never calls the three GETs that create an organization as a side effect of being read (ensureUserOrganization, debugFixOrganization, debugCreateTestOrganization). Those are excluded by operationId in MUTATING_READS, and a test asserts the names still resolve, since a rename would silently re-arm them.

Because the commands are generated, the CLI cannot cover fewer routes than the spec documents; what it can lose is an operation whose spec entry breaks generation — a missing or duplicated operationId, an undeclared path parameter, a tag that collides with a meta command, a query parameter that shadows a common option. ai/tests/cli/test_api_swagger_parity.py fails on each of those, and is the reason no per-route registry is kept here.

kavai findings — the worklist the API has no route for

src/kavai/cli/findings.py is the one hand-written group that overlaps the generated surface, and the overlap is deliberate. kavai api can already call each route behind a damage-mechanism finding; what it cannot do is the three things that make those routes usable from a terminal.

It resolves names. Every command takes an asset by tag — AB106 as readily as T100-AB106 — and a grounding by name, then looks the uuids up in the register and the two catalogs before it writes. The tag ranking is the same one web/lib/mcp/manifest.ts uses (tagMatchScore), so a shorthand means the same asset to the CLI and to the MCP tool surface. A name matching more than one row aborts with its candidates rather than taking the first: a finding filed against the wrong vessel is an organisation-visible claim that nothing downstream shows as misplaced.

It lists findings across assets. No backend route does. GET /equipment-damage-mechanisms answers for one photo and GET /equipment/{id} for one asset, which is the gap docs/proposals/20260821_findings_parity_with_anomalies.md records as P3. So findings list reads the register once, reads only the assets whose findings_count is non-zero, and fans out over those concurrently. The per-asset read is expensive (datasheet, nozzles, confirmed and nearby image sets), so the fan-out is capped at --max-assets (default 25, 0 lifts it) and names the assets it did not reach. A worklist that truncates in silence reads as a clean plant.

It reads a finding back whole. There is no GET by finding id at all, so findings show locates one through the asset that holds it — accepting the uuid or an unambiguous prefix of at least 8 characters, and --asset to skip the search.

kavai findings assets                                   # register, findings-first
kavai findings list --undecided                         # the worklist
kavai findings show <finding-id>                        # grounding, evidence, decisions
kavai findings mechanisms --grep insulation             # API-571 catalog
kavai findings raise AB106 -m "Corrosion Under Insulation" -n "Stained lagging"
kavai findings decide <finding-id> confirm -r "Wall loss measured"
kavai findings amend <finding-id> -c "Coating Loss"     # re-ground, logged
kavai findings evidence <finding-id> --add <image-id>

Nothing here re-implements a route: each call names an operationId and lets kavai.web_api supply the method, path and auth, so a path that moves in swagger.yaml moves here with it, and a test asserts every id the module names still resolves. Writes confirm before they run unless given --yes, matching kavai api. decide offers confirm and dismiss only: reclassify survives in the decision log’s enum but was retired as a verb by D2 of the 20260821 proposal, which made re-grounding an ordinary amendment.

The group is a workflow layer, not a second copy of the surface. If swagger.yaml ever grows a Findings tag, kavai api findings and kavai findings would be two different things wearing one word; a test in ai/tests/cli/test_findings.py fails if that tag appears, so the collision is resolved deliberately rather than discovered by a user.

Packaged data

Non-code assets shipped with the package:

  • kavai.integrity.data/*.yaml — API 571/584 reference data (damage mechanisms, process-unit mappings, IOW parameters).
  • kavai.data.api571/*.json — API 571 catalogue.
  • kavai.cli/scripts/*.sh — shell helpers invoked by the CLI.