Sandbox Profiles¶
A sandbox profile describes what one target repo needs inside the agent
sandbox: toolchains, network egress, setup and validation commands, checks the
sandbox can never run, environment variables, resources, and paths to drop
before the workdir is copied back. Callers build one with
agentic_ci.sandbox_profile.parse_profile() and hand it to the skill runner
as SkillConfig.sandbox_profile.
What takes effect in this release
Only resources takes effect today: the OpenShell backend sizes the
sandbox with it. Every other field is validated, merged and carried to the
backend, but not yet acted on. Egress presets, toolchain provisioning,
in-sandbox setup, validation runs and discard_before_download land in
the following releases.
Sandbox profiles apply only to the OpenShell backend. The Podman and local backends log a warning and ignore a profile.
Schema¶
The same shape is used in JSON (central configuration) and YAML (a repo overlay):
toolchains: # name -> version
go: auto
node: "22"
shfmt: "3.12.0"
egress: # preset names; raw host:port:access strings are central-only
- goproxy
- npm
setup: # commands run inside the sandbox before the agent
- name: modules
run: go mod download
timeout: 900
validate: # ordered validation commands
- name: lint
kind: lint # lint | build | test | generated
run: make lint
timeout: 900
skips: # checks the sandbox can never run
- match: "unshare --net"
reason: network namespaces are blocked by seccomp
env: # non-secret variables for the sandbox
CGO_ENABLED: "0"
resources: # central only
memory: 8Gi
cpu: "4"
discard_before_download:
- node_modules
overlay: merge # central only: merge | ignore
Every field is optional. A field set to null (for example an empty YAML
key) means its default.
Validation Rules¶
| Field | Rule |
|---|---|
toolchains |
Name is one of buf, go, golangci-lint, helm, kustomize, node, pnpm, protoc, python, shfmt, yq. Version matches ^[0-9]+(\.[0-9]+){0,2}$ or is exactly auto. |
egress |
Preset names are pypi, npm, goproxy, github-release-assets. An entry containing : is a raw endpoint, host:port:access[:protocol[:enforcement[:options]]], with no whitespace: host is an ASCII host name or IPv4 address, optionally starting with a *. wildcard; port is 1 to 65535; access is read-only, read-write or full; protocol is empty, rest, websocket or sql; enforcement is empty, enforce or audit, and needs a protocol; options is a comma-separated list of allow-uninspected-credentials, websocket-credential-rewrite, request-body-credential-rewrite and allowed-ip=IPV4[/BITS], and must not be empty when the field is present. |
setup, validate |
Each step has a name of letters, digits, ., _ or -, unique within its list; a non-empty run string; and a timeout in seconds from 1 to 3600 (default 600). A validate step also has a kind: lint, build, test or generated. |
skips |
Each entry has non-empty match and reason strings. |
env |
Names match ^[A-Za-z_][A-Za-z0-9_]*$. See Reserved environment variable names for the names that are rejected. Values are strings; integers are converted with str(), and booleans, decimals, null, lists and mappings are rejected. |
resources |
memory is a positive quantity string: digits with an optional unit of Ki, Mi, Gi or Ti (512Mi, 8Gi). cpu is a positive string or number such as "4", "2.5" or "500m". gpu is a non-negative integer. |
discard_before_download |
Workdir-relative paths. No absolute paths, no .. component, no empty string, not the workdir itself (.), and not .git or anything under it. Paths are normalized (./dist/ becomes dist). |
overlay |
merge (default) or ignore. |
Every list and mapping holds at most 1000 entries (MAX_ENTRIES). Every
pattern must match the whole value; a trailing newline is rejected.
Reserved environment variable names¶
Profile env goes into the same environment as the harness, so names that
would change the harness, its telemetry or credentials, the dynamic loader,
the shell, git, or TLS and proxy settings are rejected. All checks ignore
case.
- Names:
ALL_PROXY,BASH_ENV,ENV,HOME,HTTP_PROXY,HTTPS_PROXY,IFS,NODE_EXTRA_CA_CERTS,NODE_OPTIONS,NO_PROXY,PATH,PYTHONHOME,PYTHONSTARTUP,REQUESTS_CA_BUNDLE,SSL_CERT_DIR,SSL_CERT_FILE. - Prefixes:
AGENT_,ANTHROPIC_,CLAUDE_,CLOUD_ML_,CODEX_,GCP_,GIT_,GOOGLE_,LD_,OPENAI_,OTEL_,VERTEX_. - Any name containing
token,secret,key,passwordorcredential(a substring match, case-insensitive). - Any name with
pat(personal access token) as a whole_-separated word, such asPAT,GH_PATorPAT_FILE.GOPATH,PYTHONPATH,NODE_PATHandCLASSPATHare allowed.
Quote versions
YAML reads an unquoted 1.20 as the number 1.2, so the original text
cannot be recovered. Unquoted decimal versions are rejected; write
go: "1.20". Unquoted integers such as node: 22 are accepted.
Errors raise SandboxProfileError, a ValueError whose message names the
field path and the rule broken, for example
validate[2].kind: must be one of build, generated, lint, test. Messages
never include env values or run strings.
Central Profiles and Repo Overlays¶
parse_profile(data, source=...) takes source="central" for reviewed CI
configuration and source="overlay" for a file from the target repo. Central
profiles fail loudly on any problem. An overlay must not be able to break the
run or widen what central configuration allows, so the parser drops these with
a warning instead of raising:
- unknown keys, at the top level or inside a step, skip or resource entry
- unknown toolchains and egress presets
- rejected
envnames - a step whose name repeats an earlier step in the same list
- raw egress endpoints
resources(runner capacity is a central cost)overlay
Other overlay problems, such as a bad toolchain version or a step without a
run, still raise; the caller decides whether to drop the whole overlay.
Warnings quote untrusted names, so a name cannot add a line to a log or
comment, and at most 50 are returned (MAX_WARNINGS), followed by one line
counting the rest.
Merge Precedence¶
merge_profiles(central, overlay) combines the two:
- Both
None: returnsNone. - A central profile with
overlay: ignoredrops the overlay entirely, with one warning when an overlay was given. - Central scalars and map keys win:
toolchains,env,resourcesandoverlay. - Lists (
setup,validate,skips,egress,discard_before_download) are concatenated central first and de-duplicated. Steps are matched bynameand skips bymatch; an overlay step or skip that the central profile already defines is dropped with a warning. - An overlay skip is dropped with a warning when its
matchand the name orrunof a centralvalidatestep contain one another, ignoring case (for exampleTESTorunit testsagainst a stepunitthat runsmake test), so a repo cannot skip validation that central configuration requires. - Overlay egress presets outside
overlay_allowed_presets(defaultpypi,npm,goproxy) are dropped with a warning. - An overlay built directly instead of by
parse_profile()is held to the same rules: unknown toolchains, bad toolchain versions and rejectedenvnames are dropped with a warning. - An overlay with no central profile is merged into a default envelope:
overlay: merge, only allowed presets, no resources and no raw egress.
The result is a MergedProfile with the effective profile and a tuple of
warnings. It never raises for an overlay that parse_profile(...,
source="overlay") accepted.
Serialization¶
profile_to_dict() returns the shape parse_profile() accepts, so a central
profile round-trips. profile_hash() is the sha256 hex digest of that dict as
canonical JSON (sort_keys=True, compact separators).
Resources¶
When the OpenShell backend receives a profile with resources, each of
memory, cpu and gpu comes from the profile unless the caller passed that
value to the backend explicitly; explicit values win. The backend logs one line
saying where each value came from. As with explicit values, resources apply
only when the sandbox is created; see
Sandbox Resources.