Operating Modes

spaces evaluates Starlark modules in two different operating modes: rules mode and execution mode. Choosing the right mode determines whether work is planned as a dependency graph or run immediately, top-to-bottom.

Use *.spaces.star for declarative, dependency-aware workflows. Use *.exec.star for imperative scripting.

At a glance

ModeFile patternRuntime modelPrimary APIs
Rules mode*.spaces.starBuild dependency graph first, execute after evaluation/docs/reference/@star/sdk/star
Execution mode*.exec.starExecute immediately while evaluating the file/docs/reference/@star/prelude/exec

Rules mode (*.spaces.star)

In rules mode, evaluation registers rules and their dependencies. After evaluation completes, spaces executes in dependency order.

Evaluate module code

Starlark runs normally (load, variable assignments, function calls, control flow).

Register rules

Calls like run_add_exec(...) and checkout_add_repo(...) register graph nodes instead of executing commands.

Execute the graph

After all relevant modules are evaluated, spaces runs rules in dependency order and can parallelize independent nodes.

Rules mode example

build-and-test.spaces.star
load("//@star/prelude/rules/run.star", "run_add_exec")

run_add_exec(
    name = "build",
    command = "cargo",
    args = ["build", "--release"],
)

run_add_exec(
    name = "test",
    command = "cargo",
    args = ["test"],
    deps = [":build"],
)

When you run spaces run //<path>:test, spaces executes build first, then test.

Execution mode (*.exec.star)

In execution mode, built-ins execute immediately while the file is evaluated.

Evaluate top-to-bottom

Statements run in order, like a shell script.

Execute calls immediately

sh_capture(...), process_exec(...), and similar APIs run now and return values now.

Continue with returned values

Subsequent logic can branch based on live command output.

Execution mode example

status.exec.star
load("//@star/prelude/exec/sh.star", "sh_capture", "sh_exit_code")
load("//@star/prelude/exec/log.star", "log_info")

branch = sh_capture("git rev-parse --abbrev-ref HEAD")
log_info("Current branch: " + branch)

if sh_exit_code("git diff --quiet") != 0:
    log_info("Uncommitted changes detected")
else:
    log_info("Working directory is clean")

All commands above run during evaluation of status.exec.star.

Comparison

AspectRules mode (*.spaces.star)Execution mode (*.exec.star)
Execution modelDeferred graph planning, then executionImmediate execution during evaluation
ParallelismYes, for independent graph nodesNo graph-driven parallelism
IncrementalitySupports dependency-aware reuse/skipsRe-runs each statement each time
Primary API namespaceRulesExec
Typical useBuild/test/checkouts and durable workflowsScripting, probes, and one-off automation

Choosing the right mode

  • Choose rules mode when outcomes should be reproducible, dependency-aware, and scalable across teams.
  • Choose execution mode when you need direct command execution and immediate control-flow decisions.
  • It is normal to use both in one workspace: rules for durable workflows, exec scripts for operational tasks.
Common pitfall: mixing mode-specific APIs

*.spaces.star modules should use rules APIs (for example, run.*, checkout.*).

*.exec.star modules should use exec APIs (for example, sh.*, process.*, fs.*).

If you call a rules API in execution mode (or vice versa), spaces will error complaining the built-in is missing.

See also