Lifecycle Scripts¶
Lifecycle scripts build, configure, and grade your sandbox. There are four: build.sh, setup.sh, answer.sh, and score.sh.
All scripts must begin with #!/usr/bin/env bashp — not #!/bin/bash. The bashp preprocessor inlines library functions like install::apt_packages and scoring::check before execution. It also implies set -ex, so only options that differ from that default need to be set explicitly (e.g. set -uo pipefail or set +e).
When each script runs¶
| Script | When | What to put here |
|---|---|---|
build.sh |
Once, at image build time | Package installs, tool downloads, static files, service enables |
setup.sh |
Fresh-session setup; see local command behavior below | Readiness waits only — leave blank when possible |
answer.sh |
During testing only | The steps a perfect candidate would take |
score.sh |
When the candidate submits | Pass/fail checks followed by scoring::report |
build.sh¶
Runs once when the VM image is built. Results are baked into the image, so sandboxes start fast.
#!/usr/bin/env bashp
install::apt_packages vim git nginx
systemctl enable --now nginx
Enable and start services here, not in setup.sh
Use systemctl enable --now <service> in build.sh. The --now flag both enables the service for boot and starts it immediately during the build, so subsequent build steps can use it, such as pulling a container image. Enablement is baked into the image; the service starts again when a new VM boots.
Sandbox network access
Sandbox VMs have full outbound internet access. Prefer installing packages in build.sh over downloading them at runtime — not because the network is unavailable, but because runtime fetches from external URLs are brittle: upstream packages move, URLs change, and rate limits break otherwise-passing items.
setup.sh¶
The platform runs setup.sh during fresh candidate or learner session setup. Locally, sandbox setup, sandbox answer, sandbox score, and sandbox test run it as part of their lifecycle sequence. Leave it blank when possible.
Plain sandbox shell, sandbox start, and sandbox attach do not run setup.sh. Background VMs keep their state across attachments and source reloads; updating the mounted script does not reset the VM or apply the script's effects.
Fresh sessions start from the built image. Services enabled in build.sh start at boot, so setup should not repeat package installation or service enablement. See the CLI command and ownership rules before switching between background development and a fresh lifecycle test.
The only work that belongs in setup.sh is waiting for things with a significant, observable startup delay that outlasts SSH availability. Kubernetes cluster readiness is the canonical case (k8s::wait_for_all). Fast-starting services like Docker do not qualify — even if a theoretical race with SSH exists, it has never caused failures in practice.
#!/usr/bin/env bashp
k8s::wait_for_all
For non-Kubernetes sandboxes, setup.sh is typically a no-op:
#!/usr/bin/env bashp
answer.sh¶
Applies the reference solution. Never runs in production. Locally, sandbox answer, sandbox score, and sandbox test run it after setup. Write it as the exact steps a correct candidate would take.
#!/usr/bin/env bashp
echo "bar" > /tmp/foo
score.sh¶
Checks the candidate's work. Each check is a function that returns 0 for pass or non-zero for fail. Run checks with scoring::check and end the script with scoring::report.
#!/usr/bin/env bashp
fileExists() {
test -f /tmp/foo
}
fileContent() {
grep -q "bar" /tmp/foo
}
scoring::check fileExists
scoring::check fileContent
scoring::report
The combined number of scoring::check and scoring::namedcheck registrations must equal total_check_count in metadata.yaml.
Scoring can run repeatedly. sandbox test runs it twice — before and after answer.sh — and production hooks may retry scoring after a transient failure. Keep checks deterministic and idempotent. A source reload does not run scoring or any other lifecycle script automatically.
Bashp helpers and local libraries¶
The CLI compiles source scripts into ordinary Bash under dist/<vm>/scripts/. A running local VM mounts that VM's dist directory at /sandbox, so it executes /sandbox/scripts/<name>.sh, not the host's Bashp source.
Functions can come from the CLI's bundled library, a vendored package under bashp-packages/libs/, or VM-local libs/ and scripts/libs/ directories. Names map to files: demo::report can be defined in cp/libs/demo/report. Referenced helpers and their transitive dependencies are inlined into the compiled script.
Since v1.25, the preprocessor tracks quotes separately inside nested $(...) and ${...} expressions. Use ordinary Bash quoting rather than adding escaping workarounds.
Verify library-only reload¶
Use a VM named cp with an image already built, then start background live reload with CLI v1.27 or later. From the sandbox directory, create these three source files:
function vendor::message() {
printf '%s\n' 'vendor-v1'
}
function demo::report() {
printf 'local-v1|%s|%s\n' "$(vendor::message)" "$(system::get_os)"
}
#!/usr/bin/env bashp
demo::report
The local function uses both the vendored helper and bundled system::get_os. After saving, execute the regenerated script explicitly:
sandbox exec --target cp -- bash /sandbox/scripts/probe.sh
On a Debian image, the output is:
local-v1|vendor-v1|debian
Change only cp/libs/demo/report from local-v1 to local-v2, or only the vendored helper from vendor-v1 to vendor-v2. Leave probe.sh unchanged. Its compiled copy updates automatically; executing it again shows the new library output without restarting the VM.
The same dependency-only reload applies to setup.sh, answer.sh, and score.sh. VM-local changes affect that VM's generated scripts. Shared vendored library changes trigger regeneration for every started VM watching that root. The bundled library is embedded in the CLI; upgrading those bundled implementations requires a CLI update.
Edit source libraries and scripts, not their generated dist/ copies. Reload does not execute the new code or replace function definitions already loaded into an attached shell.
Inspect helpers with an agent¶
CLI v1.26 introduced the read-only Bashp MCP workflow: find helpers, explain the selected implementation, then resolve the saved lifecycle script or unsaved buffer. Supply the real file context so local overrides and transitive dependencies are resolved for the correct VM.
A resolution pass is not a lifecycle, scoring-policy, or runtime test. Keep the independent content checks and full sandbox acceptance test below.
Test the full lifecycle¶
Build the current image and verify the untouched and answered states:
sandbox build && sandbox test
This passes when scoring returns zero checks before answer.sh runs, and full marks after. If it fails, the output tells you which check did not pass and at which stage.
Exit any owning shell, or stop background VMs with sandbox stop, before this test. sandbox test owns and cleans up its fresh VM session and does not enable live reload.