3.7 Workflow Orchestration (Outside the Graph)

Bazel makes a very strong promise when you type a command like bazel test //...: inspect the graph implied by the request, then produce declared outputs and test results under Bazel's output tree. That promise is the reason Bazel can be incremental and cacheable. It is also the reason some useful repository work starts to feel awkward when you try to hide it inside a BUILD target.

The maintainer trap is to treat every repeated workflow as "something Bazel should build." A release archive of the whole checkout, a bazel query ... selection piped into bazel test, a Gazelle rewrite, a coverage report uploaded to CI, or a bazel run //docs:publish step are not all the same kind of thing. Some are graph requests. Some are side effects. Some are scripts around Bazel. This section is about seeing that boundary early enough to design the workflow in the right layer.

Bazel Is The Engine, Not Every Workflow

The sequence starts with 3.7.1 Bazel's Output Boundary because the boundary has to be visible before the patterns make sense. Bazel owns graph inspection and declared outputs. The workspace symlinks, source tree, deployment target, CI artifact store, and comparison against another revision sit outside that core contract. Once you can name the boundary, you can stop asking whether every task can be squeezed into one more rule.

3.7.2 Workflows Outside Bazel then turns the boundary into day-to-day maintainer judgment. Whole-repo fan-in, two-snapshot checks, source-tree rewrites, publish flows, and coverage reporting still use Bazel, but they usually need a wrapper step, a bazel run verb, or a task runner around the Bazel invocation. Let Bazel do the graph work and put the orchestration around it.

3.7.3 Aspect Extension Language (AXL) is the optional scaling answer. If the outer workflow is still one shell line, keep it one shell line. If it becomes a real program that coordinates multiple Bazel calls, listens to build events, or drives repo-wide tools, AXL gives that layer a Starlark shape without pretending it is a BUILD rule.

The Mental Shift: Shape Before Tool

The organizing idea is the shape of the request. A normal Bazel request is rooted in targets: build this target, test this target set, inspect this graph. Workflow orchestration starts when the request is instead rooted in an operation: rewrite files, compare this commit with another commit, collect outputs from unrelated targets, publish a result, or turn raw coverage into a report.

That distinction connects this section back to 2.2 Three Phases of a Build. Bazel's loading, analysis, and execution phases work best when the output contract is declared before actions run. A task that mutates the checkout or decides what to do after reading CI artifacts does not fit that contract cleanly. It may still call Bazel, and often should, but it should not be disguised as an ordinary build action.

It also separates this section from 3.5 Developer Experience & Local Tooling. Developer-experience tooling asks how people invoke Bazel comfortably: IDE sync, wrappers, plugins, local tools. Workflow orchestration asks a stricter architectural question: where does this repeated job live relative to Bazel's graph and output boundary?

The Smell Of A Fake Target

Most mistakes in this area have the same smell: a target exists only so some other workflow can pretend the repository is one artifact. A giant collector target, a whole-repo glob(["**/*"]), or a rule that writes back into the source tree is usually a sign that the maintainer is using the build graph as a task runner.

That does not make every side effect wrong. It means the side effect should be explicit. bazel build and bazel test stay inside the declared-output model. bazel run can cross the boundary on purpose. A script or AXL task can ask Bazel for labels, outputs, or build events, then do the part that Bazel should not model as a cached action.

think

Decide: A release task builds a declared archive, compares it with another revision, and uploads it to a registry. Which parts belong in Bazel's target graph, and which belong in an outer workflow?

Reveal

Producing the archive from declared inputs belongs in Bazel because it is a cacheable target output. Fetching or comparing another revision and uploading to a registry are procedures with external state or side effects, so a script, CI job, or task runner should orchestrate them. The outer workflow can call Bazel for the artifact and graph facts without pretending the whole release is one build action.

How To Read The Section

Read 3.7.1 Bazel's Output Boundary first if you have ever wondered why a perfectly reasonable repo task becomes strange inside a BUILD file. It gives the vocabulary for "inside" and "outside" the graph.

Then read 3.7.2 Workflows Outside Bazel as a checklist for recurring maintainer work. This is the article to revisit when someone proposes a collector target, a generated archive, a compare-against-main check, a Gazelle-in-the-build trick, or a coverage publishing flow.

Treat 3.7.3 Aspect Extension Language (AXL) as enrichment unless you are already maintaining a serious task layer. It matters because it shows the direction a mature outer workflow can take, but the core skill for Level 3 is simpler: recognize when Bazel should be the build engine inside a larger workflow, not the container for the whole workflow.

key takeaway

Workflow orchestration starts where Bazel's declared-output contract ends. Keep graph inspection and artifact production in Bazel. Put source-tree writes, cross-revision comparison, repo-wide fan-in, reporting, and publishing in an explicit outer layer. The maintainer skill is not choosing between Bazel and scripts. It is choosing the right boundary between them.