ADR-0036: CLI Architecture — argparse with Config Builder Pattern¶
Status: Accepted Enforced by: (process) Date: 2026-03-08
Context¶
HydraFlow needs a CLI that supports 50+ configuration options with values sourced from multiple layers: config files, environment variables, and command-line arguments. The two main Python CLI framework choices are:
- Click — Decorator-based, widely used, adds an external dependency.
- argparse — Standard library, no external dependency, imperative style.
Beyond framework choice, the CLI must merge configuration from three sources with clear precedence rules. Two patterns are common:
- Flat parsing: Each source is handled independently; merging happens ad-hoc at the call site.
- Builder pattern: A dedicated function (
build_config()) owns the merge logic, producing a single validated config object.
The hf console script (hf_cli/__main__.py) acts as a two-layer dispatcher:
workspace management commands (run, stop, view, init) are handled directly, while
orchestrator commands and flags are delegated to cli.main(). The dashboard
exposes /api/control/* endpoints for start/stop/config but does not yet cover
all CLI operations (clean, prep, scaffold, labels, audit).
Decision¶
Use argparse (Python standard library) as the CLI framework, paired with a
config builder pattern implemented in build_config() (src/cli.py).
Framework: argparse over Click¶
- argparse is a stdlib module — no additional dependency to install, pin, or
audit. The project has zero Click usage in source or
pyproject.toml. - HydraFlow's CLI is an operational tool, not a user-facing product with rich help formatting needs. argparse's built-in help is sufficient.
- The CLI surface is flag-heavy (50+ flags) rather than subcommand-heavy. Click's decorator model adds boilerplate for flag-dominated interfaces without meaningful readability gains.
Config merge: builder pattern¶
build_config(args: argparse.Namespace) -> HydraFlowConfig merges config with
explicit precedence (lowest to highest):
- Defaults — Pydantic model field defaults in
HydraFlowConfig. - Config file — YAML/JSON loaded via
load_config_file(), filtered againstHydraFlowConfig.model_fields.keys()to reject unknown keys. - Environment variables — Resolved by Pydantic's
model_validate(). - CLI arguments — Highest priority; only explicitly-provided args override.
- Repo-scoped overlay — Applied post-validation for fields not set by CLI.
The builder tracks which CLI args were explicitly provided (cli_explicit set)
so that repo-scoped overlays only fill in gaps rather than clobbering intentional
CLI overrides.
Two-layer dispatch¶
hf_cli/__main__.py handles supervisor/workspace commands directly and delegates
orchestrator flags to cli.main() via a _FLAG_COMMANDS mapping. This keeps the
two concern domains (workspace management vs. orchestrator operation) separated
without requiring a shared CLI framework.
Consequences¶
Positive:
- Zero external CLI dependency — reduces supply chain surface and simplifies Docker images.
- Single, testable merge function (
build_config()) owns all config precedence logic. Adding a new config source (e.g., remote config) requires changes in one place. - Pydantic validation in
HydraFlowConfigcatches invalid combinations at startup, before any orchestrator loop runs. - The
cli_explicittracking enables repo-scoped overlays to coexist with CLI overrides without ambiguity.
Negative / Trade-offs:
- argparse lacks Click's composable command groups. If the CLI grows many subcommands (beyond current flag-based dispatch), this decision should be revisited.
- The two-layer dispatch (
hf_cli+cli.py) means CLI help is split across two parsers. Users runninghf --helpsee workspace commands; orchestrator flags requirehf start --help. - Dashboard
/api/control/*endpoints do not yet cover all CLI operations (clean, prep, scaffold, labels, audit). Parity will require either extending the API or invokingcli.main()programmatically from the dashboard.
Alternatives considered¶
- Click: Would provide richer help formatting and subcommand composition, but adds an external dependency for marginal benefit given the current flag-heavy interface.
- Typer: Built on Click with type-hint-driven signatures. Attractive for new projects, but migrating 50+ argparse flags provides no functional improvement and adds two new dependencies (Typer + Click).
- Flat config merge: Spreading merge logic across call sites would make precedence rules implicit and harder to test. The builder pattern keeps them explicit and centralized.
Related¶
- Source memory: Issue #2268
src/cli.py—build_config(),parse_args(),main()src/hf_cli/__main__.py— Two-layer CLI dispatchersrc/config.py—HydraFlowConfigPydantic model- ADR-0004 (agent cli as runtime) — related but distinct: that ADR covers agent invocation via CLI subprocesses, this ADR covers HydraFlow's own CLI