Skip to content

Fragments and the DSL

A fragment is one .sbx file with one fragment declaration.

policies/tools/ripgrep.sbx
# What this fragment is for — plain comments are dropped.
fragment "ripgrep" {
selects_on = { commands = ["rg"] }
requires = ["toolchains/rust"]
fs {
allow_read ["$WORKDIR"] # the project being searched
allow_read ["~/.ripgreprc"] # user config
}
}
schema_version = 1the DSL version the fragment is written for. A fragment stating a version this wardcell does not read is refused before anything is applied; one that states none is read as the current version
selects_on = { ... }when this fragment applies automatically
offer_on = ...whether this fragment is a choice in the workspace setup picker, and which markers pre-check it
requires = [...]other fragments to pull in, by root-relative path without .sbx
selects_on = { commands = ["git", "gitk"], app_bundles = ["com.example.app"] }

commands matches the basename of the command being run, so git matches /usr/bin/git. app_bundles is an exact match, macOS only.

A fragment with no selects_on applies only when something names it — --profile, or another fragment’s requires.

offer_on = { group = "Toolchains", files = ["Cargo.toml", "rust-toolchain.toml"] } # offered under "Toolchains", pre-checked on these markers
offer_on = { group = "Tools" } # offered under "Tools", never pre-checked

offer_on makes a fragment a choice in the workspace setup picker. The required group is the heading it sits under — stated in the fragment, not inferred from the directory, so gh, curl and docker share a “Tools” heading without sharing a folder. The optional files pre-check the fragment when a checkout holds one near the root; they are basename patterns with * as the only wildcard ("*.xcodeproj"), matching a file or directory entry. Omit files for a tool with no repository footprint (gh, ssh): still offered under the picker’s show all, never pre-checked.

Headings show in a curated order — Toolchains, Tools, Integrations — then any other group alphabetically. offer_on is metadata only: read by the picker when you set a workspace up, never when a command runs, and kept strictly apart from selects_on, which decides what a run selects. offer_on = true/false and an object with no group are refused; omit offer_on to keep a fragment out of the picker.

Prefix a block with [linux] or [macos] and it applies only there. An untagged block applies to both.

[linux]
fs { allow_read_ancestors ["~/.ssh"] }
[macos]
fs { allow_read_metadata_ancestors ["~/.ssh"] }

# runs to end of line. A comment on the same line as a list entry or a block becomes that rule’s description and shows up in policy explain:

allow_read [
"~/.gitconfig", # User gitconfig read by the git CLI.
]
allow_http "api.example.com" { # the orders API
allow_requests ["GET /v1/**"]
}

A comment on its own line is documentation and is discarded. Use same-line comments for anything a reviewer will want when the rule is questioned.

$NAME tokens in fs paths and env values are expanded by the backend at render time, so the IR keeps exactly what you wrote. The ones you reach for most:

~, $HOMEthe user’s home directory
$WORKDIRthe directory wardcell run was invoked from
$TMPDIRthe per-run temporary directory

The DSL reference has the complete table — every context, git, macOS, proxy and plugin variable, and which ones an env value points a tool at.

field = "string" # string
field = true # bool
field = 65536 # integer
field ["a", "b"] # list — note: no `=`
block "label" { ... } # nested block with a label

Lists take no =. That is the one piece of the grammar that catches people out.

See the DSL Reference.