Skip to content
bench CLI

bench CLI

bench is the CLI for LabBench, the composition layer that scaffolds and builds new modular services. You describe a service in a bench.yaml spec, and bench validates it, generates a working skeleton, and compiles it into a deployable OCI image.

This page is the quickstart. The LabBench project and its architecture doc are the canonical sources.

The model

The spec bench reads is a three-level hierarchy:

  • A bench is the workspace — one Git repository, described by a single bench.yaml. It holds one or more assemblies.
  • An assembly produces one independently deployable OCI image running as one process. It composes one or more same-language services, and is the unit that gets built and deployed.
  • A service is the smallest unit you declare: an inbound endpoint (an HTTP route, a gRPC method, or a NATS subject), the handler that answers it, and its outbound backends. It is the developer-owned business logic that the assembly wires onto the shared chassis.

The word “service” is overloaded: here it means the entity inside a bench.yaml, not a deployed platform service. Renaming it is tracked in bench#52.

A service can appear in more than one assembly, but each assembly carries its own copy of the declaration — there is no shared reference across assemblies. Every assembly in a bench shares one runtime, since the scaffolded repository has a single toolchain root.

Install

There are no binary releases yet. Build bench from source. You need Rust and protoc (the Protocol Buffers compiler: brew install protobuf, or apt install protobuf-compiler on Debian/Ubuntu).

cargo install --git https://gitlab.com/gitlab-org/lab-bench/bench bench-cli

Cargo compiles and drops the bench binary in ~/.cargo/bin/; make sure that is on your PATH. Verify:

bench --help

To scaffold and build a real service you also need copier (scaffolding), plus the toolchain for your assembly’s language (Go or Rust). A --release build additionally needs docker or podman to package the image.

Quickstart

Declare a bench with one Go assembly and a service, scaffold it, build it, and run it.

1. Create the bench spec

bench init content-scanner

This writes bench.yaml seeded with the bench name. bench reads bench.yaml by default; override it anywhere with -C/--config.

2. Add an assembly and a service

bench assembly create --runtime go --port 8080 \
  --description "Scans uploaded content for policy violations" scanner
bench service create --description "Analyzes a document and returns findings" \
  scanner analyze

--runtime is required and picks the language (go or rust). --port publishes an inbound listener; omit it for a subscriber-style assembly whose services only consume NATS subjects and publish no inbound port. Add --protocol grpc for a gRPC listener (defaults to http).

3. Declare the service’s endpoint

The generator derives the verb, route, and handler from the spec, not from the service name. Set them with bench set using dotted paths:

bench set assemblies.scanner.services.analyze.inbound.http.0.methods.0 POST
bench set assemblies.scanner.services.analyze.inbound.http.0.route /analyze
bench set assemblies.scanner.services.analyze.inbound.http.0.handler.go.pkg  analyze/handler
bench set assemblies.scanner.services.analyze.inbound.http.0.handler.go.func AnalyzeHandle

func must name an exported method — the generated entrypoint calls it across a package boundary, and bench check enforces it.

Optionally validate the spec at any point (scaffold runs this check for you):

bench check

4. Scaffold the project

bench scaffold --group gitlab-org/my-group

scaffold wraps copier against the paved-path template and renders a working project into the current directory: a toolchain root with CI, CODEOWNERS, and manifests, plus a handler skeleton for each service. --group sets the GitLab namespace the template renders ownership and CI against (it becomes gitlab_namespace in .copier-answers.yml); it must start with gitlab-org/.

5. Build

bench build compiles an assembly, working the same way for Go and Rust. Two paths:

bench build                     # a native binary, for local iteration
bench build --release --tag v1  # a deployable, cross-compiled OCI image

--release cross-compiles the assembly for Linux, generates a Dockerfile and a Fairway manifest (.runway/<assembly>/fairway.yaml), and packages an OCI image via docker or podman. Use --platform linux/arm64 to target a specific architecture. Set default_registry first so the image is named against your project’s registry:

bench set default_registry '"registry.gitlab.com/gitlab-org/my-group/content-scanner"'

6. Run it

Run the native binary from the build directory and exercise the route:

./target/debug/scanner/scanner &
curl -sf localhost:9090/-/liveness
curl -sf -XPOST localhost:9090/analyze \
  -H 'Content-Type: application/json' -d '{"document":"..."}'

The assembly serves its declared listener port (8080 here) and the framework adds health endpoints like /-/liveness automatically.

Command reference

Global flags apply to every subcommand:

FlagDefaultPurpose
-C, --config <PATH>bench.yamlSpec file to read and edit
-F, --format <FMT>inferred from extensiontoml, json, yaml, or textproto
CommandWhat it does
init <name>Create a new bench.yaml seeded with the name.
checkValidate the spec against the schema.
assembly create --runtime <go|rust> [--port N] [--protocol http|grpc] [--description ...] <name>Add an assembly.
service create [--description ...] <assembly> <service>Add a service to an assembly.
show [targets...] (alias ls)List matching assemblies and services.
get <paths...> / get -aRead config values by dotted path; -a dumps all paths.
set <path> <value>Set a config value by dotted path (value parsed as JSON).
scaffold --group gitlab-org/<group>Render the spec into a working project via the copier template.
build [--release] [--platform ...] [--tag ...]Compile an assembly; --release cross-compiles for Linux and packages an OCI image.
convert <src> <dst> [--dst-format ...]Round-trip the spec between TOML, JSON, YAML, and textproto.

Run any subcommand with --help for its full flag set.

Status

bench is in active development. What ships today:

  • Runtimes: Go and Rust assemblies build and package.
  • Endpoints: HTTP routes are wired into the built binary. gRPC methods and NATS subjects are fully modeled in the spec — you can declare them and bench check validates them — but the build does not yet mount them onto the chassis, so they are absent from the binary and bench build warns when it skips them.
  • Spec editing: init, check, assembly/service create, get/set, show, convert are all available.
  • Scaffold and build: scaffold renders the paved-path template; build and build --release produce a native binary and a cross-compiled OCI image plus a Fairway manifest.

Current limitations:

  • One assembly per bench, for now. bench build --release emits one OCI image and one Fairway manifest per assembly, but the release-platform CI component deploys only one manifest per repository. A single-assembly bench is the supported case today; multi-assembly deployment is tracked in bench#57.
  • Single-language assemblies. Every assembly is one runtime (Go or Rust); polyglot single-binary assemblies (RUNTIME_MULTI) are reserved and rejected by validation.

Not yet shipped: PREP evidence generation ( bench#60). Track the Phase 1 epic for milestones.

See also

Last updated on