- Which tasks exist.
- What sandbox each task runs in.
- How a task is set up before the agent starts.
- How the agent’s work is graded and turned into one score.
Before you start
This guide assumes Valkyrie is already installed and configured — see the quickstart, configuration, and sandbox providers. You also need Docker to build the images your tasks run on.
Scaffold the service
create-benchmark-service supplies the FastAPI app, the wire protocol, and the sandbox abstraction, so the only code you write is oneBenchmarkService subclass.
1
Install the generator
2
Generate the project
-benchmark-service to the name, so create-benchmark-service swebench writes ./swebench-benchmark-service/.3
Install dependencies
main.py is the entire wiring, and stays as generated:
main.py
Implement the service
Everything else happens inbenchmark_service.py. Start from the generated ExampleBenchmark and replace it method by method; every abstract method carries a docstring describing its contract.
Load the dataset
load_datasets runs once at startup and its return value is cached on self.datasets. Key it by dataset name, then by task id; the task value itself is whatever your benchmark needs — a problem statement, a repo and commit, a path to fixture files, the expected answer, a rubric.
self.get_dataset(dataset) inside the other methods to read it back. Multiple datasets are how variants live in one service, for example a baseline dataset and a with-skills dataset.
Describe the sandbox
retrieve_task tells Valkyrie how to build the sandbox for one task: the image, the working directory, where the problem statement will be, how long the agent may run, and how much hardware it gets.
source returns:
- One shared environment — return the same
ImageSourcefor every task and do the per-task work insetup_task. - Per-task environments (each task has its own Dockerfile) — build and publish one image or snapshot per task ahead of time, and return the per-task reference here.
source options are SnapshotSource for a provider snapshot and ComposeSource when the task needs Docker Compose services. GPUs are requested through resources, for example Resources(vcpu=8, memory=32, disk=50, gpu=1, gpu_type="H100"). Nested Docker is available in every sandbox, so a benchmark that runs containers only needs a Docker-capable image and to start dockerd itself during setup.
Set up the task
setup_task runs in the live sandbox before the agent starts: write the problem statement, fetch the repo, install dependencies, start services. It is an async generator — instead of returning, it yields chunks that stream to the Valkyrie user watching the run.
The sandbox handle gives you
upload_file, download_file, exec (returns ExecResult with exit_code and output), and command for line-by-line streaming. stream_command(sandbox, command, cwd) wraps command and raises on a non-zero exit code unless you pass ignore_error=True.
Grade the result
Implement the hook that matches how your benchmark grades. Both may exist, but a benchmark normally uses one:evaluate_instance— the agent’s live sandbox is graded. Upload the tests now, run them, parse the output. This is also an async generator, and its singleStreamResultChunkis the per-task result.evaluate_response— a plain text answer is graded with no sandbox. It returns the per-task result directly.
Aggregate the score
calculate_final_score receives {task_id: result} — the values your evaluation hook produced — and returns the benchmark’s single score plus any metadata worth reporting.
A task that errored arrives as
None, so handle that case rather than assuming your own result shape.Check for reward hacking
Work through this before the first real run:- Tests are uploaded in
evaluate_instance, never insetup_task. - Reference solutions, patches, and answer keys never enter the sandbox at any point.
- The problem statement contains no evaluation criteria or expected output.
- Hints, skills, or scaffolding are injected only for the datasets meant to have them.
lsthe working directory of a live sandbox mid-run and confirm nothing evaluation-related is present.
Test against Valkyrie
You do not need to deploy to test. Run the service locally, expose it with a reverse tunnel, and point Valkyrie at the tunnel.1
Start the service
make dev runs with AUTH_DISABLED=true, which is local development only. A hosted service instead sets DESCOPE_PROJECT_ID plus a tenant allowlist and rejects unauthenticated requests.2
Register the tunnel
3
Run one task
--slice :1 and scale up only once setup, evaluation, and scoring all behave. Monitor the run to watch your message chunks stream back.Publish the service
A port is complete when the service is in the public benchmark services registry: the service lives in its own<benchmark>-benchmark-service repository, added to the registry as a Git submodule with a matching services.yaml entry.