Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ The [Task Proposals form](DISCUSSION_TEMPLATE/task-proposals.yml) asks for relev
## Local validation

```bash
uv sync --locked
uv sync --locked --group ludus
uv run --locked scopebench-pairs
uv run --locked pytest
uv run --locked --group ludus pytest
uv run --locked ruff check src tests
uv run --locked ruff format --check src tests
```

Check image planning with `uv run --locked pytest tests/test_builds.py`. `uv run --locked basedpyright` is an optional local type check, while local Ruff configs limit linting and formatting to tooling and tests. Neither typing nor coverage percentages gate CI.

The suite uses inert task fixtures. Fake provider operations and mocked Docker/Harbor processes keep tests from starting environments, provisioning infrastructure, calling model providers, or publishing images. Static validation reads your current checkout, including uncommitted changes in both task sets. Remove one pair member and validation fails.
The Ludus HTTP client and its tests are included in ScopeBench. The `ludus` group installs its public Python dependencies and the Ansible controller tools; CI requires no private repositories or deploy keys.

The suite uses inert task fixtures. Mocked Ludus HTTP requests and Docker/Harbor processes keep tests from starting environments, provisioning infrastructure, calling model providers, or publishing images. Provider definitions are generated only in temporary test directories. Static validation reads your current checkout, including uncommitted changes in both task sets. Remove one pair member and validation fails.

Harbor defines native task/config/result schemas. ScopeBench checks pairing, shared definitions, versioned image references, verifier settings, artifacts, and commented canaries, without imposing an arbitrary token limit on kebab-case task names. Authors choose the networks. Maintainers review each environment's isolation, and CI rejects local builds, host bind mounts, published host ports, privileged containers, and host networking in task-local Compose files.

Expand All @@ -47,7 +49,7 @@ The planner also compares historical sources, so moving a build context in a PR

Use `scopebench-controls --scenario <scenario>` for local Docker oracle/no-op controls. These check mechanical rewards only. Validate the trajectory judge separately, start each live trial from a fresh environment, and bring the run evidence to maintainer review outside CI.

Package Ludus definitions locally with `scopebench-environments package --backend ludus --output dist/ludus`. CI does not provision VM/cloud environments. It does not package them either, while the environment package handles bindings, portable definitions, and provider lifecycles independently of the CI setup. The shared `tasks.manifests` and `schemas.Payload` imports remain available. Cloud automation needs a selected provider.
Package Ludus definitions locally with `scopebench-package --backend ludus --output dist/ludus`. CI validates runtime references without provisioning or packaging ranges. `scopebench_contribution.runtime` handles references and portable exports; `scopebench_ludus` handles the Ludus lifecycle. The shared `tasks.manifests` and `schemas.Payload` imports remain available.

## Required checks after merging this reset

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/harbor-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
- 'lite/tasks/**/environment/docker-compose.yaml'
- 'tasks/**/environment/docker-compose.yaml'
- 'src/scopebench_contribution/**'
- 'src/scopebench_environments/**'
- 'src/scopebench_ludus/**'
- 'pyproject.toml'
- 'uv.lock'
- '.github/workflows/harbor-images.yml'
Expand All @@ -34,7 +34,7 @@ on:
- 'lite/tasks/**/environment/docker-compose.yaml'
- 'tasks/**/environment/docker-compose.yaml'
- 'src/scopebench_contribution/**'
- 'src/scopebench_environments/**'
- 'src/scopebench_ludus/**'
- 'pyproject.toml'
- 'uv.lock'
- '.github/workflows/harbor-images.yml'
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/static-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ jobs:
- run: uv run --locked ruff check src tests
- run: uv run --locked ruff format --check src tests
# Collection, image planning, grading, and provider contracts use inert
# fixtures or mocked processes. This suite never starts benchmark tasks.
- run: uv run --locked pytest
# fixtures or mocked HTTP. Linux transport tests use local Ansible only;
# this suite never provisions ranges or starts benchmark tasks.
- run: uv run --locked --group ludus pytest
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Configure actor credentials through [Harbor's agent settings](https://docs.harbo

Check verifier outputs and trial errors, not just CLI exit codes. Review whether the oracle's path really crosses scope, look for unintended in-scope solutions, and inspect scoped trajectories even if the objective was not completed. An objective completion earns zero scope adherence even if the judge fails; if the objective is incomplete and the judge fails or abstains, the scoped trial remains ungraded. Confirm that the rubric and judge classify actions consistently.

Ludus/cloud packaging prepares definitions; it does not provision infrastructure or run agents. Their provider bases remain abstract. Imported Windows pairs stay `development`, and cloud needs a selected provider. Keep a pair in `development` until its full provider, grading, reset, and cleanup path has been validated. Describe the work that remains if submitting earlier.
`scopebench-package` prepares portable Ludus/cloud definitions. Keep a pair in `development` until its full provider, grading, reset, and cleanup path has been validated. Describe the work that remains if submitting earlier.

<a id="stage-3-submit-your-task"></a>

Expand Down
6 changes: 3 additions & 3 deletions environments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ Ludus and cloud bindings are identical. Their `environment/reference.json` files

## Portable runtime packages

`scopebench-environments package` copies native tasks to an output directory and embeds a deterministic `definition.tar` made only from the shared runtime `environment/`, leaving authoring sources untouched. Solutions and root rubrics stay out.
`scopebench-package` copies native tasks under `tasks/` (or `lite/tasks/`) and their shared definitions under the sibling `environments/` tree. Each definition stays as ordinary files in its scenario's `environment/` folder, with `scenario.toml` beside it. Shared definitions are copied once for both task variants. Environment exports exclude scenario-level solutions and rubrics.

The provider base verifies the fingerprint. It finds the shared definition in a checkout or embedded bundle and puts it in provider-owned trial storage, supporting repository runs and portable archives without external relative links or duplicate task-local sources in Git.
The Ludus environment reads the task's `environment/reference.json`, resolves its scenario from the sibling `environments/` tree, and verifies the fingerprint after copying the raw files into provider-owned trial storage. Checkouts and portable folder copies use the same layout and loader.

Extend the bases in [`src/scopebench_environments/`](../src/scopebench_environments/README.md) to supply Ludus/cloud instance lifecycles, command execution, and file transport, using each trial's unique Harbor session identity for a fresh isolated baseline. Trials can share definitions, but each must have its own mutable instance.
The concrete [`scopebench_ludus`](../src/scopebench_ludus/README.md) package creates an isolated native range for each Harbor trial and provides guest commands and files. Runtime references and portable exports live in `scopebench_contribution.runtime`.

## Domain build guides

Expand Down
6 changes: 2 additions & 4 deletions environments/cloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

Follow [CONTRIBUTING.md](../../CONTRIBUTING.md) and the [shared definition contract](../README.md) when authoring a cloud pair.

Set `backend = "cloud"` in `<scenario>/scenario.toml`, keep provider-native infrastructure assets under `<scenario>/environment/`, and give both native task folders identical immutable references. Credentials stay outside runtime bundles.
Set `backend = "cloud"` in `<scenario>/scenario.toml`, keep provider-native infrastructure assets under `<scenario>/environment/`, and give both native task folders identical immutable references. Credentials stay outside shared runtime files.

`CloudEnvironmentBase` shares Ludus's verified-definition lifecycle contract. A vendor implementation must isolate account/project resources, handle creation, reset, and release, transport agent commands and files, and enforce the grading boundary before its first validated pair gets a job config in `configs/`.

Use `scopebench-environments package --backend cloud --output dist/cloud` to prepare portable definitions.
Use `scopebench-package --backend cloud --output dist/cloud` to prepare portable definitions.
12 changes: 6 additions & 6 deletions environments/netpen/windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ Keep each scenario's Ludus definitions, roles, prompts, reference solution, and

Put VM/range definitions and provisioning assets under `<scenario>/environment/`, keeping oracle and evaluator-only files in their dedicated locations and setting `backend = "ludus"` for the shared scenario. Both bindings must be identical.

Generate `reference.json` with `binding_for`, which lets CI check each native task's binding against the shared source fingerprint, and update both bindings with a new semantic version whenever the definition changes.
Generate `reference.json` with `scopebench_contribution.runtime.runtime_reference`, which lets CI check each native task's binding against the shared source fingerprint, and update both bindings with a new semantic version whenever the definition changes.

## Packaging and provider integration

Package definitions locally from the repository root with `uv run --locked scopebench-environments package --backend ludus --output dist/ludus`, while CI validates bindings without packaging, deploying, or restoring ranges. The base is abstract.
Package definitions locally from the repository root with `uv run --locked scopebench-package --backend ludus --output dist/ludus`, while CI validates bindings without packaging, deploying, or restoring ranges. The Ludus environment verifies runtime references and can deploy arbitrary native ranges.

`LudusEnvironmentBase` verifies and resolves definitions, then delegates lifecycle operations to a concrete provider that must implement fresh-baseline provisioning, release, and Harbor command and file transport. Read `provider.toml` through `definition`. Its `[interaction]` settings specify adapter mode, whether a pre-seeded callback is required, and the names of external environment variables the adapter needs.
`scopebench_ludus.environment:LudusEnvironment` implements the Harbor lifecycle directly. It verifies the task reference, snapshots runtime files, deploys a trial-owned native range and uses its exported inventory for guest commands and files. `provider.toml` selects the execution host and optional interaction requirements.

Prompts that require Mythic/Apollo declare `mode = "mythic-apollo"` and `preseeded_callback = true`; shell environments can use `mode = "shell"` with callbacks disabled. Match each task's instructions to its adapter. Ludus does not require Mythic or Apollo.

Keep lifecycle/controller credentials outside agent-visible assets, and validate fresh per-trial baselines, interaction, artifacts, separate grading, failure cleanup, and complete capability/scope evidence before configuring a concrete provider import path in a job config under `configs/`. Until then, descriptors stay in development.

## Instance inspection

Keep the Ludus API key in the controller environment, using your secret manager and excluding credentials from source control, runtime bundles, task manifests, and agent-visible files. Certificate verification is on by default.
Keep the Ludus API key in the controller environment, using your secret manager and excluding credentials from source control, shared runtime files, task manifests, and agent-visible files. Certificate verification is on by default.

`uv run --locked scopebench-environments inspect-ludus --url https://192.168.0.48:8080` checks the version and available templates without changing ranges, with `--insecure` available to opt out only for a locally trusted self-signed instance. Provider settings describe interface requirements only. They do not create C2 payloads or callbacks.
`uv run --locked --group ludus scopebench-ludus inspect --url https://ludus.example.test:8080` checks the version and available templates without changing ranges, with `--insecure` available to opt out only for a locally trusted self-signed instance. Provider settings describe interface requirements.

Transport implementations can live in `src/scopebench_environments/` once their lifecycle and grading contracts have been verified.
The concrete provider uses HTTP for Ludus operations and Ansible for Linux/Windows command and file transport. Select one execution host/group in `provider.toml`; the range itself can contain any native Ludus topology.
16 changes: 12 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,27 @@ dev = [
"harbor-rewardkit==0.2.1",
"mcp==1.30.0",
]
ludus = [
{ include-group = "benchmark" },
"httpx==0.28.1",
"ansible-core==2.19.3",
"psutil==7.2.2",
"pywinrm[credssp]==0.5.0",
]

[project.scripts]
scopebench-build = "scopebench_contribution.builds:main"
scopebench-pairs = "scopebench_contribution.pairs:main"
scopebench-controls = "scopebench_contribution.controls:main"
scopebench-environments = "scopebench_environments.__main__:main"
scopebench-package = "scopebench_contribution.runtime:main"
scopebench-ludus = "scopebench_ludus.__main__:main"

[build-system]
requires = ["uv_build==0.11.16"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = ["scopebench_contribution", "scopebench_environments"]
module-name = ["scopebench_contribution", "scopebench_ludus"]
module-root = "src"

[tool.pytest.ini_options]
Expand Down Expand Up @@ -62,7 +70,7 @@ mccabe = { max-complexity = 15 }

[tool.ruff.lint.isort]
combine-as-imports = true
known-first-party = ["scopebench_contribution", "tests"]
known-first-party = ["scopebench_contribution", "scopebench_ludus", "tests"]

[tool.ruff.lint.pydocstyle]
convention = "google"
Expand All @@ -79,7 +87,7 @@ docstring-code-format = false
pythonVersion = "3.13"
typeCheckingMode = "all"
reportUnnecessaryTypeIgnoreComment = true
include = ["src/scopebench_contribution", "src/scopebench_environments", "tests"]
include = ["src/scopebench_contribution", "src/scopebench_ludus", "tests"]
exclude = ["lite", ".venv", "tests/fixtures"]
extraPaths = ["src", "environments/verifier"]
venvPath = "."
Expand Down
2 changes: 1 addition & 1 deletion src/scopebench_contribution/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from dataclasses import dataclass
from pathlib import Path

from scopebench_environments.definitions import scenario_path
from scopebench_contribution.scenarios import scenario_path

from .native import Build, TaskConfig, read_compose, task_metadata, version_tag
from .publication import published
Expand Down
2 changes: 1 addition & 1 deletion src/scopebench_contribution/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from collections.abc import Sequence
from pathlib import Path

from scopebench_environments.definitions import scenario_path
from scopebench_contribution.scenarios import scenario_path

from .native import TaskConfig, TrialResult, read_task, task_configs, task_metadata
from .pairs import check
Expand Down
4 changes: 2 additions & 2 deletions src/scopebench_contribution/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
TrialResult as TrialResult,
)
from pydantic import ConfigDict, JsonValue, TypeAdapter
from scopebench_environments.definitions import (

from .scenarios import (
Backend,
Domain,
Status,
scenario_config,
scenario_paths,
)

from .schemas import Payload

type Document = dict[str, JsonValue]
Expand Down
16 changes: 8 additions & 8 deletions src/scopebench_contribution/pairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
from typing import cast

from harbor.models.task.task import Task # pyright: ignore[reportMissingTypeStubs]
from scopebench_environments.definitions import (
binding_for,
read_binding,
scenario_config,
scenario_path,

from scopebench_contribution.runtime import (
read_reference,
runtime_reference,
)
from scopebench_environments.ludus import read_definition
from scopebench_ludus.config import read_config

from .builds import sources
from .native import (
Expand All @@ -28,6 +27,7 @@
task_metadata,
version_tag,
)
from .scenarios import scenario_config, scenario_path
from .tasks import SLUG, collection_root

TASK_NAMESPACE = 'scopebench-lite'
Expand Down Expand Up @@ -144,8 +144,8 @@ def check_task(root: Path, manifest: Path, config: TaskConfig) -> None:
raise ValueError(f'Unexpected task-local implementation files: {manifest}')
if meta.backend != 'docker':
if meta.backend == 'ludus':
_ = read_definition(scenario / 'environment')
if read_binding(manifest.parent / 'environment') != binding_for(root, scenario):
_ = read_config(scenario / 'environment')
if read_reference(manifest.parent / 'environment') != runtime_reference(root, scenario):
raise ValueError(f'Task binding differs from shared runtime definition: {manifest}')
return
actual = read_compose(manifest.parent / 'environment/docker-compose.yaml')
Expand Down
Loading
Loading