Files
commandkit/docs/adr/0001-command-dispatch-and-inputs.md

11 KiB

ADR 0001: Own dispatch through an explicit registry and typed inputs

Status

Accepted.

Date

2026-10-06

Context

Audita, Narratio, Notarius, and Scriptorium repeat configuration inspection, routing, parsing, help, and error handling. Their command vocabulary and syntax have diverged. CommandKit aims to provide one implementation of standard inspection, consistent vocabulary and mechanics, and less application infrastructure. Application migrations are expected to make breaking changes.

Applications have genuine domain commands, such as Narratio's session restore, that remain outside the standard suite. Merely forwarding standard families from separate application dispatchers would leave custom-command mechanics and root help dependent on each application's implementation.

Input handling also needs a common boundary. Raw argv handlers or string-based flag retrieval leave repeated parsing and input assembly with applications. Presence matters for configuration overrides: omitted values differ from explicitly supplied empty strings, false, and zero.

Decision

Ownership and namespace structure

CommandKit owns the dispatcher, shared parsing and presence tracking, help, syntax diagnostics, and standard-family handlers and outcomes. Applications own custom command names, domain meaning, validation, effects, artifacts, and operational outcomes.

Use namespace/resource-first syntax for the standard suite. The config family and an installed promptkit family belong to CommandKit. PromptKit definition inspection uses the single promptkit namespace; root prompt and profile names remain available to applications. This provides a clear family ownership boundary without spreading a namespace across root verbs.

Applications register commands outside reserved families. They cannot replace standard handlers or add private operations underneath a reserved family. Standard-family context selectors and typed overrides use the defined input extension mechanism rather than introducing application-owned operations there.

Applications retain main, signal handling, context creation, stream selection, runtime construction, and final process exit. CommandKit returns invocation outcomes and does not terminate the process. Configuration schemas, acquisition, domain resolution and validation, and provider construction remain application integration responsibilities. ConfigKit and PromptKit retain their own configuration, provenance, safe inspection, and native rendering semantics.

Explicit registry and capability installation

Each explicitly constructed application instance owns a registry. Applications register individual commands and composable groups. There is no package-global registry or import-time init registration.

Definitions provide paths, descriptions, flags and defaults, repetition and presence metadata, operand requirements, and execution handlers. One definition supplies routing, parsing, and help. Duplicate paths and attempts to replace reserved commands return registration errors; order does not select a winner.

Help and version are installed automatically using supplied application identity and build metadata. Configuration and PromptKit families are installed when the application supplies their providers. Supplying a capability installs its complete standard family, not a collection requiring manual operation-by-operation wiring.

Registering providers does not execute them. Construction may diagnose missing or invalid wiring without reading configuration, resolving credentials, or building engines. Invalid configuration is an invocation-time failure. Command declarations, help, and version remain available independently of application initialization. The selected handler acquires dependencies when it needs them.

Do not install placeholder commands. Omitting the configuration provider does not make a partial integration a complete core adopter. PromptKit is optional; its selected family is installed as a complete capability. The CLI roadmap owns the proposed command inventory and the remaining choices for that standard.

Typed inputs with explicit bindings

Use application-defined input structs for custom commands and CommandKit-defined inputs for standard handlers. Explicit registration binds flags and operands to their input fields. The declarations also supply parsing and help metadata. Handlers do not retrieve primitive values by flag name or parse the complete argv.

Use ordinary typed fields where omission has no separate meaning. Where it does, keep supplied/not-supplied state beside the value in a presence-aware field. Defaults do not mark input as explicitly supplied. Preserve explicit empty, false, and zero values.

Create fresh input storage per invocation. Registered commands must not capture one mutable input permanently or retain prior values, collections, or presence. This decision addresses lifetime without establishing concurrent dispatch.

Supply context and stream access separately from CLI inputs. Input declarations and binding setup must not construct execution dependencies. Applications perform domain validation and may interpret parsed domain values or supply a custom value parser through the binding system.

The public factory, binding, registration, and handler signatures and presence type names remain API design choices. No illustrative declaration establishes an exact public API.

Outcomes and domain behavior

Resolve the command and handle help/version before loading application state. For execution, allocate fresh inputs, apply shared parsing and operand checks, then invoke the typed handler. Return its invocation outcome to the application.

Shared usage errors and standard-family outcomes belong to CommandKit. Custom handlers must communicate application-specific outcomes without silent generic reclassification. For example, Scriptorium output validation and Notarius artifact outcomes remain application concerns. Do not classify domain errors by matching message strings or print usage unconditionally for runtime failures.

Common mechanics do not require a common success-report format for custom commands. The application owns its artifact and operational output contracts. Exact result/error interfaces and unresolved grammar choices are separate design work, not approvals implied by this decision.

Alternatives considered

Dispatcher and ownership

Alternative Analysis
Application-owned dispatcher forwarding standard families Small integration surface and convenient migration, but custom syntax, root help, and errors still need cross-dispatcher coordination; removes less infrastructure.
Standard definitions mounted into an application's framework Reuses routing infrastructure, but application configuration can preserve inconsistent behavior outside mounted families. Internal framework reuse remains compatible with this decision.
Shared metadata/help with separate routing Unifies discovery but adds synchronization among metadata, routing, and parsing. One dispatcher avoids it.
Both full dispatch and embeddable family handlers Serves established external CLI architectures but adds integration surfaces and maintenance. Add embedding only for a concrete need.
Raw-argument handlers for custom commands Eases migration but leaves parsing and help responsibilities with applications. A future transition adapter needs explicit limitations and is not full adoption.
Universal application runtime framework Could absorb startup and operational services, but expands beyond the demonstrated CLI boundary and domain ownership.
Verb-first standard paths Groups actions but distributes namespace ownership among individual routes. Namespace/resource-first paths keep family ownership clear.
Reserve root prompt and profile Provides short resource paths but consumes application namespaces. A single promptkit family gives the library one ownership boundary.

Registration

Alternative Analysis
Declarative tree passed to construction Makes assembly visible together; larger applications still need group composition. May become a convenience over the same registry.
Constructor options for every command/group Compact setup, but excessive options can obscure commands versus application settings. Option conventions remain API design work.
Global registry populated by init Less entry-point code, but hidden import effects, shared mutable state, and unclear ownership. Explicit instances avoid those costs.

Handler inputs

Alternative Analysis
Parsed-value accessor object Uniform handlers, but string-based lookup, runtime type errors, and repeated assembly remain.
Individual typed parameters Clear for small commands; grows into long signatures and adapter machinery. Typed structs scale more naturally.
Reflection and struct tags Compact declarations, but adds a tag language, field-discovery rules, and runtime declaration validation. Explicit bindings keep declarations visible.
Typed command objects Can organize complex handlers; must separate fresh input from dependency and execution lifetimes. Applications may use objects without making them mandatory.
Generated bindings/adapters Can combine compact authoring and typing, but adds tooling and generated-file maintenance without demonstrated need.
Raw arguments Simple routing integration, but returns common parsing and help to applications.

Consequences

One command system removes repeated routing, parser setup, help assembly, and shared error plumbing as well as standard inspection handlers. Domain handlers and their declarations remain recognizable application code.

The public registration surface is larger than family delegation, and explicit bindings require connecting declarations to the correct fields. Those costs support consistent mechanics without requiring reflection or generated code.

Shared parsing defects can affect every adopter. Consumer and backend integration evidence must protect selected semantics, including ownership conflicts, help without configuration, presence, fresh input, cancellation, and domain outcomes. Intentional migration changes require coordinated application documentation and subprocess-consumer updates.

The architecture policy owns normative boundaries. The dispatcher roadmap owns implementation status and pending API design. The CLI roadmap retains command proposals and their approval status. ADR 0002 selects the backend, and ADR 0003 defines its public/internal boundary.