Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ef70351
verify: fc-verify with status, classify, report, review and automatic…
ArjunS07 Oct 19, 2026
7fcb5eb
verify: precheck stage (patch applies, base edits, benchmark coverage…
ArjunS07 Oct 19, 2026
61f41a8
verify: build, pin, scrub and publish stages for task images
ArjunS07 Oct 19, 2026
37fb966
verify: measure stage, measure gate service and NopTouch; template lo…
ArjunS07 Oct 19, 2026
964e046
verify: test reads the job config with Path
ArjunS07 Oct 19, 2026
c5f37e1
verify: release readiness (registry, template commits, runs, CPU type…
ArjunS07 Oct 19, 2026
3d3c26b
verify: snapshot capture stage
ArjunS07 Oct 19, 2026
2ed3f64
verify: port the build guard, disk stop and image check tests
ArjunS07 Oct 19, 2026
493750c
verify: PR number from the image name too
ArjunS07 Oct 19, 2026
6759111
verify: an empty pin reads as no pin
ArjunS07 Oct 19, 2026
08dca8a
datasmith: load dotenv only when tokens.env exists, so the verify sta…
ArjunS07 Oct 19, 2026
f76f520
verify: the command imports with the standard library, so build hosts…
ArjunS07 Oct 19, 2026
c8ace12
verify: push verified manifest rows to the registry as they are
ArjunS07 Oct 19, 2026
a2faa02
verify: select (records, database candidates, sampler) and env-image …
ArjunS07 Oct 19, 2026
764896c
verify: port the sampler order and replacement tests
ArjunS07 Oct 19, 2026
7887c21
Merge remote-tracking branch 'origin/release/template-v1' into verify…
ArjunS07 Oct 19, 2026
fe662ba
verify: apply approved prompt text to rendered tasks
ArjunS07 Oct 19, 2026
10eeee4
prompts: short (symptom-only) prompts by default for new tasks, a pro…
ArjunS07 Oct 19, 2026
38c2048
Revert an unrelated lint change to harbor_healthcheck.py
ArjunS07 Oct 19, 2026
9f97aa9
Pin harbor to formulacode/harbor-reliability e7d19622, the commit for…
ArjunS07 Oct 19, 2026
d10e6a6
Merge remote-tracking branch 'origin/release/template-v1' into verify…
ArjunS07 Oct 19, 2026
fa3ae71
verify: count oracle test failures from the test rerun (oracle_tests_…
ArjunS07 Oct 19, 2026
8f0db18
prompts: rewrite handles an empty problem text
ArjunS07 Oct 19, 2026
5d56364
prompts: keep the blank line before the next heading when the problem…
ArjunS07 Oct 19, 2026
498c2bd
Merge origin/release/template-v1 (template-v1.4) into verify/core
ArjunS07 Oct 19, 2026
3964b62
measurement: new package with the noise rule and the NopTouch agent, …
ArjunS07 Oct 19, 2026
1963ea7
harbor healthcheck: keep the oracle snapshot reference in a folder; d…
ArjunS07 Oct 19, 2026
f80f61e
measurement: the measure job moves to measurement/run.py; trials take…
ArjunS07 Oct 19, 2026
c4dbcf6
measurement: precheck decides from the image build check fields (temp…
ArjunS07 Oct 19, 2026
2f98e4e
images: task image build in docker/, the build loop as BuildTaskImage…
ArjunS07 Oct 19, 2026
6dce9d6
prompts: RenderShortProblemsRunner renders the symptom only, refuses …
ArjunS07 Oct 19, 2026
6cf5990
measurement: write the harbor job config as JSON (drops the yaml impo…
ArjunS07 Oct 19, 2026
7f5dda7
verify: delete the package and fc-verify; fc-measure (precheck, measu…
ArjunS07 Oct 19, 2026
13b98a0
build_task_images: look up Docker's data dir once, and fall back to /…
ArjunS07 Oct 19, 2026
7854274
measurement, harbor_adapter: type annotations for mypy; ruff format; …
ArjunS07 Oct 19, 2026
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
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ classifiers = [
dependencies = [
"dspy>=2.6.27",
"gitpython",
"harbor @ git+https://github.com/formula-code/harbor.git",
"harbor @ git+https://github.com/formula-code/harbor.git@e7d19622d60c477193a3904d3c6c60f95cf763c3",
"httpx>=0.27",
"huggingface-hub>=0.20",
"jinja2>=3.1.6",
Expand All @@ -35,6 +35,8 @@ dependencies = [
[project.scripts]
fc-data = "datasmith.update.cli:main"
fc-render-tasks = "datasmith.harbor_adapter.regen:main"
fc-measure = "datasmith.measurement.cli:main"
fc-build-tasks = "datasmith.runners.build_task_images:main"

[project.urls]
Homepage = "https://formula-code.github.io/datasmith/"
Expand Down
2 changes: 1 addition & 1 deletion src/datasmith/agents/extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class ProblemExtractorSignature(dspy.Signature):
pr_body: str = dspy.InputField(desc="The GitHub PR description")
pr_comments: str = dspy.InputField(desc="Comments on the PR thread.")
initial_observations: str = dspy.OutputField(
desc="Objective symptoms of the problematic behavior, described in the present tense. Focus strictly on what is happening (metrics, user impact, frequency). Do not include causes, hypotheses, or explanations."
desc="Objective symptoms of the problematic behavior, described in the present tense, as the developer would describe them before any fix existed: what is slow, the rough data size and the rough time. Do not include causes, hypotheses, explanations, internal functions, the slow code path, the fix, benchmark names, benchmark timings, links or code."
)
triage_attempts: str = dspy.OutputField(
desc="The investigative steps and reasoning used to narrow down contributing factors—what you checked, what you ruled out, and what evidence you gathered to understand where the issue originates."
Expand Down
22 changes: 22 additions & 0 deletions src/datasmith/docker/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import os
import posixpath
import re
import subprocess
from collections.abc import Callable
from pathlib import Path

from python_on_whales import DockerClient
Expand Down Expand Up @@ -100,6 +102,13 @@ def get_pr_image_name(owner: str, repo: str, issue_number: int) -> str:
return f"{_docker_namespace()}/{owner}-{repo}:{issue_number}".lower()


def task_image_tag(task_dir: Path) -> str:
"""fc-task/<task>:<first 12 hex digits of the environment/ digest>."""
from datasmith.harbor_adapter.stamp import env_digest

return f"fc-task/{task_dir.name.lower()}:{env_digest(task_dir / 'environment')[:12]}"


class ImageManager:
def __init__(self, timeout: int = 3600) -> None:
self._docker = DockerClient()
Expand Down Expand Up @@ -206,6 +215,19 @@ def build_pr_image(
)
return tag

def build_task_image(
self, task_dir: Path, run: Callable[[list[str], dict[str, str]], tuple[int, str]] | None = None
) -> tuple[str, int, str]:
"""Build a rendered harbor task's environment/ with the legacy builder: (tag, exit code, why it was stopped).

``run(cmd, env)`` runs the build, for example with output and disk limits; the default waits for it.
"""
tag = task_image_tag(task_dir)
cmd = ["docker", "build", "--rm", "--force-rm", "--network", "host", "-t", tag, str(task_dir / "environment")]
env = dict(os.environ, DOCKER_BUILDKIT="0")
rc, why = run(cmd, env) if run else (subprocess.run(cmd, env=env, check=False).returncode, "")
return tag, rc, why

def image_exists(self, tag: str) -> bool:
try:
self._docker.image.inspect(tag)
Expand Down
57 changes: 57 additions & 0 deletions src/datasmith/docker/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,63 @@ def evaluate_invariants(manifest: dict | None) -> InvariantReport:
return report


_TASK_IMAGE_CHECK = r"""
set -u
export NVM_DIR=/root/.nvm; set +u; . "$NVM_DIR/nvm.sh" >/dev/null; set -u
echo "QWEN=$(qwen --version)"
set +u; . /etc/profile.d/asv_build_vars.sh; set -u
eval "$(micromamba shell hook --shell=bash)"
micromamba run -n "$ENV_NAME" python -c "import asv_runner, coverage, jinja2, asv.contrib.lightspeed" && echo IMPORTS=ok
micromamba run -n "$ENV_NAME" snapshot-tool --help >/dev/null 2>&1 && echo SNAPSHOT_TOOL=ok
echo "HEAD=$(git -C /workspace/repo rev-parse HEAD 2>/dev/null)"
echo "LSV_INIT=$(cat /opt/lsv/cache/lsv_init_results.json 2>/dev/null | tr -d '\n')"
echo "ADDED_DEPS=$(cat /opt/lsv/cache/added_deps.json 2>/dev/null | tr -d '\n')"
"""


def _json_or(text: str | None, default: Any) -> Any:
try:
return json.loads(text or "null") or default
except json.JSONDecodeError:
return default


def check_task_image(tag: str) -> tuple[dict, list[str]]:
"""Facts about a built harbor task image and the problems that keep it from being measured."""
r = subprocess.run(
["docker", "run", "--rm", "--network", "none", "--entrypoint", "bash", tag, "-c", _TASK_IMAGE_CHECK],
capture_output=True,
text=True,
timeout=600,
)
out = dict(line.split("=", 1) for line in r.stdout.splitlines() if "=" in line and line.split("=", 1)[0].isupper())
problems = []
if not out.get("QWEN", "").strip():
problems.append("qwen-code does not run")
if out.get("IMPORTS") != "ok":
problems.append("import asv_runner/coverage/jinja2/asv.contrib.lightspeed failed")
if out.get("SNAPSHOT_TOOL") != "ok":
problems.append("snapshot-tool missing")
lsv = _json_or(out.get("LSV_INIT"), {})
info = {
"head": out.get("HEAD", ""),
"env_fingerprint": lsv.get("env_fingerprint"),
"baseline_sha": lsv.get("baseline_sha"),
"bake_cpus_bounded": lsv.get("bake_cpus_bounded"),
"benchmarks_discovered": len(lsv.get("benchmarks_discovered") or []),
"added_deps": _json_or(out.get("ADDED_DEPS"), None),
}
if not lsv.get("baseline_sha"):
problems.append("no stored baseline in /opt/lsv/cache/lsv_init_results.json")
elif lsv["baseline_sha"] != info["head"]:
problems.append(f"baseline_sha {lsv['baseline_sha']} != HEAD {info['head']}")
if not info["env_fingerprint"]:
problems.append("no env_fingerprint in the stored baseline")
if not r.stdout.strip():
problems.append(f"check container failed: {r.stderr.strip()[-500:]}")
return info, problems


def read_build_manifest(image: str) -> dict | None:
"""Read the sealed manifest out of a built image.

Expand Down
3 changes: 3 additions & 0 deletions src/datasmith/harbor_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,15 @@ def generate_task(
verifier_env: dict[str, str] | None = None,
expected_n: int | None = None,
restore_paths: list[str] | None = None,
docker_image: str | None = None,
) -> Path:
"""Render the Harbor task dir for ``rec``.

``rounds`` (default ``DATASMITH_LSV_ROUNDS``) drives both the image baseline and the trial passes.
``expected_n`` (from ``formulacode_task_overrides``) becomes ``FORMULACODE_EXPECTED_N`` for the
dilution_ratio invariant; ``None`` emits no key, so the invariant skips.
``restore_paths`` are repo files the image puts back to the base commit (base_restore.json).
``docker_image`` pins a built task image in task.toml.
"""
rounds = DATASMITH_LSV_ROUNDS if rounds is None else rounds
out_dir = self.out_root / rec.task_dir_name
Expand Down Expand Up @@ -120,6 +122,7 @@ def generate_task(
memory=memory,
storage=storage,
verifier_env=verifier_env,
docker_image=docker_image,
)
_write(out_dir / "task.toml", task_toml)

Expand Down
75 changes: 60 additions & 15 deletions src/datasmith/harbor_adapter/regen.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
python -m datasmith.harbor_adapter.regen export \
--tasks-root initial_survey/tasks --ids-file rl_train_ids_gated.md \
--ids-file rl_eval_ids_gated.md --out initial_survey/rl_task_records.jsonl
# or from the database, for new tasks
python -m datasmith.harbor_adapter.regen export --from-db --ids-file new_ids.md --out records.jsonl

# 2. reproducible render into a scratch dir (never over a live tree)
python -m datasmith.harbor_adapter.regen render \
--records initial_survey/rl_task_records.jsonl --out /scratch/tasks \
--cpus 2 --memory 16G --storage 20G
--cpus 2 --memory 16G --storage 20G [--images OUT/manifest.jsonl]

# 3. drift check (see stamp.py)
python -m datasmith.harbor_adapter.regen check /scratch/tasks/*
Expand All @@ -35,7 +37,8 @@
from pathlib import Path

from datasmith.harbor_adapter.adapter import FormulaCodeAdapter, FormulaCodeRecord
from datasmith.harbor_adapter.stamp import check_task_dirs
from datasmith.harbor_adapter.records import to_record
from datasmith.harbor_adapter.stamp import check_task_dirs, env_digest
from datasmith.harbor_adapter.utils import DATASMITH_LSV_ROUNDS

_ID_SLASH = re.compile(r"([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)#(\d+)")
Expand All @@ -59,6 +62,7 @@
"difficulty",
"repo_name",
)
_PR_FIELDS = "owner,repo,issue_number,merge_commit_sha,base_sha,container_name,patch,rendered_problem,classification,difficulty,merged_at"


def parse_ids(text: str) -> list[str]:
Expand Down Expand Up @@ -100,7 +104,33 @@ def _load_records(path: Path) -> list[FormulaCodeRecord]:
return recs


def _db_records(ids: list[str]) -> tuple[list[FormulaCodeRecord], list[str]]:
"""Records for owner__repo__N ids from pull_requests, and the ids without a complete row."""
from datasmith.utils.db import fetch_all

recs, missing = [], []
for tid in ids:
owner, repo, num = tid.split("__")
rows = fetch_all("pull_requests", _PR_FIELDS, filters={"owner": owner, "repo": repo, "issue_number": int(num)})
if rows and all(
rows[0].get(k) for k in ("patch", "base_sha", "merge_commit_sha", "rendered_problem", "container_name")
):
recs.append(to_record(rows[0]))
else:
missing.append(tid)
return recs, missing


def cmd_export(args: argparse.Namespace) -> int:
if args.from_db:
recs, missing = _db_records(load_ids(args.ids_file))
Path(args.out).write_text(
"".join(json.dumps({k: getattr(r, k) for k in _RECORD_FIELDS}, sort_keys=True) + "\n" for r in recs)
)
print(
f"[export] wrote {len(recs)} record(s) to {args.out}; missing or incomplete: {', '.join(missing) or 'none'}"
)
return 1 if missing and not args.allow_missing else 0
root = Path(args.tasks_root)
ids = (
load_ids(args.ids_file)
Expand Down Expand Up @@ -143,19 +173,29 @@ def cmd_render(args: argparse.Namespace) -> int:
if out_root.exists() and any(out_root.iterdir()) and not args.force:
raise SystemExit(f"{out_root} is not empty; pass --force to re-render into it")
restore = json.loads(Path(args.restore_paths).read_text()) if args.restore_paths else {}
# The last verified row of a task in a built-images manifest (runners/build_task_images.py).
built = (
{r["task_id"]: r for r in map(json.loads, args.images.read_text().splitlines()) if r.get("verified")}
if args.images
else {}
)
adapter = FormulaCodeAdapter(harbor_tasks_root=out_root, force=False)
for rec in records:
out_dir = adapter.generate_task(
rec,
run_pytest=not args.no_pytest,
timeout_sec=args.timeout,
cpus=args.cpus,
memory=args.memory,
storage=args.storage,
rounds=args.rounds,
verifier_env=verifier_env,
restore_paths=restore.get(rec.task_dir_name),
)
kw = {
"run_pytest": not args.no_pytest,
"timeout_sec": args.timeout,
"cpus": args.cpus,
"memory": args.memory,
"storage": args.storage,
"rounds": args.rounds,
"verifier_env": verifier_env,
"restore_paths": restore.get(rec.task_dir_name),
}
out_dir = adapter.generate_task(rec, **kw)
row = built.get(rec.task_dir_name)
# The image tag is known only once environment/ is rendered; task.toml is outside it, so the digest holds.
if row and row.get("env_digest") == env_digest(out_dir / "environment"):
adapter.generate_task(rec, **kw, docker_image=row["tag"])
print(f"[render] {out_dir}")
print(
f"[render] {len(records)} task dir(s) -> {out_root} "
Expand Down Expand Up @@ -184,7 +224,9 @@ def main(argv: list[str] | None = None) -> int:
sub = ap.add_subparsers(dest="cmd", required=True)

ex = sub.add_parser("export", help="collect tests/config.json records from a rendered tree into a jsonl")
ex.add_argument("--tasks-root", required=True, type=Path)
src = ex.add_mutually_exclusive_group(required=True)
src.add_argument("--tasks-root", type=Path)
src.add_argument("--from-db", action="store_true", help="build records for --ids-file ids from pull_requests")
ex.add_argument(
"--ids-file", action="append", type=Path, default=[], help="restrict to ids listed in this file (repeatable)"
)
Expand Down Expand Up @@ -221,6 +263,9 @@ def main(argv: list[str] | None = None) -> int:
help="json {task_id: [repo paths]} the image puts back to the base commit ('' for none)",
)
rn.add_argument("--no-pytest", action="store_true")
rn.add_argument(
"--images", type=Path, help="built-images manifest.jsonl; pins each task's verified image in task.toml"
)
rn.add_argument(
"--force", action="store_true", help="render into a non-empty output root (overwrites per task dir)"
)
Expand All @@ -232,7 +277,7 @@ def main(argv: list[str] | None = None) -> int:
ck.set_defaults(fn=cmd_check)

args = ap.parse_args(argv)
return args.fn(args)
return int(args.fn(args))


if __name__ == "__main__":
Expand Down
11 changes: 10 additions & 1 deletion src/datasmith/harbor_adapter/stamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ def render_source_digest() -> str:
return h.hexdigest()


def env_digest(env_dir: Path) -> str:
"""sha256 over a task's environment/ relative paths and bytes; the task image tag carries its first 12 hex digits."""
h = hashlib.sha256()
for p in sorted(q for q in env_dir.rglob("*") if q.is_file()):
h.update(str(p.relative_to(env_dir)).encode() + b"\0" + p.read_bytes() + b"\0")
return h.hexdigest()


def source_commit() -> dict[str, Any]:
"""Best-effort git identity of the datasmith checkout rendering the task."""
info: dict[str, Any] = {"commit": None, "dirty": None}
Expand Down Expand Up @@ -90,7 +98,8 @@ def read_stamp(task_dir: Path) -> dict[str, Any] | None:
if not path.is_file():
return None
try:
return json.loads(path.read_text())
stamp: dict[str, Any] = json.loads(path.read_text())
return stamp
except json.JSONDecodeError:
return None

Expand Down
9 changes: 8 additions & 1 deletion src/datasmith/harbor_adapter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def render_template(template_name: str, **context: object) -> str:
return _TEMPLATE_ENV.get_template(template_name).render(**context)


def patch_files(solve_sh: Path) -> list[str]:
"""Files the oracle patch in a rendered solve.sh changes."""
return sorted(set(re.findall(r"^\+\+\+ b/(\S+)", solve_sh.read_text(), re.M))) if solve_sh.exists() else []


def make_task_id(owner: str, repo: str, issue_number: int | str) -> str:
"""Harbor task id (dir name, snapshot prefix, $TASK_ID); DB queries use the triple."""
return f"{owner}__{repo}__{int(issue_number)}"
Expand Down Expand Up @@ -78,7 +83,9 @@ def render_task_toml(
memory: str = "4G",
storage: str = "10G",
verifier_env: dict[str, str] | None = None,
docker_image: str | None = None,
) -> str:
"""``docker_image``: a built task image for harbor to run instead of building environment/."""
check_verifier_env(verifier_env)
env_lines = "".join(f'{k} = "{v}"\n' for k, v in (verifier_env or {}).items())
verifier_env_section = f"\n[verifier.env]\n{env_lines}" if env_lines else ""
Expand All @@ -101,7 +108,7 @@ def render_task_toml(
cpus = {cpus}
memory = '{memory}'
storage = '{storage}'
"""
""" + (f'docker_image = "{docker_image}"\n' if docker_image else "")


def render_dockerfile(
Expand Down
1 change: 1 addition & 0 deletions src/datasmith/measurement/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Timing of tasks: the measure job, the per-benchmark noise rule and the checks before timing."""
23 changes: 23 additions & 0 deletions src/datasmith/measurement/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Harbor agents for measuring: NopTouch appends a newline to each file the oracle patch changes, so LSV times the
oracle's benchmarks on unchanged code (an empty diff selects nothing)."""

from __future__ import annotations

import shlex
from typing import Any

from harbor.agents.nop import NopAgent


class NopTouch(NopAgent): # type: ignore[no-any-unimported]
def __init__(self, *args: Any, files: list[str] | None = None, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self._files = list(files or [])

async def run(self, instruction: str, environment: Any, context: Any) -> None:
if not self._files:
return
quoted = " ".join(shlex.quote(f) for f in self._files)
await environment.exec(
command=f'cd /workspace/repo && for f in {quoted}; do [ -f "$f" ] && printf "\\n" >> "$f"; done; git diff --stat'
)
Loading
Loading