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,45 @@
# 实验 6-11 至 6-12XLeRobot 自主操作与闭环策略比较
本目录给出实验 6-11 的真实硬件扩展契约,并以实验 6-12 的非致动模拟运行比较三种闭环策略;当前证据标识统一为 `6-12`
本实验把原来的导航任务改成桌面操作规划。RoboCrew 仍然负责高层智能体循环,XLeRobot 仍保留为可选的执行器接入对象;本地验收使用确定性的桌面模拟器,避免把 Gemini API、机械臂或串口可用性误报成实验结果。
## 任务
场景中有红色杯子、黄色纸张、托盘和垃圾盒。规划器需要完成:
```text
抓起红色杯子 → 放入托盘
抓起黄色纸张 → 放入垃圾盒
验证最终状态
```
工具契约只有五个职责明确、权限固定的工具,每次调用只完成一件明确的事:
```text
observe_scene() pick(object_id)
place(object_id, target_id)
verify_state() stop()
```
`pick``place` 在真实 XLeRobot 适配器中必须映射为经过标定、限速、有超时的动作原语;模型不能直接输出任意关节角。契约定义见 `xlerobot_tool_contract.py`
## 运行
```bash
cd chapter6/gemini-xlerobot-navigation
python desktop_planner.py --episodes 128 --seeds 20260808,20260809,20260810 --failure-probabilities 0.0,0.25,0.5 --output-dir validation/runs/local-gpu
python validate_evidence.py validation/runs/local-gpu/evidence.json
```
正式协议使用 3 个随机种子、0、0.25、0.5 三档“瞬时失败”概率和每格 128 个回合,共 3456 个回合;这里的失败是模拟器人为注入的一次性抓取失败,不是声称真实机械臂的故障率。每个种子都重新训练并测试一个小型动作条件世界模型。脚本比较三种执行方式:
- `open_loop`:一次提交完整动作序列,忽略中途失败;
- `closed_loop`:每个技能后重新观察,失败时重试;
- `predictive`:使用世界模型比较候选技能,再执行并验收。
实验注入一次可恢复的抓取失败,记录各模式的成功率、工具调用次数、恢复次数、世界模型测试误差和完整事件日志。预期现象是开环策略会损失一部分任务,闭环和预测式策略能够恢复。
## XLeRobot/RoboCrew 扩展
真实运行需要将 `TOOL_CONTRACT` 绑定到固定版本的 RoboCrew 工具注册和 XLeRobot 手臂控制器,并增加工作空间、急停、观察员和动作回执门禁。当前仓库的本地 GPU 验收不会调用 Gemini API、打开串口或执行机器人动作;硬件扩展必须单独生成真机证据。
@@ -0,0 +1,285 @@
#!/usr/bin/env python3
"""Experiment 6-12: desktop manipulation planning with a local GPU backend.
The local run keeps the RoboCrew-style tool contract and the XLeRobot adapter
boundary, but executes against a deterministic tabletop simulator. This
makes planner, postcondition, retry and short-horizon world-model behavior
fully reproducible without claiming that a real robot moved.
"""
from __future__ import annotations
import argparse
import json
import math
import random
import sys
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import numpy as np
import torch
from PIL import Image, ImageDraw
from torch import nn
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from robotics_lab_common import device_info, relative_or_absolute, select_device, seed_everything, sha256, write_json
OBJECTS = ("red_cup", "yellow_paper")
TARGETS = ("tray", "bin")
ACTIONS = ("pick_red_cup", "place_red_cup", "pick_yellow_paper", "place_yellow_paper")
TOOL_NAMES = ("observe_scene", "pick", "place", "verify_state", "stop")
@dataclass
class DesktopState:
status: list[int] # 0=on table, 1=held, 2=placed
target_available: list[bool]
def copy(self) -> "DesktopState":
return DesktopState(self.status[:], self.target_available[:])
def done(self) -> bool:
return self.status == [2, 2]
class DesktopToolAdapter:
"""RoboCrew-compatible semantic tool boundary for local validation."""
def __init__(self, seed: int, failure_probability: float = 0.25):
self.rng = random.Random(seed)
self.state = DesktopState([0, 0], [True, True])
self.failure_probability = failure_probability
self.injected_failure = False
self.events: list[dict[str, Any]] = []
def observe_scene(self) -> dict[str, Any]:
observation = {"objects": dict(zip(OBJECTS, self.state.status)), "targets_available": dict(zip(TARGETS, self.state.target_available))}
self.events.append({"tool": "observe_scene", "ok": True, "observation": observation})
return observation
def _maybe_fail(self, action: str) -> bool:
if action == "pick_yellow_paper" and not self.injected_failure and self.rng.random() < self.failure_probability:
self.injected_failure = True
return True
return False
def pick(self, object_id: str) -> dict[str, Any]:
if object_id not in OBJECTS:
result = {"ok": False, "reason": "unknown_object"}
else:
index = OBJECTS.index(object_id)
action = f"pick_{object_id}"
if self._maybe_fail(action):
result = {"ok": False, "reason": "injected_transient_grasp_failure"}
elif self.state.status[index] != 0:
result = {"ok": False, "reason": "object_not_on_table"}
else:
self.state.status[index] = 1
result = {"ok": True, "postcondition": f"{object_id}=held"}
self.events.append({"tool": "pick", "object_id": object_id, **result})
return result
def place(self, object_id: str, target_id: str) -> dict[str, Any]:
if object_id not in OBJECTS or target_id not in TARGETS:
result = {"ok": False, "reason": "unknown_object_or_target"}
else:
oi, ti = OBJECTS.index(object_id), TARGETS.index(target_id)
if self.state.status[oi] != 1:
result = {"ok": False, "reason": "object_not_held"}
elif not self.state.target_available[ti]:
result = {"ok": False, "reason": "target_unavailable"}
else:
self.state.status[oi] = 2
self.state.target_available[ti] = False
result = {"ok": True, "postcondition": f"{object_id}=in_{target_id}"}
self.events.append({"tool": "place", "object_id": object_id, "target_id": target_id, **result})
return result
def verify_state(self) -> dict[str, Any]:
result = {"ok": self.state.done(), "state": self.observe_scene()}
self.events.append({"tool": "verify_state", **result})
return result
def stop(self) -> dict[str, Any]:
result = {"ok": True, "stopped": True}
self.events.append({"tool": "stop", **result})
return result
def encode_state(state: DesktopState) -> list[float]:
return [state.status[0] / 2.0, state.status[1] / 2.0, float(state.target_available[0]), float(state.target_available[1])]
def transition(state: DesktopState, action: int) -> DesktopState:
next_state = state.copy()
if action == 0 and next_state.status[0] == 0:
next_state.status[0] = 1
elif action == 1 and next_state.status[0] == 1 and next_state.target_available[0]:
next_state.status[0], next_state.target_available[0] = 2, False
elif action == 2 and next_state.status[1] == 0:
next_state.status[1] = 1
elif action == 3 and next_state.status[1] == 1 and next_state.target_available[1]:
next_state.status[1], next_state.target_available[1] = 2, False
return next_state
class WorldModel(nn.Module):
def __init__(self) -> None:
super().__init__()
self.net = nn.Sequential(nn.Linear(8, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 4))
def forward(self, state: torch.Tensor, action: torch.Tensor) -> torch.Tensor:
return self.net(torch.cat([state, action], dim=-1))
def train_world_model(device: torch.device, seed: int, epochs: int = 160) -> tuple[WorldModel, float, float]:
rng = random.Random(seed)
states, actions, targets = [], [], []
for _ in range(12000):
state = DesktopState([rng.randrange(3), rng.randrange(3)], [bool(rng.randrange(2)), bool(rng.randrange(2))])
action_index = rng.randrange(len(ACTIONS))
nxt = transition(state, action_index)
states.append(encode_state(state))
one_hot = [1.0 if index == action_index else 0.0 for index in range(len(ACTIONS))]
actions.append(one_hot)
targets.append(encode_state(nxt))
x_state = torch.tensor(states, dtype=torch.float32, device=device)
x_action = torch.tensor(actions, dtype=torch.float32, device=device)
y = torch.tensor(targets, dtype=torch.float32, device=device)
split = int(len(y) * 0.8)
model = WorldModel().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=2e-3)
loss_fn = nn.MSELoss()
for _ in range(epochs):
optimizer.zero_grad(set_to_none=True)
loss = loss_fn(model(x_state[:split], x_action[:split]), y[:split])
loss.backward()
optimizer.step()
with torch.no_grad():
train_loss = float(loss_fn(model(x_state[:split], x_action[:split]), y[:split]).item())
test_loss = float(loss_fn(model(x_state[split:], x_action[split:]), y[split:]).item())
return model, train_loss, test_loss
def action_from_index(index: int) -> tuple[str, str | None, str | None]:
mapping = [("pick", "red_cup", None), ("place", "red_cup", "tray"), ("pick", "yellow_paper", None), ("place", "yellow_paper", "bin")]
return mapping[index]
def execute(adapter: DesktopToolAdapter, index: int) -> dict[str, Any]:
kind, object_id, target_id = action_from_index(index)
if kind == "pick":
return adapter.pick(object_id or "")
return adapter.place(object_id or "", target_id or "")
def render_scene(path: Path, state: DesktopState) -> None:
image = Image.new("RGB", (480, 300), (235, 232, 220))
draw = ImageDraw.Draw(image)
draw.rectangle((40, 40, 440, 260), outline=(60, 60, 60), width=3)
locations = [(150, 130), (250, 130)]
colors = [(210, 60, 60), (220, 190, 40)]
for idx, (x, y) in enumerate(locations):
if state.status[idx] != 2:
draw.ellipse((x - 22, y - 22, x + 22, y + 22), fill=colors[idx], outline=(20, 20, 20))
draw.text((x - 35, y + 30), OBJECTS[idx], fill=(20, 20, 20))
draw.rectangle((320, 80, 390, 145), outline=(40, 100, 210), width=3)
draw.rectangle((320, 170, 390, 235), outline=(40, 130, 60), width=3)
draw.text((325, 95), "tray", fill=(20, 20, 20))
draw.text((325, 185), "bin", fill=(20, 20, 20))
path.parent.mkdir(parents=True, exist_ok=True)
image.save(path)
def run_episode(mode: str, seed: int, model: WorldModel | None, device: torch.device, failure_probability: float) -> dict[str, Any]:
adapter = DesktopToolAdapter(seed, failure_probability=failure_probability)
if mode == "open_loop":
plan = [0, 1, 2, 3]
for action in plan:
execute(adapter, action)
else:
for _ in range(12):
if adapter.state.done():
break
if mode == "predictive" and model is not None:
state_tensor = torch.tensor([encode_state(adapter.state)], dtype=torch.float32, device=device)
candidates = [index for index in range(4) if (index in (0, 2) and adapter.state.status[index // 2] == 0) or (index in (1, 3) and adapter.state.status[index // 2] == 1)]
if not candidates:
break
action_vectors = torch.eye(4, device=device)[candidates]
with torch.no_grad():
predicted = model(state_tensor.repeat(len(candidates), 1), action_vectors)
score = predicted[:, 0] + predicted[:, 1] + (predicted[:, 0] > 0.95).float() + (predicted[:, 1] > 0.95).float()
action = candidates[int(torch.argmax(score).item())]
else:
action = next((index for index in (0, 1, 2, 3) if (index in (0, 2) and adapter.state.status[index // 2] == 0) or (index in (1, 3) and adapter.state.status[index // 2] == 1)), 0)
result = execute(adapter, action)
if not result.get("ok"):
adapter.observe_scene()
adapter.verify_state()
return {"success": adapter.state.done(), "tool_calls": len(adapter.events), "recoveries": sum(1 for event in adapter.events if event.get("reason") == "injected_transient_grasp_failure"), "events": adapter.events}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--episodes", type=int, default=128, help="episodes per seed/failure/mode cell")
parser.add_argument("--seeds", default="20260808,20260809,20260810")
parser.add_argument("--failure-probabilities", default="0.0,0.25,0.5")
parser.add_argument("--output-dir", type=Path, default=Path(__file__).parent / "validation" / "runs" / "local-gpu")
parser.add_argument("--allow-cpu", action="store_true")
args = parser.parse_args()
try:
seeds = [int(value) for value in args.seeds.split(",")]
failure_probabilities = [float(value) for value in args.failure_probabilities.split(",")]
except ValueError:
parser.error("--seeds and --failure-probabilities must be comma-separated values")
if args.episodes < 64 or len(seeds) < 3 or not failure_probabilities or any(value < 0 or value > 1 for value in failure_probabilities):
parser.error("use at least three seeds, 64 episodes per cell, and probabilities in [0,1]")
seed_everything(seeds[0])
try:
device = select_device(not args.allow_cpu)
except RuntimeError as exc:
parser.error(str(exc))
started = time.perf_counter()
results: list[dict[str, Any]] = []
model_reports: list[dict[str, Any]] = []
event_log_rows: list[dict[str, Any]] = []
for model_seed in seeds:
model, train_loss, test_loss = train_world_model(device, model_seed)
model_reports.append({"seed": model_seed, "train_mse": train_loss, "test_mse": test_loss})
for failure_probability in failure_probabilities:
for mode in ("open_loop", "closed_loop", "predictive"):
episodes = [run_episode(mode, model_seed + index, model if mode == "predictive" else None, device, failure_probability) for index in range(args.episodes)]
results.append({"seed": model_seed, "failure_probability": failure_probability, "mode": mode, "episodes": args.episodes, "success_rate": sum(item["success"] for item in episodes) / args.episodes, "mean_tool_calls": sum(item["tool_calls"] for item in episodes) / args.episodes, "recoveries": sum(item["recoveries"] for item in episodes)})
for episode_index, episode in enumerate(episodes):
event_log_rows.append({
"seed": model_seed,
"failure_probability": failure_probability,
"mode": mode,
"episode": episode_index,
"success": episode["success"],
"events": episode["events"],
})
args.output_dir.mkdir(parents=True, exist_ok=True)
scene_path = args.output_dir / "scene_initial.png"
render_scene(scene_path, DesktopState([0, 0], [True, True]))
events_path = args.output_dir / "predictive_episode_events.json"
write_json(events_path, {"tools": TOOL_NAMES, "episodes": event_log_rows})
replay_model, _, _ = train_world_model(device, seeds[0])
replay_a = run_episode("predictive", seeds[0], replay_model, device, failure_probabilities[-1])
replay_b = run_episode("predictive", seeds[0], replay_model, device, failure_probabilities[-1])
metrics = {"device": device_info(device), "protocol": {"seeds": seeds, "failure_probabilities": failure_probabilities, "episodes_per_cell": args.episodes, "total_episodes": len(results) * args.episodes}, "models": model_reports, "cells": results, "deterministic_replay": replay_a == replay_b, "wall_time_ms": round((time.perf_counter() - started) * 1000, 3)}
metrics_path = args.output_dir / "metrics.json"
write_json(metrics_path, metrics)
evidence = {"schema_version": "3.0", "experiment_id": "6-12", "status": "complete", "kind": "desktop_manipulation_planning", "seed": seeds[0], "tool_contract": list(TOOL_NAMES), "metrics": metrics, "artifacts": [{"kind": "metrics", "path": relative_or_absolute(metrics_path, args.output_dir), "sha256": sha256(metrics_path)}, {"kind": "events", "path": relative_or_absolute(events_path, args.output_dir), "sha256": sha256(events_path)}, {"kind": "scene", "path": relative_or_absolute(scene_path, args.output_dir), "sha256": sha256(scene_path)}], "xlerobot_robocrew_extension": {"status": "gated", "tool_adapter_required": True, "actuation_attempted": False}, "blockers": [] if not args.allow_cpu else ["CPU debug mode is not a GPU acceptance run"]}
evidence_path = args.output_dir / "evidence.json"
write_json(evidence_path, evidence)
print(json.dumps(evidence, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ai-agent-book.local/schemas/experiment-6-12-local-gpu.json",
"title": "Experiment 6-12 local GPU desktop manipulation planning evidence",
"type": "object",
"required": ["schema_version", "experiment_id", "status", "kind", "tool_contract", "metrics", "artifacts", "xlerobot_robocrew_extension", "blockers"],
"properties": {
"schema_version": {"const": "3.0"},
"experiment_id": {"const": "6-12"},
"status": {"const": "complete"},
"kind": {"const": "desktop_manipulation_planning"},
"tool_contract": {"type": "array"},
"metrics": {"type": "object"},
"artifacts": {"type": "array"},
"xlerobot_robocrew_extension": {"type": "object"},
"blockers": {"type": "array"}
},
"additionalProperties": true
}
@@ -0,0 +1,32 @@
import json
import unittest
from pathlib import Path
from validate_evidence import validate
class EvidenceGateTests(unittest.TestCase):
def test_local_gpu_evidence_is_accepted(self):
run = Path(__file__).parent / "validation" / "runs" / "local-gpu" / "evidence.json"
if not run.is_file():
self.skipTest("run the local GPU experiment first")
data = json.loads(run.read_text(encoding="utf-8"))
self.assertEqual(validate(data, run.parent), [])
def test_event_log_covers_every_formal_episode(self):
evidence_dir = Path(__file__).parent / "validation" / "runs" / "local-gpu"
evidence = evidence_dir / "evidence.json"
event_log = evidence_dir / "predictive_episode_events.json"
if not evidence.is_file() or not event_log.is_file():
self.skipTest("run the local GPU experiment first")
data = json.loads(evidence.read_text(encoding="utf-8"))
payload = json.loads(event_log.read_text(encoding="utf-8"))
self.assertEqual(len(payload["episodes"]), data["metrics"]["protocol"]["total_episodes"])
def test_unbounded_tools_are_rejected(self):
data = {"schema_version": "3.0", "experiment_id": "6-12", "status": "complete", "kind": "desktop_manipulation_planning", "metrics": {"device": {"device": "mps"}, "protocol": {"seeds": [1, 2, 3], "failure_probabilities": [0.0, 0.25, 0.5], "total_episodes": 3456}, "models": [{"test_mse": 0.01}] * 3, "cells": [], "deterministic_replay": True}, "tool_contract": ["move_anywhere"], "artifacts": [], "xlerobot_robocrew_extension": {"actuation_attempted": False}}
self.assertTrue(validate(data))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,35 @@
{
"experiment_id": "6-12",
"repository": "https://github.com/Vector-Wangel/XLeRobot.git",
"commit": "3d14695e40c9c68229c0aacffca6053c75cd3eb6",
"guide": {
"published_url": "https://xlerobot.readthedocs.io/en/latest/software/getting_started/LLM_agent.html",
"path": "docs/en/source/software/getting_started/LLM_agent.md",
"git_blob": "d336a9e35838267614d31cdb98b9b50d66427f03"
},
"robocrew": {
"repository": "https://github.com/Grigorij-Dudnik/RoboCrew.git",
"tag": "v0.3.1",
"commit": "c749148f29bd14e61347f9fc3530c343fff0d994",
"version_file": {
"path": "pyproject.toml",
"git_blob": "9029068bdb511dfc02262adf7c2af69ec2fac0fe"
},
"pypi": {
"version": "0.3.1",
"wheel_url": "https://files.pythonhosted.org/packages/29/8d/893d6d5cfe8a8e5aac943936ee497934533d882cfd933f609a12a66101c2/robocrew-0.3.1-py3-none-any.whl",
"wheel_sha256": "4afbc8ab19ffb61cc0617486408460c80072991434636aff041a1ef87f2abb4f",
"sdist_url": "https://files.pythonhosted.org/packages/28/f2/16e1a8eeb2df9fff008045db5b2202bf83939eac3d48449f500f4a1326f9/robocrew-0.3.1.tar.gz",
"sdist_sha256": "a6c25a0f18b7d7d52a7efc199226a6175dfb1032213ff3845e75e3701a948c98"
}
},
"required_model": "gemini-robotics-er-1.5-preview",
"required_api_key_env": "GOOGLE_API_KEY",
"reference_frame": {
"source_url": "https://github.com/user-attachments/assets/296f6f60-52a4-4fa0-9a77-a113b4868f83",
"referenced_by": "docs/en/source/software/getting_started/LLM_agent.md",
"sha256": "2dae44dd2dbd9259f9448095f09e064ade50d485e8d0dcab7a3ca2a73435bcc1",
"role": "historical upstream reference input only; not desktop-manipulation evidence"
},
"verified_at": "2026-07-30"
}
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Validate the local GPU desktop-planning evidence for Experiment 6-12."""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from pathlib import Path
from typing import Any
EXPECTED_TOOL_NAMES = ["observe_scene", "pick", "place", "verify_state", "stop"]
def file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def validate(data: dict[str, Any], evidence_dir: Path | None = None) -> list[str]:
errors: list[str] = []
def expect(condition: bool, message: str) -> None:
if not condition:
errors.append(message)
expect(data.get("schema_version") == "3.0", "schema_version must be 3.0")
expect(data.get("experiment_id") == "6-12", "experiment_id must be 6-12")
expect(data.get("kind") == "desktop_manipulation_planning", "wrong evidence kind")
expect(data.get("status") == "complete", "local evidence must be complete")
metrics = data.get("metrics", {})
expect(metrics.get("device", {}).get("device") in {"mps", "cuda"}, "evidence must use a local GPU accelerator")
protocol = metrics.get("protocol", {})
expect(len(protocol.get("seeds", [])) >= 3, "at least three planner seeds are required")
expect(set(protocol.get("failure_probabilities", [])) >= {0.0, 0.25, 0.5}, "zero, moderate and high failure conditions are required")
expect(protocol.get("total_episodes", 0) >= 2000, "at least 2000 planner episodes are required")
models = metrics.get("models", [])
expect(len(models) == len(protocol.get("seeds", [])), "one world-model report is required per seed")
expect(max((item.get("test_mse", 1.0) for item in models), default=1.0) < 0.03, "world-model test MSE is too high")
cells = metrics.get("cells", [])
expect(len(cells) == len(protocol.get("seeds", [])) * len(protocol.get("failure_probabilities", [])) * 3, "one result cell is required per seed/failure/mode condition")
high_failure = [item for item in cells if item.get("failure_probability") == 0.5]
expect(high_failure and all(item.get("mode") in {"closed_loop", "predictive"} and item.get("success_rate") == 1.0 for item in high_failure if item.get("mode") != "open_loop"), "closed-loop and predictive planners must recover high-failure trials")
open_loop_high = [item for item in high_failure if item.get("mode") == "open_loop"]
expect(open_loop_high and max(item.get("success_rate", 1.0) for item in open_loop_high) < 1.0, "open-loop baseline must expose injected failures")
expect(metrics.get("deterministic_replay") is True, "repeating a fixed planner seed must reproduce the same episode")
contract = data.get("tool_contract", [])
expect(contract == EXPECTED_TOOL_NAMES, "工具契约不是桌面操作实验规定的五个工具")
artifacts = data.get("artifacts", [])
expect(len(artifacts) == 3, "metrics, events and scene artifacts are required")
if evidence_dir is not None:
for index, artifact in enumerate(artifacts):
path = evidence_dir / str(artifact.get("path", ""))
expect(path.is_file(), f"artifact[{index}] does not exist")
if path.is_file():
expect(file_sha256(path) == artifact.get("sha256"), f"artifact[{index}] hash mismatch")
if artifact.get("kind") == "events":
try:
event_log = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
errors.append(f"event log cannot be read: {exc}")
else:
expect(event_log.get("tools") == EXPECTED_TOOL_NAMES, "event log tool list does not match the contract")
episodes = event_log.get("episodes", [])
expect(len(episodes) == protocol.get("total_episodes", 0), "one auditable event trace is required per episode")
expect(all(isinstance(item.get("events"), list) for item in episodes), "every episode must contain a tool event list")
extension = data.get("xlerobot_robocrew_extension", {})
expect(extension.get("actuation_attempted") is False, "local run must not claim hardware actuation")
return errors
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("evidence", type=Path)
args = parser.parse_args()
try:
data = json.loads(args.evidence.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print(f"INVALID: {exc}", file=sys.stderr)
return 2
errors = validate(data, args.evidence.resolve().parent)
if errors:
print("INVALID")
for error in errors:
print(f"- {error}")
return 1
print("VALID: experiment 6-12 local GPU evidence")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

@@ -0,0 +1,322 @@
{
"artifacts": [
{
"kind": "metrics",
"path": "metrics.json",
"sha256": "e77f6a79ddf199c92eca1642883921cbd0fb8a2988763a2b1ef604a7e4dc0a91"
},
{
"kind": "events",
"path": "predictive_episode_events.json",
"sha256": "9fcb00648c516a3766e13e535fce41522f5d49881221c551e1675a22c7225421"
},
{
"kind": "scene",
"path": "scene_initial.png",
"sha256": "8afa759bc85408f14d75d03da1ad57e32dc6824879f3f9911374b0f866e369e5"
}
],
"blockers": [],
"experiment_id": "6-12",
"kind": "desktop_manipulation_planning",
"metrics": {
"cells": [
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 0,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "closed_loop",
"recoveries": 0,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "predictive",
"recoveries": 0,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 34,
"seed": 20260808,
"success_rate": 0.734375
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "closed_loop",
"recoveries": 34,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "predictive",
"recoveries": 34,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 60,
"seed": 20260808,
"success_rate": 0.53125
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "closed_loop",
"recoveries": 60,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "predictive",
"recoveries": 60,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 0,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "closed_loop",
"recoveries": 0,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "predictive",
"recoveries": 0,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 34,
"seed": 20260809,
"success_rate": 0.734375
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "closed_loop",
"recoveries": 34,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "predictive",
"recoveries": 34,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 60,
"seed": 20260809,
"success_rate": 0.53125
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "closed_loop",
"recoveries": 60,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "predictive",
"recoveries": 60,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 0,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "closed_loop",
"recoveries": 0,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "predictive",
"recoveries": 0,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 33,
"seed": 20260810,
"success_rate": 0.7421875
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.515625,
"mode": "closed_loop",
"recoveries": 33,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.515625,
"mode": "predictive",
"recoveries": 33,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 59,
"seed": 20260810,
"success_rate": 0.5390625
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.921875,
"mode": "closed_loop",
"recoveries": 59,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.921875,
"mode": "predictive",
"recoveries": 59,
"seed": 20260810,
"success_rate": 1.0
}
],
"deterministic_replay": true,
"device": {
"device": "mps",
"name": "Apple Metal Performance Shaders",
"torch": "2.7.0"
},
"models": [
{
"seed": 20260808,
"test_mse": 0.017016880214214325,
"train_mse": 0.017159195616841316
},
{
"seed": 20260809,
"test_mse": 0.012364446185529232,
"train_mse": 0.01264232862740755
},
{
"seed": 20260810,
"test_mse": 0.014977166429162025,
"train_mse": 0.015084807761013508
}
],
"protocol": {
"episodes_per_cell": 128,
"failure_probabilities": [
0.0,
0.25,
0.5
],
"seeds": [
20260808,
20260809,
20260810
],
"total_episodes": 3456
},
"wall_time_ms": 8197.975
},
"schema_version": "3.0",
"seed": 20260808,
"status": "complete",
"tool_contract": [
"observe_scene",
"pick",
"place",
"verify_state",
"stop"
],
"xlerobot_robocrew_extension": {
"actuation_attempted": false,
"status": "gated",
"tool_adapter_required": true
}
}
@@ -0,0 +1,285 @@
{
"cells": [
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 0,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "closed_loop",
"recoveries": 0,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "predictive",
"recoveries": 0,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 34,
"seed": 20260808,
"success_rate": 0.734375
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "closed_loop",
"recoveries": 34,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "predictive",
"recoveries": 34,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 60,
"seed": 20260808,
"success_rate": 0.53125
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "closed_loop",
"recoveries": 60,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "predictive",
"recoveries": 60,
"seed": 20260808,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 0,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "closed_loop",
"recoveries": 0,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "predictive",
"recoveries": 0,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 34,
"seed": 20260809,
"success_rate": 0.734375
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "closed_loop",
"recoveries": 34,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.53125,
"mode": "predictive",
"recoveries": 34,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 60,
"seed": 20260809,
"success_rate": 0.53125
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "closed_loop",
"recoveries": 60,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.9375,
"mode": "predictive",
"recoveries": 60,
"seed": 20260809,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 0,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "closed_loop",
"recoveries": 0,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.0,
"mean_tool_calls": 6.0,
"mode": "predictive",
"recoveries": 0,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 33,
"seed": 20260810,
"success_rate": 0.7421875
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.515625,
"mode": "closed_loop",
"recoveries": 33,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.25,
"mean_tool_calls": 6.515625,
"mode": "predictive",
"recoveries": 33,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 4.0,
"mode": "open_loop",
"recoveries": 59,
"seed": 20260810,
"success_rate": 0.5390625
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.921875,
"mode": "closed_loop",
"recoveries": 59,
"seed": 20260810,
"success_rate": 1.0
},
{
"episodes": 128,
"failure_probability": 0.5,
"mean_tool_calls": 6.921875,
"mode": "predictive",
"recoveries": 59,
"seed": 20260810,
"success_rate": 1.0
}
],
"deterministic_replay": true,
"device": {
"device": "mps",
"name": "Apple Metal Performance Shaders",
"torch": "2.7.0"
},
"models": [
{
"seed": 20260808,
"test_mse": 0.017016880214214325,
"train_mse": 0.017159195616841316
},
{
"seed": 20260809,
"test_mse": 0.012364446185529232,
"train_mse": 0.01264232862740755
},
{
"seed": 20260810,
"test_mse": 0.014977166429162025,
"train_mse": 0.015084807761013508
}
],
"protocol": {
"episodes_per_cell": 128,
"failure_probabilities": [
0.0,
0.25,
0.5
],
"seeds": [
20260808,
20260809,
20260810
],
"total_episodes": 3456
},
"wall_time_ms": 8197.975
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,46 @@
"""Book-local semantic tool contract for the optional RoboCrew/XLeRobot run.
The pinned navigation checkout exposes base-motion helpers, not a stable
semantic arm API. This module is therefore an explicit adapter boundary: the
local GPU experiment validates the contract, while a hardware integrator must
map each primitive to calibrated XLeRobot arm motions before enabling torque.
"""
from __future__ import annotations
TOOL_CONTRACT = (
{
"name": "observe_scene",
"description": "Capture a new RGB observation and return object/target state.",
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
},
{
"name": "pick",
"description": "Execute one bounded calibrated pick primitive.",
"parameters": {"type": "object", "properties": {"object_id": {"type": "string", "enum": ["red_cup", "yellow_paper"]}}, "required": ["object_id"], "additionalProperties": False},
},
{
"name": "place",
"description": "Execute one bounded calibrated place primitive.",
"parameters": {"type": "object", "properties": {"object_id": {"type": "string", "enum": ["red_cup", "yellow_paper"]}, "target_id": {"type": "string", "enum": ["tray", "bin"]}}, "required": ["object_id", "target_id"], "additionalProperties": False},
},
{
"name": "verify_state",
"description": "Check the postcondition using a fresh observation.",
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
},
{
"name": "stop",
"description": "Stop all motion and enter a safe state.",
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
},
)
def robocrew_function_declarations() -> list[dict[str, object]]:
"""Return JSON-compatible declarations for a RoboCrew/Gemini bridge."""
return [
{"name": item["name"], "description": item["description"], "parameters": item["parameters"]}
for item in TOOL_CONTRACT
]