ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,783 @@
#!/usr/bin/env python3
"""Exact companion for manuscript Experiment 7-13.
The runner deliberately separates three things:
1. a non-destructive host/upstream preflight;
2. generation or execution of the two real upstream val-only arms; and
3. strict analysis of episode-level evidence.
Paper numbers, historical rollout collections and dry-run commands never satisfy
the completion gate.
"""
from __future__ import annotations
import argparse
import json
import math
import os
import re
import shutil
import statistics
import subprocess
import sys
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable
HERE = Path(__file__).resolve().parent
DEFAULT_CONFIG = HERE / "config.json"
VIDEO_RE = re.compile(
r"step=(?P<global_step>\d+)--task=(?P<task>.+?)--success=(?P<success>True|False)--ran=(?P<run>[^.]+)\.mp4$"
)
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def dump_json(path: Path, value: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
def resolve_config(path: Path) -> dict[str, Any]:
cfg = load_json(path)
upstream = Path(cfg["upstream_path"])
if not upstream.is_absolute():
upstream = (path.parent / upstream).resolve()
cfg["upstream_path"] = str(upstream)
return cfg
def run_capture(argv: list[str], cwd: Path | None = None) -> tuple[int, str]:
try:
proc = subprocess.run(
argv,
cwd=cwd,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
return proc.returncode, proc.stdout.strip()
except FileNotFoundError as exc:
return 127, f"{type(exc).__name__}: {exc}"
def gpu_inventory() -> tuple[list[dict[str, Any]], str | None]:
code, output = run_capture(
[
"nvidia-smi",
"--query-gpu=index,name,memory.total,driver_version",
"--format=csv,noheader,nounits",
]
)
if code != 0:
return [], output or "nvidia-smi unavailable"
gpus = []
for line in output.splitlines():
parts = [part.strip() for part in line.split(",")]
if len(parts) >= 4:
gpus.append(
{
"index": int(parts[0]),
"name": parts[1],
"memory_mib": int(parts[2]),
"driver": parts[3],
}
)
return gpus, None
def preflight(cfg: dict[str, Any]) -> dict[str, Any]:
upstream = Path(cfg["upstream_path"])
checks: dict[str, dict[str, Any]] = {}
def check(name: str, ok: bool, evidence: Any, required: bool = True) -> None:
checks[name] = {"ok": bool(ok), "required": required, "evidence": evidence}
check("upstream_checkout", (upstream / ".git").exists(), str(upstream))
commit_code, commit = run_capture(["git", "rev-parse", "HEAD"], cwd=upstream)
check(
"pinned_upstream_commit",
commit_code == 0 and commit == cfg["expected_upstream_commit"],
{"observed": commit, "expected": cfg["expected_upstream_commit"]},
)
required_files = [
"examples/run_openvla_oft_rl_twin2.sh",
"examples/robotwin2_tasks_info.txt",
"verl/trainer/main_ppo.py",
"verl/trainer/ppo/ray_trainer.py",
"verl/utils/dataset/rob_dataset.py",
"verl/workers/rollout/rob_rollout.py",
"modified_codes/robotwin2/envs/move_can_pot.py",
"modified_codes/robotwin2/task_config/demo_randomized.yml",
"verl/utils/envs/robotwin2/seeds/robotwin2_eval_seeds.json",
]
missing = [item for item in required_files if not (upstream / item).is_file()]
check("required_upstream_files", not missing, {"missing": missing, "required": required_files})
task_info = upstream / "examples/robotwin2_tasks_info.txt"
task_supported = task_info.is_file() and cfg["task"] in task_info.read_text(encoding="utf-8")
check("move_can_pot_supported", task_supported, str(task_info))
seed_file = upstream / "verl/utils/envs/robotwin2/seeds/robotwin2_eval_seeds.json"
observed_seed_count = 0
if seed_file.is_file():
seed_data = load_json(seed_file)
observed_seed_count = len(seed_data.get(cfg["task"], {}).get("success_seeds", []))
check(
"ood_seed_inventory",
observed_seed_count >= cfg["ood_validation_seeds"],
{"observed": observed_seed_count, "required": cfg["ood_validation_seeds"]},
)
rollout_source = upstream / "verl/workers/rollout/rob_rollout.py"
rollout_text = rollout_source.read_text(encoding="utf-8") if rollout_source.is_file() else ""
source_invariants = {
"three_view_branch": "num_images_in_input == 3" in rollout_text,
"left_wrist": '"left_wrist"' in rollout_text,
"right_wrist": '"right_wrist"' in rollout_text,
"proprio_vector": "joint_action']['vector" in rollout_text,
"environment_success": "self.env.eval_success" in rollout_text,
"validation_video": "save_rollout_video" in rollout_text,
}
check("source_invariants", all(source_invariants.values()), source_invariants)
checkpoint_raw = os.environ.get(cfg["checkpoint_env"], "")
checkpoint = Path(checkpoint_raw).expanduser() if checkpoint_raw else None
checkpoint_metadata: dict[str, Any] = {
"environment": cfg["checkpoint_env"],
"path": checkpoint_raw or None,
"checkpoint_id": cfg["checkpoint_id"],
"expected_revision": cfg["expected_checkpoint_revision"],
}
checkpoint_ready = bool(checkpoint and checkpoint.is_dir())
if checkpoint_ready and checkpoint is not None:
metadata_dir = checkpoint / ".cache/huggingface/download"
primary_names = [
"config.json",
"model-00001-of-00004.safetensors",
"model-00002-of-00004.safetensors",
"model-00003-of-00004.safetensors",
"model-00004-of-00004.safetensors",
"proprio_projector--20000_checkpoint.pt",
"lora_adapter/adapter_model.safetensors",
]
artifact_metadata = {}
observed_revisions = set()
missing_artifacts = []
for name in primary_names:
artifact = checkpoint / name
metadata = metadata_dir / f"{name}.metadata"
if not artifact.is_file() or not metadata.is_file():
missing_artifacts.append(name)
continue
lines = metadata.read_text(encoding="utf-8").splitlines()
if len(lines) < 2:
missing_artifacts.append(name)
continue
observed_revisions.add(lines[0])
artifact_metadata[name] = {
"bytes": artifact.stat().st_size,
"huggingface_etag": lines[1],
}
checkpoint_metadata.update({
"observed_revisions": sorted(observed_revisions),
"artifact_metadata": artifact_metadata,
"missing_artifacts": missing_artifacts,
})
checkpoint_ready = (
not missing_artifacts
and observed_revisions == {cfg["expected_checkpoint_revision"]}
)
check(
"pretrained_checkpoint",
checkpoint_ready,
checkpoint_metadata,
)
robotwin_raw = os.environ.get(cfg["robotwin2_env"], "")
robotwin = Path(robotwin_raw).expanduser() if robotwin_raw else None
robotwin_markers = ["envs", "task_config", "script"]
check(
"robotwin2_checkout",
bool(robotwin and robotwin.is_dir() and all((robotwin / marker).exists() for marker in robotwin_markers)),
{"environment": cfg["robotwin2_env"], "path": robotwin_raw or None, "markers": robotwin_markers},
)
robotwin_code, robotwin_commit = (
run_capture(["git", "rev-parse", "HEAD"], cwd=robotwin)
if robotwin and robotwin.is_dir()
else (1, "")
)
check(
"pinned_robotwin2_commit",
robotwin_code == 0 and robotwin_commit == cfg["expected_robotwin2_commit"],
{"observed": robotwin_commit or None, "expected": cfg["expected_robotwin2_commit"]},
)
align_raw = os.environ.get(cfg["align_path_env"], "")
align = Path(align_raw).expanduser() if align_raw else upstream / "align.json"
check(
"ray_runtime_environment",
align.is_file(),
{"environment": cfg["align_path_env"], "path": str(align)},
)
gpus, gpu_error = gpu_inventory()
check(
"nvidia_gpu_count",
len(gpus) >= cfg["gpus_required"],
{"observed": len(gpus), "required": cfg["gpus_required"], "gpus": gpus, "error": gpu_error},
)
custom_task_config = HERE / "task_config_exp7_13_three_view.yml"
task_config_text = custom_task_config.read_text(encoding="utf-8") if custom_task_config.is_file() else ""
check(
"three_rgb_view_config",
custom_task_config.is_file()
and "collect_head_camera: true" in task_config_text
and "collect_wrist_camera: true" in task_config_text,
str(custom_task_config),
)
required_failures = [name for name, item in checks.items() if item["required"] and not item["ok"]]
return {
"schema_version": 1,
"experiment": cfg["experiment"],
"generated_at_utc": utc_now(),
"host": {
"platform": sys.platform,
"python": sys.version.split()[0],
"cwd": str(Path.cwd()),
},
"configuration": cfg,
"checks": checks,
"ready_for_real_validation": not required_failures,
"blocking_checks": required_failures,
"acceptance_note": "Preflight readiness is necessary but never sufficient for Experiment 7-13 completion.",
}
def hydra_command(cfg: dict[str, Any], arm: str, run_dir: Path, worktree: Path) -> list[str]:
chunk = int(arm.removeprefix("chunk_"))
checkpoint = str(Path(os.environ[cfg["checkpoint_env"]]).expanduser().resolve())
align_raw = os.environ.get(cfg["align_path_env"])
align_path = Path(align_raw).expanduser().resolve() if align_raw else worktree / "align.json"
experiment_name = f"exp7_13_{cfg['task']}_{arm}"
return [
sys.executable,
"-u",
"-m",
"verl.trainer.main_ppo",
f"data.task_suite_name={cfg['task_suite']}",
"data.num_trials_per_task=128",
"data.n_samples=1",
"data.filter_accuracy=False",
"data.train_batch_size=64",
"data.val_batch_size=8",
"data.max_prompt_length=256",
"data.max_response_length=128",
f"actor_rollout_ref.model.path={checkpoint}",
f"actor_rollout_ref.model.vla={cfg['vla']}",
f"actor_rollout_ref.model.action_token_len={cfg['action_token_length']}",
f"actor_rollout_ref.model.action_chunks_len={chunk}",
"actor_rollout_ref.model.resume=False",
f"actor_rollout_ref.actor.num_images_in_input={cfg['rgb_views']}",
"actor_rollout_ref.actor.traj_mini_batch_size=8",
"actor_rollout_ref.actor.ppo_mini_batch_size=128",
"actor_rollout_ref.actor.ppo_micro_batch_size=8",
"actor_rollout_ref.actor.use_dynamic_bsz=False",
"actor_rollout_ref.actor.fsdp_config.param_offload=False",
"actor_rollout_ref.actor.fsdp_config.grad_offload=True",
"actor_rollout_ref.actor.fsdp_config.optimizer_offload=True",
f"actor_rollout_ref.rollout.twin2_task_config={cfg['task_config']}",
"actor_rollout_ref.rollout.twin2_instruction_type=seen",
f"actor_rollout_ref.rollout.num_images_in_input={cfg['rgb_views']}",
f"+actor_rollout_ref.rollout.action_token_len={cfg['action_token_length']}",
"actor_rollout_ref.rollout.use_proprio=True",
"actor_rollout_ref.rollout.val_micro_batch_size=8",
"actor_rollout_ref.rollout.temperature=1.6",
f"actor_rollout_ref.rollout.experiment_name={experiment_name}",
"actor_rollout_ref.rollout.micro_batch_size=1",
f"actor_rollout_ref.rollout.unnorm_key=robotwin2_{cfg['task']}_1k",
f"actor_rollout_ref.rollout.model_family={cfg['model_family']}",
f"actor_rollout_ref.rollout.task_suite_name={cfg['task_suite']}",
"actor_rollout_ref.rollout.num_steps_wait=10",
f"actor_rollout_ref.rollout.pretrained_checkpoint={checkpoint}",
"actor_rollout_ref.rollout.center_crop=True",
"actor_rollout_ref.rollout.max_prompt_length=512",
"actor_rollout_ref.rollout.tensor_model_parallel_size=1",
"actor_rollout_ref.rollout.name=hf",
"actor_rollout_ref.rollout.gpu_memory_utilization=0.9",
"actor_rollout_ref.ref.log_prob_micro_batch_size=32",
"actor_rollout_ref.ref.fsdp_config.param_offload=True",
"algorithm.adv_estimator=grpo",
"algorithm.kl_ctrl.kl_coef=0.0",
"trainer.logger=['console']",
"trainer.project_name=AI-Agent-Book-Experiment-7-13",
f"trainer.experiment_name={experiment_name}",
f"trainer.default_local_dir={str((run_dir / arm / 'checkpoints').resolve())}",
f"trainer.n_gpus_per_node={cfg['gpus_required']}",
"trainer.nnodes=1",
"trainer.save_freq=-1",
"trainer.test_freq=-1",
"trainer.total_epochs=1",
"trainer.val_only=True",
"trainer.val_before_train=True",
f"trainer.runtime_env={str(align_path)}",
"trainer.wandb_mode=disabled",
]
def shell_quote(value: str) -> str:
import shlex
return shlex.quote(value)
def prepare_worktree(cfg: dict[str, Any], run_dir: Path) -> Path:
upstream = Path(cfg["upstream_path"])
worktree = run_dir / "instrumented-upstream"
if worktree.exists():
raise RuntimeError(f"refusing to replace existing worktree: {worktree}")
code, output = run_capture(
["git", "worktree", "add", "--detach", str(worktree), cfg["expected_upstream_commit"]],
cwd=upstream,
)
if code != 0:
raise RuntimeError(f"git worktree add failed: {output}")
robotwin = Path(os.environ[cfg["robotwin2_env"]]).expanduser().resolve()
setup = subprocess.run(
["bash", "copy_overwrite_robotwin2.sh", str(robotwin), str(worktree)],
cwd=worktree,
check=False,
)
if setup.returncode != 0:
raise RuntimeError(f"RoboTwin2 overlay setup failed with exit {setup.returncode}")
task_config_dest = worktree / "verl/utils/envs/robotwin2/task_config" / f"{cfg['task_config']}.yml"
task_config_dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(HERE / "task_config_exp7_13_three_view.yml", task_config_dest)
patch_proc = subprocess.run(
[sys.executable, str(HERE / "instrument_upstream.py"), str(worktree)],
check=False,
)
if patch_proc.returncode != 0:
raise RuntimeError(f"upstream instrumentation failed with exit {patch_proc.returncode}")
return worktree
def write_launch_manifest(cfg: dict[str, Any], run_dir: Path, worktree: Path) -> dict[str, Any]:
arms = {}
for chunk in cfg["action_chunks"]:
arm = f"chunk_{chunk}"
argv = hydra_command(cfg, arm, run_dir, worktree)
episode_path = (run_dir / arm / "episodes.jsonl").resolve()
env = {
"EXP7_13_EPISODE_JSONL": str(episode_path),
"EXP7_13_ARM": arm,
"EXP7_13_UPSTREAM_COMMIT": cfg["expected_upstream_commit"],
"HYDRA_FULL_ERROR": "1",
"TOKENIZERS_PARALLELISM": "true",
"NCCL_DEBUG": "WARN",
"ROBOT_PLATFORM": "ALOHA",
"VERL_DISABLE_VLLM_IMPORT": "1",
}
arms[arm] = {
"action_chunk_length": chunk,
"episode_evidence": str(episode_path),
"rollout_directory": str((worktree / "rollouts" / f"exp7_13_{cfg['task']}_{arm}").resolve()),
"environment": env,
"argv": argv,
"shell_command": " ".join(
[*(f"{key}={shell_quote(value)}" for key, value in env.items()), *(shell_quote(item) for item in argv)]
),
}
manifest = {
"schema_version": 1,
"experiment": cfg["experiment"],
"generated_at_utc": utc_now(),
"real_execution_required": True,
"upstream_worktree": str(worktree.resolve()),
"upstream_commit": cfg["expected_upstream_commit"],
"arms": arms,
}
dump_json(run_dir / "launch_manifest.json", manifest)
return manifest
def execute_arms(manifest: dict[str, Any], selected: Iterable[str]) -> None:
worktree = Path(manifest["upstream_worktree"])
for arm in selected:
spec = manifest["arms"][arm]
arm_dir = Path(spec["episode_evidence"]).parent
arm_dir.mkdir(parents=True, exist_ok=True)
log_path = arm_dir / "upstream.log"
env = os.environ.copy()
env.update(spec["environment"])
started = utc_now()
with log_path.open("w", encoding="utf-8") as log:
proc = subprocess.run(
spec["argv"],
cwd=worktree,
env=env,
check=False,
stdout=log,
stderr=subprocess.STDOUT,
)
dump_json(
arm_dir / "process.json",
{
"arm": arm,
"started_at_utc": started,
"ended_at_utc": utc_now(),
"exit_code": proc.returncode,
"log": str(log_path.resolve()),
},
)
if proc.returncode != 0:
raise RuntimeError(f"{arm} real validation failed with exit {proc.returncode}; see {log_path}")
def percentile(values: list[float], q: float) -> float | None:
if not values:
return None
ordered = sorted(values)
if len(ordered) == 1:
return ordered[0]
pos = (len(ordered) - 1) * q
lo = math.floor(pos)
hi = math.ceil(pos)
if lo == hi:
return ordered[lo]
return ordered[lo] + (ordered[hi] - ordered[lo]) * (pos - lo)
def distribution(values: list[float]) -> dict[str, float | int | None]:
return {
"count": len(values),
"mean": statistics.fmean(values) if values else None,
"std": statistics.pstdev(values) if len(values) > 1 else 0.0 if values else None,
"min": min(values) if values else None,
"p50": percentile(values, 0.50),
"p95": percentile(values, 0.95),
"max": max(values) if values else None,
}
def read_jsonl(path: Path) -> list[dict[str, Any]]:
rows = []
if not path.is_file():
return rows
for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if not line.strip():
continue
try:
rows.append(json.loads(line))
except json.JSONDecodeError as exc:
raise ValueError(f"{path}:{line_no}: {exc}") from exc
return rows
def video_index(
directory: Path,
*,
started_at_utc: str | None = None,
ended_at_utc: str | None = None,
) -> dict[tuple[str, bool], list[str]]:
"""Index rollout videos, optionally restricting them to one process window.
Upstream names videos with a random ``ran`` suffix and does not clear an
existing rollout directory. A bounded resume can therefore leave two
files for the same task/result key. Process timestamps let the evidence
reader reject those stale files without guessing from the random suffix.
"""
index: dict[tuple[str, bool], list[str]] = {}
if not directory.is_dir():
return index
started = datetime.fromisoformat(started_at_utc) if started_at_utc else None
ended = datetime.fromisoformat(ended_at_utc) if ended_at_utc else None
for path in directory.rglob("*.mp4"):
modified = datetime.fromtimestamp(path.stat().st_mtime, timezone.utc)
if started is not None and modified < started:
continue
if ended is not None and modified > ended:
continue
match = VIDEO_RE.match(path.name)
if not match:
continue
task_key = match.group("task")
success = match.group("success") == "True"
index.setdefault((task_key, success), []).append(str(path.resolve()))
return index
def annotation_key(row: dict[str, Any]) -> str:
return f"{row['arm']}|{row['data_source']}|{row['trial_seed']}"
def analyze(cfg: dict[str, Any], run_dir: Path, annotations_path: Path | None) -> dict[str, Any]:
manifest_path = run_dir / "launch_manifest.json"
manifest = load_json(manifest_path) if manifest_path.is_file() else None
annotations = load_json(annotations_path) if annotations_path and annotations_path.is_file() else {}
all_rows: list[dict[str, Any]] = []
arm_reports: dict[str, Any] = {}
strict_errors: list[str] = []
expected_total = cfg["iid_validation_seeds"] + cfg["ood_validation_seeds"]
for chunk in cfg["action_chunks"]:
arm = f"chunk_{chunk}"
episode_path = run_dir / arm / "episodes.jsonl"
rows = read_jsonl(episode_path)
all_rows.extend(rows)
bad_source = [row for row in rows if row.get("source") != "upstream_val_only"]
if bad_source:
strict_errors.append(f"{arm}: {len(bad_source)} rows are not upstream_val_only evidence")
if len(rows) != expected_total:
strict_errors.append(f"{arm}: expected {expected_total} episodes, observed {len(rows)}")
if any(row.get("action_chunk_length") != chunk for row in rows):
strict_errors.append(f"{arm}: action chunk mismatch")
if any(row.get("action_dimension") != cfg["action_dimension"] for row in rows):
strict_errors.append(f"{arm}: action dimension mismatch")
if any(row.get("rgb_views") != cfg["rgb_views"] for row in rows):
strict_errors.append(f"{arm}: three-view observation not proven")
if any(not row.get("proprioception_enabled") for row in rows):
strict_errors.append(f"{arm}: proprioception not enabled in all rows")
iid = [row for row in rows if str(row.get("data_source", "")).endswith("_train_iid")]
ood = [row for row in rows if str(row.get("data_source", "")).endswith("_eval_ood")]
if len({row.get("trial_seed") for row in iid}) != cfg["iid_validation_seeds"]:
strict_errors.append(f"{arm}: incomplete IID seed coverage")
if len({row.get("trial_seed") for row in ood}) != cfg["ood_validation_seeds"]:
strict_errors.append(f"{arm}: incomplete OOD seed coverage")
video_dir = Path(manifest["arms"][arm]["rollout_directory"]) if manifest else Path()
process_path = run_dir / arm / "process.json"
process_data = load_json(process_path) if process_path.is_file() else {}
videos = video_index(
video_dir,
started_at_utc=process_data.get("started_at_utc"),
ended_at_utc=process_data.get("ended_at_utc"),
)
unmatched_videos = 0
failure_counts: Counter[str] = Counter()
unclassified_failures = []
missing_video_count = 0
for row in rows:
task_file = f"{cfg['task']}_trial_{row.get('trial_id')}_seed_{row.get('trial_seed')}"
candidates = videos.get((task_file, bool(row.get("success"))), [])
if candidates:
row["video_path"] = candidates.pop(0)
else:
missing_video_count += 1
if row.get("success"):
row["failure_mode"] = None
row["failure_evidence"] = None
else:
annotation = annotations.get(annotation_key(row))
if annotation:
label = annotation.get("failure_mode")
evidence = annotation.get("evidence")
if label not in cfg["failure_modes"] or not evidence:
unclassified_failures.append(annotation_key(row))
else:
row["failure_mode"] = label
row["failure_evidence"] = evidence
failure_counts[label] += 1
else:
unclassified_failures.append(annotation_key(row))
unmatched_videos = sum(len(paths) for paths in videos.values())
if missing_video_count:
strict_errors.append(f"{arm}: {missing_video_count} episodes lack rollout video evidence")
if unclassified_failures:
strict_errors.append(f"{arm}: {len(unclassified_failures)} failed episodes lack valid failure annotations")
action_steps = [float(row["finish_action_steps"]) for row in rows if row.get("finish_action_steps") is not None]
control_seconds = [value / cfg["control_hz"] for value in action_steps]
successes = sum(bool(row.get("success")) for row in rows)
by_split = {}
for split_name, split_rows in (("iid", iid), ("ood", ood)):
split_successes = sum(bool(row.get("success")) for row in split_rows)
by_split[split_name] = {
"episodes": len(split_rows),
"successes": split_successes,
"success_rate": split_successes / len(split_rows) if split_rows else None,
}
arm_reports[arm] = {
"action_chunk_length": chunk,
"episodes": len(rows),
"successes": successes,
"success_rate": successes / len(rows) if rows else None,
"by_split": by_split,
"completion_action_steps": distribution(action_steps),
"completion_control_seconds_at_50hz": distribution(control_seconds),
"failure_modes": dict(sorted(failure_counts.items())),
"unclassified_failure_count": len(unclassified_failures),
"missing_video_count": missing_video_count,
"unmatched_video_count": unmatched_videos,
}
control = arm_reports.get("chunk_1", {})
treatment = arm_reports.get("chunk_25", {})
paired = {}
rows_by_key = {
(row.get("arm"), row.get("data_source"), row.get("trial_seed")): row for row in all_rows
}
pair_deltas = []
for source in ("train_iid", "eval_ood"):
suffix = f"_{source}"
seeds = {
row.get("trial_seed")
for row in all_rows
if str(row.get("data_source", "")).endswith(suffix)
}
for seed in seeds:
left = next(
(
row
for row in all_rows
if row.get("arm") == "chunk_1"
and str(row.get("data_source", "")).endswith(suffix)
and row.get("trial_seed") == seed
),
None,
)
right = next(
(
row
for row in all_rows
if row.get("arm") == "chunk_25"
and str(row.get("data_source", "")).endswith(suffix)
and row.get("trial_seed") == seed
),
None,
)
if left and right:
pair_deltas.append(int(bool(right.get("success"))) - int(bool(left.get("success"))))
paired = {
"complete_pairs": len(pair_deltas),
"chunk_25_wins": sum(delta > 0 for delta in pair_deltas),
"chunk_1_wins": sum(delta < 0 for delta in pair_deltas),
"ties": sum(delta == 0 for delta in pair_deltas),
"mean_paired_success_delta": statistics.fmean(pair_deltas) if pair_deltas else None,
"unpaired_rows": len(all_rows) - 2 * len(pair_deltas),
}
if pair_deltas and len(pair_deltas) != expected_total:
strict_errors.append(f"paired comparison: expected {expected_total} pairs, observed {len(pair_deltas)}")
preflight_files = sorted((run_dir / "preflight.json", *HERE.glob("results/preflight-*.json")), key=lambda p: p.stat().st_mtime if p.exists() else 0)
preflight_report = load_json(preflight_files[-1]) if preflight_files and preflight_files[-1].is_file() else None
if not preflight_report or not preflight_report.get("ready_for_real_validation"):
strict_errors.append("no passing real-execution preflight is attached to this run")
process_failures = []
for chunk in cfg["action_chunks"]:
arm = f"chunk_{chunk}"
process_path = run_dir / arm / "process.json"
process_data = load_json(process_path) if process_path.is_file() else None
if not process_data or process_data.get("exit_code") != 0:
process_failures.append(arm)
if process_failures:
strict_errors.append(f"missing or failed upstream processes: {', '.join(process_failures)}")
report = {
"schema_version": 1,
"experiment": cfg["experiment"],
"generated_at_utc": utc_now(),
"evidence_policy": {
"paper_numbers_accepted": False,
"historical_rollouts_accepted": False,
"dry_run_commands_accepted": False,
"required_source": "instrumented upstream val-only execution",
},
"configuration": cfg,
"arms": arm_reports,
"controlled_action_chunk_comparison": paired,
"strict_completion": {
"complete": not strict_errors,
"errors": strict_errors,
},
}
dump_json(run_dir / "report.json", report)
lines = [
"# Experiment 7-13 OpenVLA + RoboTwin2 evaluation",
"",
f"Official completion: **{report['strict_completion']['complete']}**",
"",
"| Arm | Episodes | Success rate | IID | OOD | Completion p50 / p95 (50 Hz seconds) |",
"| --- | ---: | ---: | ---: | ---: | ---: |",
]
for arm, item in arm_reports.items():
times = item["completion_control_seconds_at_50hz"]
lines.append(
f"| {arm} | {item['episodes']} | {item['success_rate']} | "
f"{item['by_split']['iid']['success_rate']} | {item['by_split']['ood']['success_rate']} | "
f"{times['p50']} / {times['p95']} |"
)
lines.extend(["", "## Controlled comparison", "", f"```json\n{json.dumps(paired, indent=2)}\n```", "", "## Failure modes", ""])
for arm, item in arm_reports.items():
lines.append(f"- {arm}: {json.dumps(item['failure_modes'], ensure_ascii=False)}")
lines.extend(["", "## Completion audit", ""])
if strict_errors:
lines.extend(f"- {error}" for error in strict_errors)
else:
lines.append("- Every direct manuscript gate is supported by real per-episode evidence.")
(run_dir / "report.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--config", type=Path, default=DEFAULT_CONFIG)
sub = parser.add_subparsers(dest="command", required=True)
pre = sub.add_parser("preflight", help="inspect the real host and upstream checkout")
pre.add_argument("--output", type=Path)
prep = sub.add_parser("prepare", help="prepare an instrumented disposable worktree and commands")
prep.add_argument("--run-dir", type=Path, required=True)
launch = sub.add_parser("launch", help="execute real val-only arms from an existing manifest")
launch.add_argument("--run-dir", type=Path, required=True)
launch.add_argument("--arm", choices=["all", "chunk_1", "chunk_25"], default="all")
ana = sub.add_parser("analyze", help="build strict success/time/failure/chunk report")
ana.add_argument("--run-dir", type=Path, required=True)
ana.add_argument("--failure-annotations", type=Path)
args = parser.parse_args()
cfg = resolve_config(args.config.resolve())
if args.command == "preflight":
report = preflight(cfg)
output = args.output or HERE / "results" / f"preflight-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json"
dump_json(output, report)
print(json.dumps(report, indent=2, ensure_ascii=False))
return 0 if report["ready_for_real_validation"] else 2
if args.command == "prepare":
readiness = preflight(cfg)
args.run_dir.mkdir(parents=True, exist_ok=True)
dump_json(args.run_dir / "preflight.json", readiness)
if not readiness["ready_for_real_validation"]:
print(json.dumps(readiness, indent=2, ensure_ascii=False))
return 2
worktree = prepare_worktree(cfg, args.run_dir.resolve())
manifest = write_launch_manifest(cfg, args.run_dir.resolve(), worktree)
print(json.dumps(manifest, indent=2, ensure_ascii=False))
return 0
if args.command == "launch":
manifest = load_json(args.run_dir / "launch_manifest.json")
selected = list(manifest["arms"]) if args.arm == "all" else [args.arm]
execute_arms(manifest, selected)
return 0
if args.command == "analyze":
report = analyze(cfg, args.run_dir.resolve(), args.failure_annotations)
print(json.dumps(report, indent=2, ensure_ascii=False))
return 0 if report["strict_completion"]["complete"] else 2
raise AssertionError(args.command)
if __name__ == "__main__":
raise SystemExit(main())