Sandbox Profile¶
sandbox_profile
¶
Sandbox profiles: what a target repo needs inside the agent sandbox.
A sandbox profile describes the toolchains, egress presets, in-sandbox setup and validation commands, declared skips, environment variables, resources and download exclusions for one target repo. The same shape is used in two places:
- Central profiles come from reviewed CI configuration (for example
autofix.json) and may set everything, including raw egress endpoints,
resourcesandoverlay. - Overlay profiles come from the target repo (the
sandbox:section of.agentic-ci/config.ymlon its base branch). A repo file must not be able to break the run or widen what central config allows, so fields an overlay may not set are dropped with a warning instead of failing the parse.
:func:parse_profile validates raw YAML/JSON data into an immutable
:class:SandboxProfile, :func:merge_profiles combines a central profile
with an overlay, and :func:profile_to_dict / :func:profile_hash serialize
one. SkillConfig.sandbox_profile carries the result to the backend.
In this release only resources takes effect (OpenShellBackend sizes
the sandbox with it). The other fields are validated and carried but not yet
acted on.
Error and warning messages name the field path (for example
validate[2].kind) and the rule broken. They never include env values
or run strings, which can hold anything.
KNOWN_TOOLCHAINS = frozenset({'buf', 'go', 'golangci-lint', 'helm', 'kustomize', 'node', 'pnpm', 'protoc', 'python', 'shfmt', 'yq'})
module-attribute
¶
Toolchain names a profile may request. A later release replaces this with a catalog.
KNOWN_EGRESS_PRESETS = frozenset({'github-release-assets', 'goproxy', 'npm', 'pypi'})
module-attribute
¶
Egress preset names a profile may request. A later release attaches endpoint lists.
DEFAULT_OVERLAY_ALLOWED_PRESETS = frozenset({'goproxy', 'npm', 'pypi'})
module-attribute
¶
Egress presets a repo overlay may add unless the caller allows others.
FrozenMap(items=())
¶
Bases: Mapping[str, str]
Immutable, hashable str -> str mapping with keys kept in sorted order.
Source code in src/agentic_ci/sandbox_profile.py
SetupStep(name, run, timeout=DEFAULT_STEP_TIMEOUT)
dataclass
¶
A command run inside the sandbox before the agent starts.
ValidateStep(name, kind, run, timeout=DEFAULT_STEP_TIMEOUT)
dataclass
¶
A validation command; kind is one of lint, build, test, generated.
Skip(match, reason)
dataclass
¶
A check the sandbox can never run, recorded instead of attempted.
Resources(memory=None, cpu=None, gpu=None)
dataclass
¶
Sandbox size. None leaves that limit to OpenShell's default.
SandboxProfile(toolchains=FrozenMap(), egress=(), raw_egress=(), setup=(), validate=(), skips=(), env=FrozenMap(), resources=None, discard_before_download=(), overlay='merge')
dataclass
¶
A validated, immutable sandbox profile.
Build one with :func:parse_profile. Mappings passed to the constructor
are frozen and sequences turned into tuples, so a profile cannot change
after it is created. A Resources with no limit set becomes None,
so equivalent profiles compare and hash the same.
ParsedProfile(profile, warnings=())
dataclass
¶
Result of :func:parse_profile: the profile and any non-fatal warnings.
MergedProfile(profile, warnings=())
dataclass
¶
Result of :func:merge_profiles: the effective profile and merge warnings.
SandboxProfileError(path, rule)
¶
parse_profile(data, *, source)
¶
Validate raw profile data (the YAML/JSON shape) into a :class:SandboxProfile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The profile mapping, as loaded from JSON or YAML. A missing
( |
required |
source
|
Source
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
ParsedProfile
|
class: |
|
most |
ParsedProfile
|
data: |
ParsedProfile
|
yields no warnings: anything wrong raises. |
Raises:
| Type | Description |
|---|---|
SandboxProfileError
|
The data breaks a rule. For an overlay, unknown
keys, unknown toolchains and egress presets, rejected |
Rules worth knowing when writing a profile:
- Quote toolchain versions (
go: "1.20"). Integers are accepted and converted, but an unquoted decimal is rejected because YAML has already turned1.20into1.2. envvalues must be strings. Integers are converted withstr(); booleans, decimals, null, lists and mappings are rejected.egressmixes preset names and rawhost:port:accessendpoints; an entry containing:is a raw endpoint.- Each list or mapping holds at most :data:
MAX_ENTRIESentries.
Source code in src/agentic_ci/sandbox_profile.py
merge_profiles(central, overlay, *, overlay_allowed_presets=DEFAULT_OVERLAY_ALLOWED_PRESETS)
¶
Combine a central profile with a repo overlay.
Central scalars and map keys win (toolchains, env, resources,
overlay). Lists (setup, validate, skips, egress,
discard_before_download) are concatenated central first and
de-duplicated; an overlay step whose name the central list already uses
is dropped. An overlay skip is dropped when the central profile has a
skip with the same match, or when its match and the name or run
of a central validate step contain one another (ignoring case), so a
repo cannot skip validation that central configuration requires. Overlay egress presets outside
overlay_allowed_presets, overlay raw endpoints and overlay
resources are dropped. A central overlay: ignore drops the
overlay entirely.
The overlay is held to the same envelope even when it was built directly
instead of by :func:parse_profile: unknown toolchains, bad toolchain
versions, rejected env names and discard_before_download paths that
are absolute, contain .. or name the workdir or .git are dropped
with a warning.
An overlay without a central profile is merged into a default envelope:
overlay: merge, no resources and no raw egress.
Returns:
| Type | Description |
|---|---|
MergedProfile | None
|
|
MergedProfile | None
|
with the effective profile and any warnings (at most |
MergedProfile | None
|
data: |
MergedProfile | None
|
overlay that |
Source code in src/agentic_ci/sandbox_profile.py
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | |
profile_to_dict(profile)
¶
Serialize profile to the shape :func:parse_profile accepts.
Preset names come before raw endpoints in egress. resources is
omitted when unset, as are its unset members.
Source code in src/agentic_ci/sandbox_profile.py
profile_hash(profile)
¶
Return the sha256 hex digest of profile's canonical JSON form.