RFC-0002: CLI Surface
- Status: Accepted (retroactive baseline — documents behavior as of v0.6.1)
- Date: 2026-07-16
- Touches: barca-cli | barca-core
- Supersedes / Related: RFC-0006 (
--env, config precedence)
1. Summary
Section titled “1. Summary”barca is a single binary with eight subcommands (get, run, plan, history,
stats, serve, list, version) plus a bare-file shorthand. It is the only
supported way to invoke barca — python/barca/api.py shells out to this same binary
rather than reimplementing any logic in Python.
2. Motivation
Section titled “2. Motivation”Per Core Constraints and the project’s design principles, planning
must stay in Rust and must never import user code. A CLI-first design means every
consumer — a human at a terminal, barca.api shelling out, CI — goes through the exact
same static-analysis-then-execute path, with no separate “programmatic mode” that could
drift from what the CLI does.
3. Guide-Level Explanation
Section titled “3. Guide-Level Explanation”3.1 CLI
Section titled “3.1 CLI”barca get [target] <file.py> [file.py ...] Get asset value(s) — cache-awarebarca run <task> <file.py> [--burst a,b] Run a task (always re-runs)barca plan <file.py> [file.py ...] Emit the execution plan as JSONbarca history [-l N] Show recent run historybarca stats <target> <file.py> [file.py ...] Show timing/cache stats for an assetbarca serve [file.py ...] [--port N] [--watch] [--no-schedule] [--timezone TZ] Run the HTTP API serverbarca list <file.py> [file.py ...] List discovered definitions and their depsbarca version Print versionbarca --help Show helpbarca pipeline.py (bare file, no subcommand) is rewritten to barca get pipeline.py.
barca get pipeline.py # all assetsbarca get summary pipeline.py # a specific targetbarca get pipeline.py --no-cache # execute everything freshbarca get pipeline.py --agent # plain progress lines instead of a progress barbarca get pipeline.py -o value # print just the final value (also: json | pretty)
barca run deploy pipeline.py # run task + bust all upstream cachesbarca run deploy pipeline.py --burst fetch,transform # only bust named assetsEvery command that touches state (get, run, plan, serve, history, stats)
accepts --env <name> — see RFC-0006.
4. Reference-Level Explanation
Section titled “4. Reference-Level Explanation”4.1 Public API Surface
Section titled “4.1 Public API Surface”Argument dispatch. If the first positional argument to get ends in .py, every
argument is treated as a source file (materialize all assets, return the last one’s
value). Otherwise the first argument is a target asset/task name and the rest are files.
This ambiguity is intentional shorthand, not accidental — it is what makes the
bare-file rewrite (barca pipeline.py → barca get pipeline.py) unambiguous with
barca get target pipeline.py.
Exit codes. 0 on success. 1 on any failure — missing/invalid files, parse
errors, DAG validation errors (e.g. the cache-poisoning guard from
RFC-0001), or a failed step during execution.
There are no distinct exit codes per failure class; failure detail lives in the stderr
message and, for get/run/plan, in the JSON on stdout.
stdout/stderr contract. Usage and argument errors go to stderr as plain text
(error: ...\n\nUsage: ...) and never touch stdout. get/run/plan write a single
JSON object to stdout as their last line (plan may pretty-print the whole output as
JSON; get/run may interleave user print() output before the final JSON line) —
this is the exact contract python/barca/api.py’s _exec() parses: try the full
stdout as JSON, fall back to the last line. Any consumer scripting against barca must
follow the same rule: read the last line, not all of stdout, as the result.
get result shape (also what POST /run resolves to, see
RFC-0004):
{ "run_id": "1b9422cf12f3", "elapsed_seconds": 0.115, "steps_executed": 2, "phases": 1, "final_output": { "path": ".barca/artifacts/…", "format": "json", "size_bytes": 8 }}Output flag (-o). get/run accept -o value | json | pretty to control how
final_output is rendered — value prints just the deserialized value, the others
print the full JSON envelope.
serve. See RFC-0004 for the full endpoint surface;
this RFC only owns the CLI flags (--port, --watch, --no-schedule, --timezone).
history/stats. Human-readable table output (history [-l N],
stats <target> <files>). python/barca/api.py parses these tables back into dicts —
see history()/stats() in api.py — which makes their column layout part of the
load-bearing surface even though it’s rendered as a table, not JSON. A column reorder
in barca-cli’s output would silently break barca.history()/barca.stats().
4.2 Implementation Details
Section titled “4.2 Implementation Details”barca-cli (crates/barca-cli/src/main.rs) is a thin clap dispatcher: it parses
arguments and calls straight into barca_core::commands::*
(crates/barca-core/src/commands.rs), which does the real work (parse → DAG → plan →
dispatch → persist). serve is the one subcommand that hands off to a different crate
(barca-server) instead of a commands::* function — see
RFC-0004.
4.3 Rust ↔ Python Boundary
Section titled “4.3 Rust ↔ Python Boundary”Not directly touched by CLI argument parsing — the CLI’s job ends at calling into
barca-core, which owns the worker subprocess boundary (see the io_loop/protocol
internals in Architecture). The one place a Python process observes
the CLI directly is python/barca/api.py._find_binary(), which locates the barca
binary next to sys.executable (same venv) or on PATH, and warns (not errors) on a
barca --version mismatch against the installed barca Python package.
4.4 Node-Kind Semantics
Section titled “4.4 Node-Kind Semantics”Unaffected — the CLI has no node-kind-specific behavior beyond what’s already described
in RFC-0001 (get targets assets/sensors,
run targets tasks).
4.5 Edge Cases
Section titled “4.5 Edge Cases”barca geton a target that resolves to a task fails —getis for cache-aware asset retrieval; userunfor tasks.--burstonrunis meaningless without--burstnaming actual upstream assets in the task’s dependency cone; unknown names are a plan-time error.
5. Determinism, Caching & Testing
Section titled “5. Determinism, Caching & Testing”Not applicable beyond what RFC-0001/RFC-0005 already cover — the CLI is a pass-through
to barca-core’s planning and caching. Covered by
tests/integration/*.sh (shell-based CLI integration tests run in CI against the built
wheel) plus cargo test for barca-cli’s argument parsing.
6. Performance
Section titled “6. Performance”The CLI itself is the thing being measured in benchmarks/ — process startup +
arg-parse + dispatch is part of the ~4ms fixed Rust overhead cited in
Architecture Decisions §5. Any change to argument parsing
or dispatch should be checked against benchmarks/trivial/bench.sh.
7. Drawbacks
Section titled “7. Drawbacks”The get-vs-target-vs-file positional ambiguity (§4.1) is a small but real footgun —
barca get foo.py bar and barca get bar foo.py mean different things depending on
which argument ends in .py.
8. Rationale & Alternatives
Section titled “8. Rationale & Alternatives”A --target/--file flag pair (rejected) would remove the positional ambiguity but
adds ceremony to the common case (barca get pipeline.py) that this design optimizes
for. The bare-file shorthand (barca pipeline.py) was kept for the same reason —
lowest-friction path from “I have a pipeline” to “I have a value.”
9. Prior Art
Section titled “9. Prior Art”dagster asset materialize, prefect deployment run — both require more ceremony
(deployment registration, explicit flow references) than barca’s direct
file-as-argument model. See Framework Comparison.
10. Unresolved Questions
Section titled “10. Unresolved Questions”Should history/stats gain a --json flag so barca.api can parse structured
output instead of scraping table columns (§4.1)?
11. Future Possibilities
Section titled “11. Future Possibilities”The barca prune command described as intent in
Core Constraints — reclaiming disk from history unreachable from
the current DAG — does not exist yet and would need its own RFC before implementation
(destructive, opt-in operation).