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.
-
Discovery —
vinxi type list [--links]andvinxi explain <type|interface|link-type|error-code>render the liveSchemaSnapshotof 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. -
Generic verbs mirroring the seam —
find --type,trace,get [--links]today; genericactandassertnext (every ticket transition is already an Act per ADR-0026, so a genericactsubsumes ticketing commands by construction). The six verbs remain the vocabulary ceiling; no CLI command may imply a seventh. -
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-unstablevinxi devnamespace (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_UNIVERSEenv →ConfigMissingwith remediation. A piped agent must never silently target a dev universe. - Bounded results with narrowing guidance (
--limit,truncatedflag, 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/explaindiscovery; the CLI remains the primary agent interface, MCP optional later.
Consequences
fault,ticket, andgraphtop-levels are gone; ticket inspection isfind --type ticketing.Ticketandget <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::systemambient authority;find/get/type listbypass the Query seam) — unchanged by this ADR and tracked as existing debt (ADR-0028’s absorption oftrace/findbehind QuerySpec, and a future CLI-as-client-of-vinxi-kernel-serverADR). - The seed/prove fixtures remain Rust under
vinxi devuntil Phase 4 turns them into data.