Skip to content

ADR-0029: CLI grammar — fixed kernel-verb surface, ontology-discovered nouns

ADR-0029: CLI grammar — fixed kernel-verb surface, ontology-discovered nouns

Status

Accepted

Date

2026-07-04

Context

By Milestone A the vinxi CLI had accumulated three vocabularies at its top level: kernel operations (reality install, bind, db, errors), one product’s domain commands (fault inject/clear/correlate, ticket list/show, with --olt flags and fault.* link ids compiled into the binary), and demo fixtures written in Rust (seed-fiber-pon, seed-trafficure, prove-trafficure). Every future product would have grown the grammar the same way. Meanwhile the CLI’s primary consumer is an AI agent (ADR-0014 names agents as first-class consumers of the error contract “over HTTP syscalls and CLI”), and the strongest empirical evidence on agent-computer interfaces (SWE-agent, NeurIPS 2024) shows interface design alone is worth model-scale capability differences: structured output, bounded result sets with narrowing guidance, non-interactive operation, and typed recoverable errors.

The kernel already has the shape kubectl proved: a small closed verb set (ADR-0002’s six verbs) over an open, user-installed noun set (SchemaDocuments ≈ CRDs). The CLI just wasn’t built that way. Separately, the terminology decision renaming userspace kind to type (EntityType; kernel Kind stays for closed categories) fixes what the CLI surface must say, even while code retains EntityKindId until the repo-wide rename.

Decision

The CLI is three strictly separated layers over a fixed grammar; domain vocabulary enters as data, never as compiled subcommands.

  1. Discoveryvinxi type list [--links] and vinxi explain <type|interface|link-type|error-code> render the live SchemaSnapshot of the target universe. The installed schema is the documentation; nothing ontology-specific is baked into the binary. This is how the CLI “speaks the customer’s ontology” while staying predictable: grammar closed, vocabulary open.

  2. Generic verbs mirroring the seamfind --type, trace, get [--links] today; generic act and assert next (every ticket transition is already an Act per ADR-0026, so a generic act subsumes ticketing commands by construction). The six verbs remain the vocabulary ceiling; no CLI command may imply a seventh.

  3. Domain language as packaged data — named macros ship in the reality package (realities/<product>/playbooks/, future) and mount under one fixed slot (vinxi run <reality>.<playbook>); the synthetic/demo harness lives under the explicitly-unstable vinxi dev namespace (seeds, provers, inject-cut, clear-alarm, correlate), documented as outside the CLI contract.

Cross-cutting output and scope contract, enforced by construction (commands return a Report { json, human }; main emits exactly once):

  • JSON when stdout is piped, human on a terminal, forced with --output. Progress lines go to stderr in machine mode; stdout is always one parseable document. Errors keep the ADR-0014 WireError-on-stderr + typed-exit-code contract.
  • No compiled scope defaults. Tenant/universe resolve flag → VINXI_TENANT/VINXI_UNIVERSE env → ConfigMissing with remediation. A piped agent must never silently target a dev universe.
  • Bounded results with narrowing guidance (--limit, truncated flag, and a “narrow with —trait / raise —limit” note), explicit empty-result messages, no fuzzy command correction, idempotent mutations advertised as safe to re-run.

The Milestone A domain commands were removed, not aliased (pre-adoption, approximately zero external users). Once the surface stabilizes, shipped commands become immutable contracts: evolve additively, never repurpose.

Alternatives Considered

  • Per-product compiled subcommands (status quo ante): every reality grows the binary; the grammar is unbounded; agents can’t predict the surface. This is the anti-pattern kubectl exists to avoid.
  • Plugin binaries (vinxi-<name> on PATH, krew-style): keeps the core clean but fragments distribution and versioning; deferred until playbooks prove insufficient.
  • MCP server instead of a CLI: measured 30x+ token overhead for schema preload and lower reliability versus progressive --help/explain discovery; the CLI remains the primary agent interface, MCP optional later.

Consequences

  • fault, ticket, and graph top-levels are gone; ticket inspection is find --type ticketing.Ticket and get <id> --links.
  • Phase 3 (generic act/assert) and Phase 4 (playbooks) are ledgered in TODO.md § “CLI — agent-first grammar”, as are --dry-run/confirm-protocol and NDJSON, deferred until a command needs them.
  • The CLI still embeds the kernel and reads some ports directly (OperationContext::system ambient authority; find/get/type list bypass the Query seam) — unchanged by this ADR and tracked as existing debt (ADR-0028’s absorption of trace/find behind QuerySpec, and a future CLI-as-client-of-vinxi-kernel-server ADR).
  • The seed/prove fixtures remain Rust under vinxi dev until Phase 4 turns them into data.