Author an evaluation package
Build a stateful world, real target adapter, and protected verifier for one useful workflow.
An evaluation package contains domain-specific code that should not live in CrashLab core. Start with one behavior that would matter in a pull request, then expand coverage as the agent evolves.
Alpha authoring surface
Evaluation packages import from crashlab.alpha. The 0.1 authoring types are an
alpha compatibility candidate, not a frozen production API. Do not import from
crashlab.core.
Package shape
crashlab/
evaluations.py
pyproject.toml
uv.lock
target_hook.py # Python/uv scaffold
target_hook.mjs # Node/pnpm scaffold (alternative)
README.mdBoth supported scaffolds use a Python evaluation package because the protected world and verifier execute in CrashLab's host runtime. Only the target hook and immutable target snapshot differ by source runtime.
CrashLab infers evaluations.py:PLAN and uv.lock from this layout while
still digesting both for exact provenance.
Use the exact Python scaffold command returned by repository inspection. A root uv project commonly uses:
crashlab eval scaffold --runtime python-uv --jsonA nested requirements-based application may instead use:
crashlab eval scaffold --runtime python-requirements \
--target-project python-backend \
--requirements-file requirements.txt \
--jsonFor a repository-root Node/pnpm agent with a pinned pnpm version and lockfile, use the corresponding target shape:
crashlab eval scaffold --runtime node-pnpm --jsonThe mounted hook receives CRASHLAB_TARGET_ROOT as the immutable repository
root. In a pnpm monorepo, resolve runtime packages from the package.json that
owns the selected agent; do not assume nested dependencies are hoisted beside
the hook. Use the repository's existing locked TypeScript loader when the
production entrypoint is TypeScript.
The scaffold is intentionally incomplete. Its world operations, host-owned provider gateway, production target hook, and protected verifier fail until a coding agent replaces them with repository-specific implementations. It is an authoring aid, never passing evaluation evidence. The generated verifier shows the typed protected-evidence fields and supplies a package-local result helper, so authors do not need to reverse-engineer the alpha result constructor.
The scaffold also includes EvaluationCoverage, VerifierCoverage, and related
human-facing descriptions. Replace every placeholder even though coverage does
not execute the world or call a model. CrashLab uses it to explain what is about
to run and how it will be checked. Its title and summary are retained with the
execution request and result, so local and request-driven execution use the same
customer-facing identity.
Use python-uv for a root or nested pyproject.toml plus uv.lock, and
python-requirements for a selected requirements.txt project. CrashLab records
requirements installs as unlocked rather than pretending they are reproducible.
Use node-pnpm only for a repository-root package.json plus pnpm-lock.yaml
and a pinned packageManager field. Do not reshape a monorepo, add duplicate
root package metadata, or regenerate the target's dependency lock merely to fit
a scaffold.
No CrashLab-specific package manifest is required. pyproject.toml and
uv.lock remain necessary because the protected evaluation runtime must be
reproducible.
Design the smallest useful case
A first case should define:
- a deterministic initial world state;
- the target's natural production entrypoint;
- functional tool, MCP, API, filesystem, or subagent boundaries;
- at least one required outcome;
- at least one invariant or minefield for collateral behavior; and
- limits for time, turns, resources, and provider requests.
The world must be coherent. If ticket.create succeeds, a later ticket.get
must observe the new ticket. A list of fixed mock responses is not a stateful
world.
Describe coverage before execution
Coverage should answer five questions without constructing a trial:
- What customer-visible behavior does this evaluation protect?
- What resources and initial facts exist in the fresh simulated world?
- What can the agent read or change, and which declared operations implement those capabilities?
- What does each verifier check, by what method, and from which protected evidence?
- Which real target and orchestration path will run?
Use human names and descriptions, not internal IDs or enum values. Keep the coverage honest and compact: it is an explanation of the authored evaluation, not a second verifier. Runtime criteria and protected evidence remain authoritative after execution.
Keep boundaries explicit
World
Own the seed, dependency operations, state mutations, and causal event log outside the target. Validate operation arguments and results at the boundary.
Target adapter
Invoke the real prompts, tools, model, and orchestration. Adapt transport when needed, but do not patch the target into a known-good answer. Run target source in Docker unless the customer explicitly chooses and records the unsafe process opt-out.
Import an existing source-controlled agent, factory, graph, application entrypoint, or runnable example. Do not construct a new agent, prompt, or tool graph inside the evaluation hook and present it as the configured target. When a framework/library repository has no runnable agent, evaluate the application that uses it or stop and request the intended entrypoint. A shipped example is a valid target only when the hook imports and preserves its source-controlled behavior.
Python hooks return the explicit CrashLab payload rather than copying field names from a framework result:
return TargetOutput(
summary=str(result.final_output),
data={"run_id": str(result.run_id)},
)When a model SDK treats its base URL as an API prefix, declare that prefix on
the host gateway instead of patching it into target code. For example, an SDK
that appends /responses should receive container_base_url_path="/v1" while
the gateway allowlists the complete /v1/responses path. The target receives a
short-lived token and private URL; the long-lived provider credential remains on
the host.
Verifier
Read protected initial/final state and events. Verify outcomes and real safety properties without prescribing incidental reasoning steps. Treat attempted tool use, handler dispatch, mutation, and returned results as different evidence.
For a natural-language property that cannot be reduced to those structured
checks, add SemanticBehaviorVerifier from crashlab.alpha. Supply one
SemanticBehaviorSpec, a protected SemanticJudge, and a selector that returns
uniquely named SemanticEvidenceItem values from the EvidenceBundle.
Keep the evidence catalog bounded and intentional. Do not send an unfiltered raw
trace to the judge. Invalid catalog construction is evaluation code failure;
judge unavailability is a typed inconclusive assessment. The default severity is
advisory, but the evaluation may explicitly select Severity.REQUIRED. The
supplied OpenAI Responses implementation uses a HostSecret, exact model/path
allowlists, strict structured output, no tools, bounded requests, and
store=false; a customer may implement the provider-neutral SemanticJudge
protocol instead.
Lock and validate
The current frozen package runtime supports uv.lock:
cd crashlab
uv lock
cd ..
crashlab eval validate \
--trust-evaluation-code \
--jsonValidation checks the conventional descriptor, lock freshness, package digest,
entrypoint, plan, and cases in a fresh frozen environment. An unlocked package
requires an explicit --allow-unlocked-evaluation opt-out and is recorded as
such.
Confirm the inferred project
crashlab init --jsonThe visible crashlab/ directory already connects the repository, so
initialization does not write another configuration file. It validates the
inferred package and returns the exact smoke and comparison commands. Treat each
returned command as an argv array, not a shell string:
crashlab run --repetitions 1 --trust-evaluation-code
crashlab check --repetitions 1 --trust-evaluation-codeProve the verifier can reject bad behavior
Before relying on a case, exercise counterexamples:
- wrong or missing state mutation;
- collateral changes to protected records;
- forbidden or destructive operation attempts;
- forged success text without dependency dispatch;
- missing or reordered causal milestones;
- duplicated mutation after a retry; and
- unavailable required verifier evidence.
A verifier is only as valuable as the failures it can distinguish.