Files
notarius/docs/internal/cli.md

9.2 KiB

CLI Internals

This document describes internal/cli, Notarius's production composition root. The CLI reference owns command syntax and exit statuses; Configuration owns configuration values; and Operations owns filesystem layout, recovery, and operator procedures.

Inputs, Outputs, And Boundaries

The CLI accepts process arguments, standard streams, and injectable options used by tests and embedding code. It writes command results to the supplied streams and returns a process exit status. For a run, it also creates the production catalog and runtime collaborators, hands a prepared pipeline and source bytes to the framework, and places the logical files returned by the runner.

It is the only boundary allowed to compose concrete registries, LLM clients, cache/checkpoint collaborators, debug recorders, and physical output paths. Pipeline modules receive interfaces and request data rather than CLI streams or filesystem roots. The Architecture defines this composition-root boundary; Pipeline Internals owns resolution, preparation, and runner mechanics after their inputs are supplied.

Dispatch And Configuration Handoff

The root dispatcher handles help, configuration validation, pipeline listing, and a pipeline run. It normalizes injectable options before dispatch so that a missing production dependency fails as a command error rather than reaching execution.

Commands that need configuration use one shared loader. The CLI discovers the file, parses it through internal/core/config, starts from defaults, applies the file and supported environment overrides, and then validates it for the command. The configured discovery and precedence contract is in Configuration, while the loading and resolution mechanics are in Configuration Internals.

Configuration validation without a selected pipeline checks structural configuration only. Validation with a selected pipeline also builds the effective catalog, resolves the pipeline, and verifies every explicit effective PromptKit profile. Selected LLM-backed input, chunk, lane, output, and validator profiles are inspected against the configured PromptKit source and backend registrations without loading a prompt or performing generation, so an unknown or invalid profile fails before pipeline preparation. Credential availability remains an execution-time concern. Pipeline listing validates configuration before returning normalized, sorted identifiers.

Production Composition

The production composition helper allocates every framework registry and the prompt-asset registry, then registers the generic, Seriatim, and D&D module families in that order. The resulting registries provide both the module catalog used for resolution and the concrete constructors used for preparation. Tests may provide a catalog or registries instead; production code must not silently merge an injected partial catalog with production registrations.

The production LLM factory builds one PromptKit-backed client from the resolved promptkit.profile_dir or promptkit.profile_file source, attaches the profile-provenance recorder, creates one scheduler from the effective global LLM limit, and wraps the client before it reaches modules. Registration and LLM construction errors are returned before a pipeline is prepared. Configuration field definitions remain in Configuration; the D&D registrar's fallback profile assets and the adapter mechanics remain in LLM Runtime.

The factory also accepts LLMRuntimeOverrides, whose reasoning pointer preserves inherit, replace, and clear states across the composition boundary. Run orchestration constructs this value from the mutually exclusive --reasoning-effort and --clear-reasoning-effort controls. Absence preserves a nil pointer, replacement is trimmed, and clear uses a non-nil empty string. The same override reaches the one shared production client, checkpoint identity, and debug invocation metadata. Persistent reasoning configuration remains owned by PromptKit profiles; Notarius configuration has no reasoning field.

Run Orchestration

After parsing and validating a run invocation, the CLI performs this ordered handoff:

  1. load and validate configuration, then apply command-level operational overrides;
  2. create and validate a safe run identity, then allocate a debug bundle only when requested;
  3. build the effective catalog, resolve requested reference changes, resolve the effective pipeline, and inspect its explicit effective PromptKit profiles;
  4. materialize external or generated references and record redacted invocation and resolution provenance when debug capture is enabled;
  5. construct registries, the scheduled LLM client, and prepared modules;
  6. read the source input once, resolve its effective session from the explicit override or resolved input module and raw bytes, then construct requested checkpoint collaborators and invoke the framework runner with that same value; and
  7. write the runner's logical output files only after a successful run, then complete the command report and user-facing result.

Preparation happens before source parsing, so module construction and dependency failures cannot begin stage execution. The CLI also preserves the framework's result and warning information when it writes summaries and the final command result. Detailed state lifecycle, resume handling, and physical path confinement are maintained in Run State Internals and Operations.

The CLI owns the versioned generated-session policy and resolves the sole effective value before checkpoint construction. It records that value in the final debug invocation summary when capture is enabled and passes it unchanged to checkpoint identity and pipeline.RunInput. The public flag and stability contract are defined by the CLI reference; framework and LLM packages only transport the supplied value.

For run --json, the CLI constructs and encodes its private run-result receipt after a successful runner result is available, before it publishes logical output files. It writes the prepared receipt to standard output only after output publication and requested debug terminalization succeed. A receipt-write failure exits with runtime status 1 and may leave partial standard-output bytes, but the already-published output bundle remains complete and requested debug reporting remains successfully terminalized. The CLI reports a bounded command-owned error and does not repeat terminal reporting. The receipt remains a CLI reporting concern rather than a framework or output-module responsibility; its public contract is the run-result receipt.

Failure Mapping And Terminal Reporting

Argument, flag, and invocation-combination failures are reported to standard error before runtime composition and use the syntax error class. Once an invocation is syntactically valid, configuration loading and validation, resolution, registration, profile checks, reference materialization, module construction, input reads, runner failures, output publication, and requested debug handling use the runtime failure class. The public status numbers and stream contract are defined in the CLI reference.

When debug capture has been allocated, one command-state value records the known run result. Guarded terminalization writes a success report once, or attempts a failure report and error record once. A persistence failure is reported in addition to the original failure and never replaces it. If a debug path exists, failure output includes that path so the retained diagnostic data is discoverable.

Invariants To Preserve

  • Only the CLI composes production implementations and physical runtime roots.
  • Configuration and resolved composition failures occur before module preparation or source parsing.
  • A runner's logical files are published only after a successful run.
  • Production registries and a caller-supplied catalog or registries are alternative composition sources, not an implicit mixture.
  • A requested debug bundle has one terminal report attempt; its persistence errors supplement rather than obscure the primary command error.
  • User-facing flags, paths, exit codes, and configuration fields are defined by their public documentation, not duplicated here.

Focused Tests

  • internal/cli/command_contract_test.go covers dispatch, help, syntax and runtime error classes, discovery, validation, and listing.
  • internal/cli/run_contract_test.go covers the run handoff, publication, debug reporting, and command-owned state collaborators.
  • internal/cli/production_contract_test.go covers registrar composition, production catalog contents, assets, and representative configuration validation.
  • internal/cli/reference_contract_test.go covers CLI reference overrides, origin separation, and materialization boundaries.
  • internal/cli/state_hardening_test.go covers safe run identity, state roots, and failure ordering.

Run go test ./internal/cli after changing command composition or command behavior. Pair it with go test ./internal/core/config when the configuration handoff changes.