From 72003f348be953dd5145027df580b14253585e22 Mon Sep 17 00:00:00 2001 From: haironghu <15395595149@163.com> Date: Tue, 28 Jul 2026 20:25:58 +0800 Subject: [PATCH 1/5] feat(kernel): integrate OpenEarth SDK v2 --- examples/kernels/openearth/.gitignore | 8 + examples/kernels/openearth/README.md | 104 ++++++++++ examples/kernels/openearth/SHA256SUMS | 1 + .../kernels/openearth/config.yaml.example | 13 +- examples/kernels/openearth/docs/sdk-usage.md | 177 ++++++++++++++++++ examples/kernels/openearth/kernel.py | 126 +++++++------ .../kernels/openearth/tests/test_delivery.py | 46 +++++ ...openearth_skill_sdk-0.6.0-py3-none-any.whl | Bin 0 -> 30738 bytes 8 files changed, 415 insertions(+), 60 deletions(-) create mode 100644 examples/kernels/openearth/.gitignore create mode 100644 examples/kernels/openearth/README.md create mode 100644 examples/kernels/openearth/SHA256SUMS create mode 100644 examples/kernels/openearth/docs/sdk-usage.md create mode 100644 examples/kernels/openearth/tests/test_delivery.py create mode 100644 examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl diff --git a/examples/kernels/openearth/.gitignore b/examples/kernels/openearth/.gitignore new file mode 100644 index 00000000..82eb8328 --- /dev/null +++ b/examples/kernels/openearth/.gitignore @@ -0,0 +1,8 @@ +# OpenEarth SDK build inputs are private and stay local. XSkill receives only +# the bridge, public documentation, configuration example, wheel, and checksum. +sdk/ + +__pycache__/ +config.yaml +workspace/ +output/ diff --git a/examples/kernels/openearth/README.md b/examples/kernels/openearth/README.md new file mode 100644 index 00000000..a3ae33a1 --- /dev/null +++ b/examples/kernels/openearth/README.md @@ -0,0 +1,104 @@ +# OpenEarth Kernel + +这个目录是 OpenEarth 接入 XSkill Kernel API v2 的可交付桥接包。训练只有一个 SDK +入口 `train_skills(...)`,输入始终是 XSkill 轨迹,不再区分“真实数据训练接口”和“数据集 +任务训练接口”。 + +## 数据流 + +```text +ready TrajectoryResource + └─ atoms: AtomResource[] + ├─ user trajectory → atom.ux_score + └─ temp trajectory → OpenEarth oracle score + ↓ + ScoredAtomInput + ↓ + reflect / curate → SkillDraft + ↓ + context.publisher.submit(...) +``` + +桥接层仅提交 `atom_split_status == "ready"` 的轨迹。SDK 将每条轨迹展开成 atom,并以 +`#` 作为稳定证据 ID: + +- 真实用户轨迹(`source="user"`)直接读取每个 `atom.ux_score`; +- 评测轨迹(`source="temp"`)忽略 atom 自身分数,读取 OpenEarth 在 + `context.workspace` 中按 `trajectory_id` 保存的 oracle 分数; +- `pending` 和 `updated` 轨迹不会进入本轮训练; +- UX 7–10 为成功,1–5 为失败,6 或未评分暂缓; +- 当前链路没有 Gate 阶段。 + +已有 XSkill Skill 会作为只读 main 快照传给 SDK。显式声明 `level` 的 Skill 直接进入 +对应层;没有 `level` 的 Skill 由 OpenEarth 在一次批量 LLM 调用中分类为 +`planning`、`functional` 或 `unclassified`。分类结果按 `name + main_commit_sha` +缓存在 workspace,不会写回原 Skill;低于 0.7 置信度的结果保持未分类。 + +## OpenEarth 处理流程 + +一次 Kernel 运行大致分为以下阶段: + +1. **选择输入**:优先处理本轮变化且已经完成 atom 拆分的轨迹;手动全量运行时读取全部 + ready 轨迹。 +2. **同步已有 Skill**:把 XSkill main Skill 的完整只读快照放入 OpenEarth workspace, + 并对没有 `level` 的新增或变化 Skill 做一次批量分类。 +3. **构造训练证据**:把每条轨迹展开成 atom,真实数据使用 `atom.ux_score`,临时评测 + 数据使用已记录的 OpenEarth oracle score。未变化的 atom 通过稳定证据 ID 和内容签名 + 去重。 +4. **划分反思通道**:UX 7–10 的成功 atom 用于提炼可复用 Planning Skill;UX 1–5 + 的失败 atom 先检索已有 Planning Skill,找不到时生成临时诊断计划,再反思产生 + Functional 修复候选。 +5. **整理候选**:对本轮 Functional 候选做一次全局 curation,可保留、修改、合并或 + 丢弃候选,同时校验其 atom 来源。 +6. **生成草稿**:候选名称与已有 Skill 完全相同时生成 update draft,并保留原 bundle; + 否则生成 create draft。 +7. **交给 XSkill 发布**:SDK 只返回 `SkillDraft`。Kernel 再通过 + `context.publisher.submit(...)` 发布;已有 active staging 的 Skill 会被跳过。 + +当前到第 6 步即结束 OpenEarth 训练,不执行 OpenEarth candidate rollout 或 Gate。 +XSkill 自己的 staging/canary 发布机制不受影响。 + +## 安装 + +SDK 源码位于本地 `sdk/`,由本目录的 `.gitignore` 排除,不会提交到 XSkill 远程仓库。 +仓库交付的是构建后的 wheel: + +```bash +python -m pip install \ + examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl +``` + +然后复制 Kernel 目录并创建私有配置: + +```bash +mkdir -p "$HOME/.xskill/kernels" +cp -R examples/kernels/openearth "$HOME/.xskill/kernels/openearth" +cp "$HOME/.xskill/kernels/openearth/config.yaml.example" \ + "$HOME/.xskill/kernels/openearth/config.yaml" +``` + +在 `~/.xskill/config.yaml` 中选择: + +```yaml +kernel: + kernels_path: ~/.xskill/kernels + kernel_id: openearth +``` + +详细 SDK 和评测接入方式见 [docs/sdk-usage.md](docs/sdk-usage.md)。 + +## 构建私有 SDK + +在本地源码目录执行: + +```bash +cd examples/kernels/openearth/sdk +python -m pip wheel \ + --no-deps \ + --no-build-isolation \ + --wheel-dir ../wheels \ + . +``` + +每次重建后同步更新 `SHA256SUMS`。wheel 中只应包含运行时代码,不应包含 Gate、 +dataset task/evaluator 或 SDK 测试。 diff --git a/examples/kernels/openearth/SHA256SUMS b/examples/kernels/openearth/SHA256SUMS new file mode 100644 index 00000000..396fdc0b --- /dev/null +++ b/examples/kernels/openearth/SHA256SUMS @@ -0,0 +1 @@ +54973815ff39bb64ec5a7a8aca5e0a8231e9b1d8f8f412359b9eec36ae4e8f81 wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl diff --git a/examples/kernels/openearth/config.yaml.example b/examples/kernels/openearth/config.yaml.example index 5db88275..aadabfc7 100644 --- a/examples/kernels/openearth/config.yaml.example +++ b/examples/kernels/openearth/config.yaml.example @@ -1,4 +1,9 @@ -# This file belongs to the OpenEarth kernel. XSkill never parses or edits it. -model: YOUR_OPENEARTH_MODEL -api_key: YOUR_OPENEARTH_KEY - +# Private OpenEarth SDK configuration. XSkill treats this file as opaque. +reflect: + # OpenEarth currently invokes the configured model through OpenCode. + base_url: opencode + model: YOUR_OPENEARTH_MODEL + binary: opencode + timeout: 600 + # Optional custom reflection strategy Markdown: + # strategy: /absolute/path/to/strategy.md diff --git a/examples/kernels/openearth/docs/sdk-usage.md b/examples/kernels/openearth/docs/sdk-usage.md new file mode 100644 index 00000000..7fd02635 --- /dev/null +++ b/examples/kernels/openearth/docs/sdk-usage.md @@ -0,0 +1,177 @@ +# OpenEarth SDK 接口 + +## 统一训练入口 + +XSkill 桥接层调用唯一入口: + +```python +from openearth_skill_sdk import ExistingSkillInput, train_skills + +result = train_skills( + config_path=context.config_path, + workspace=context.workspace, + trajectories=ready_trajectories, + existing_skills=existing_skills, + run_id=context.run_id, +) +``` + +`trajectories` 可以是 `TrajectoryResource` iterable,也可以是提供 `iter()` 的 +`TrajectoryReader`。生产桥接层会先筛选 `atom_split_status == "ready"` 的资源。 + +SDK 内部将 `trajectory.atoms` 转成 OpenEarth 自己定义、与 XSkill provider 解耦的 +`ScoredAtomInput`: + +```python +@dataclass(frozen=True) +class ScoredAtomInput: + atom_id: str + evidence_id: str + parent_trajectory_id: str + content: str + ux_score: int | None + intent: str = "" + summary: str = "" + used_skills: tuple[str, ...] = () + score_source: Literal["xskill", "oracle"] = "xskill" + metadata: Mapping[str, object] = field(default_factory=dict) +``` + +普通接入方不需要自行构造它;它也是 SDK 的公开中间契约,便于脱离 XSkill +`TrajectoryResource` 做单元测试或其他 provider 适配。 + +## 已有 Skill 的分层 + +Kernel 将 XSkill 的所有已有 main Skill 作为 `ExistingSkillInput` 传入,包括完整 +`SKILL.md`、其他 bundle 文本文件和 `main_commit_sha`。 + +SDK 按以下顺序决定反思层级: + +1. 顶层 `level` 或 `metadata.level` 已明确声明时直接沿用,不调用 LLM; +2. 没有 `level` 时,将本轮所有新增或版本变化的未分类 Skill 放入一次批量 LLM 调用; +3. 分类输出只能是 `planning`、`functional` 或 `unclassified`; +4. `planning` / `functional` 的置信度至少为 0.7 才生效,否则保持 + `unclassified`; +5. 结果按 Skill 名称和 provider `version_token` 缓存。XSkill 中该 token 是 + `main_commit_sha`,main 未变化时不重复调用。 + +缓存位于: + +```text +/openearth-skill-level-classifications.json +``` + +这是 OpenEarth 私有映射,不会修改 XSkill 原 Skill 的 frontmatter。未分类 Skill 不再 +默认当作 Functional,也不会进入 Planning/Functional 反思上下文。候选名称与已有 Skill +名称完全相同时,SDK 才生成 update draft。 + +## 训练流水线 + +`train_skills` 内部按以下顺序处理: + +```text +ready trajectories + → 展开并标准化 ScoredAtomInput + → 使用 evidence_id + 内容/评分签名过滤未变化 atom + → 按 UX 分数划分 success / failure / deferred + → success:提炼 Planning 候选 + → failure:检索 Planning 上下文或生成临时诊断计划 + 再生成 Functional 修复候选 + → 全局整理 Functional 候选 + → 组装 create/update SkillDraft +``` + +具体行为: + +- **证据去重**:`evidence_id` 由轨迹资源 ID 和 atom ID 组成;内容、评分、评分来源或摘要 + 发生变化时,同一 atom 可以重新进入反思。 +- **Planning 通道**:成功 atom 用来提炼跨步骤、可复用的规划;本轮新 Planning 候选与 + 已有 Planning Skill 一起构成失败通道的检索库。 +- **Functional 通道**:每个失败 atom 先检索相关 Planning Skill;没有匹配项时只生成 + 本轮使用的临时诊断计划,不把它作为 Skill 发布。随后生成局部操作或修复候选。 +- **Curation**:对本轮所有 Functional 候选做一次全局整理,可以保留、编辑、合并或 + 丢弃,但必须保留可验证的来源 atom。 +- **草稿判定**:候选名称与已有 Skill 名称精确一致时为 update,否则为 create。update + 会继承已有 frontmatter 中的非优化字段和其他 bundle 文件。 +- **状态边界**:轨迹证据、分类缓存、处理签名及候选保存在 + `context.workspace`;SDK 不直接修改 XSkill Skill 仓库。 +- **发布边界**:SDK 返回草稿后,由 Kernel 调用 XSkill Publisher。当前不执行 + OpenEarth Gate,但 XSkill 仍按自身 staging/canary 规则处理已有 Skill 的更新。 + +## 真实用户数据 + +真实数据无需额外评分接口。SDK 对 ready 轨迹逐个展开 atom,直接读取 XSkill 已提供的 +`atom.ux_score`: + +```python +for trajectory in ready_trajectories: + for atom in trajectory.atoms: + # atom.ux_score: 1..10 或 None + ... +``` + +OpenEarth 不调用 XSkill 内部 UX scorer,也不读取轨迹级分数,因为 +`TrajectoryResource` 没有轨迹级 `ux_score`。 + +## 评测数据和 oracle 分数 + +评测器先执行 OpenEarth rollout 并计算 oracle score,再使用同一个稳定 +`trajectory_id` 依次写分和创建临时轨迹: + +```python +from openearth_skill_sdk import record_oracle_score + +trajectory_id = "traj_oe_case_001" + +record_oracle_score( + workspace=context.workspace, + trajectory_id=trajectory_id, + ux_score=1, # 1..10 + case_id="case-001", + metadata={"suite": "smoke"}, +) + +temp = context.trajectories.create_temp( + markdown=( + "## User\n\n" + "Benchmark task.\n\n" + "## Assistant\n\n" + "Rollout result.\n" + ), + trajectory_id=trajectory_id, +) + +assert temp.source == "temp" +assert temp.atom_split_status == "pending" +``` + +`create_temp` 只登记待拆分轨迹;不要轮询。平台完成 atom 拆分后,它会在后续 Kernel +调用中以 `ready` 资源出现,此时 SDK 按 `trajectory.trajectory_id` 找到已保存的 oracle +分数并训练。 + +一个 oracle 分数必须只对应一个 atom,因此评测 Markdown 应表达一个完整的 +User/Assistant rollout。若 ready 临时轨迹被拆成多个 atom,SDK 会报错,避免把一个 case +级分数错误地复制到多段证据。 + +oracle 分数保存在: + +```text +/openearth-oracle-scores.json +``` + +这是 OpenEarth 私有状态,不写入 XSkill 的 trajectory sidecar,也不会调用或覆盖 +`atom.ux_score`。 + +## 输出与发布 + +`train_skills` 返回 `TrainingResult`,其中包含: + +- `drafts`:完整 Skill 草稿; +- `processed_trajectory_ids`:已消费的 XSkill 轨迹资源 ID; +- `processed_atom_ids`:已消费的稳定 atom 证据 ID; +- `metrics`:成功、失败、暂缓和评分来源计数; +- `candidate_dir`:OpenEarth workspace 中的候选目录。 + +SDK 不直接写 XSkill Skill 仓库。`kernel.py` 通过 +`context.publisher.submit(SkillSubmission(...))` 发布,并在已有 Skill 存在 active +staging 时跳过该草稿。本版本暂时不执行 Gate。 diff --git a/examples/kernels/openearth/kernel.py b/examples/kernels/openearth/kernel.py index 2a049337..ed3b094e 100644 --- a/examples/kernels/openearth/kernel.py +++ b/examples/kernels/openearth/kernel.py @@ -1,30 +1,22 @@ -"""Illustrative local bridge for an SDK package named ``openearth``. +"""XSkill bridge for the private OpenEarth Skill SDK wheel. -Copy this directory to ``~/.xskill/kernels/openearth`` and replace the marked -adapter call with the real SDK API. XSkill imports this bridge as trusted code. - -Trajectory / atom feed notes for OpenEarth adapters: - -- Prefer ``context.invocation.changed_trajectory_ids`` (ready-only). Read - ``traj.atoms`` and dedupe by ``atom_id`` in ``context.workspace``. -- Mother trajectory ``read_text()`` is always available; there is no - trajectory-level ``ux_score`` (scores live on atoms / Skill versions). -- For algorithm-owned rollouts, convert OE harness markdown to **platform** - style (``## User`` / ``## Assistant`` / …) on the OE side, then call - ``context.trajectories.create_temp(markdown, trajectory_id=...)``. - ``trajectory_id`` must match ``traj_[a-z0-9]...``. The returned resource is - ``source="temp"`` / ``atom_split_status="pending"``; do **not** spin-wait — - atoms arrive later via the ready feed. +The SDK receives only ready trajectory resources. It expands their Atom +views, using XSkill ``atom.ux_score`` for user trajectories and the OpenEarth +oracle score store for kernel-owned temporary benchmark trajectories. """ from __future__ import annotations try: - import openearth + from openearth_skill_sdk import ( + ExistingSkillInput, + __version__, + train_skills, + ) except ImportError as exc: raise ImportError( - "OpenEarth kernel requires its SDK package; install it in the same " - "Python environment as xskill" + "OpenEarth kernel requires openearth-skill-sdk; install the supplied " + "wheel in the same Python environment as xskill" ) from exc from xskill.kernels import ( @@ -35,71 +27,93 @@ ) +def _existing_skills(context) -> tuple[ExistingSkillInput, ...]: + snapshots = [] + for skill in context.skills.list(): + files = { + relative_path: skill.read_text(relative_path) + for relative_path in skill.list_files() + if relative_path != "SKILL.md" + } + snapshots.append(ExistingSkillInput( + name=skill.name, + skill_md=skill.read_text(), + files=files, + version_token=skill.main_commit_sha, + )) + return tuple(snapshots) + + class OpenEarthKernel(BaseKernel): metadata = KernelMetadata( id="openearth", name="OpenEarth", - version=getattr(openearth, "__version__", "unknown"), - description="OpenEarth SDK bridge example.", + version=__version__, + description="OpenEarth Atom-based Skill training bridge.", triggers=("scheduled", "manual"), api_version=2, ) def run(self, context, run_interval: int = 30) -> KernelRunResult: - # Prefer the ready-only changed feed when the host provides it. - # Offline distill / empty changed → walk all mother trajectories. - changed = tuple(context.invocation.changed_trajectory_ids) - if changed: - by_id = {item.id: item for item in context.trajectories.list()} - trajectories = [by_id[item_id] for item_id in changed if item_id in by_id] - else: - trajectories = context.trajectories.list() - - # Illustrative create_temp path (adapter must convert markdown first): - # - # platform_md = openearth.to_platform_markdown(oe_rollout_md) - # temp = context.trajectories.create_temp( - # platform_md, - # trajectory_id="traj_oe_rollout_001", - # ) - # # temp.source == "temp"; temp.atom_split_status == "pending" - # # Do not poll — next ready feed will include atoms when split done. - # - # When consuming a ready trajectory: - # for atom in traj.atoms: # atom.atom_id / content / ux_score / used_skills - # ... + del run_interval + all_trajectories = context.trajectories.list() + changed = set(context.invocation.changed_trajectory_ids) + selected = [ + trajectory + for trajectory in all_trajectories + if (not changed or trajectory.id in changed) + and trajectory.atom_split_status == "ready" + ] - # Adapt this single call to the real SDK. The SDK owns and parses its - # config; XSkill only passes the opaque path and standard inputs. - drafts = openearth.xskill_distill( # type: ignore[attr-defined] + result = train_skills( config_path=context.config_path, workspace=context.workspace, - trajectories=trajectories, - dataset_id=context.invocation.dataset_id, + trajectories=selected, + existing_skills=_existing_skills(context), + run_id=context.run_id, ) submitted = [] - consumed = [] - for draft in drafts: + skipped_active_staging = [] + for draft in result.drafts: base_commit = None try: - base_commit = context.skills.get(draft.name).main_commit_sha + current = context.skills.get(draft.name) except KeyError: - pass + current = None + if current is not None: + if current.staging_commit_sha: + skipped_active_staging.append(draft.name) + continue + base_commit = current.main_commit_sha context.publisher.submit(SkillSubmission( name=draft.name, skill_md=draft.skill_md, files=draft.files, - source_trajectory_ids=tuple(draft.trajectory_ids), + source_trajectory_ids=draft.source_trajectory_ids, message=f"OpenEarth run {context.run_id}", base_commit_sha=base_commit, )) submitted.append(draft.name) - consumed.extend(draft.trajectory_ids) + + metrics = { + **dict(result.metrics), + "selected_trajectories": len(selected), + "processed_atoms": len(result.processed_atom_ids), + "generated_drafts": len(result.drafts), + "published_drafts": len(submitted), + "skipped_active_staging": len(skipped_active_staging), + } + if result.candidate_dir: + metrics["candidate_dir"] = result.candidate_dir return KernelRunResult( - processed_trajectory_ids=tuple(dict.fromkeys(consumed)), + processed_trajectory_ids=result.processed_trajectory_ids, submitted_skills=tuple(submitted), - metrics={"sdk_outputs": len(submitted)}, + metrics=metrics, + notes=( + "OpenEarth trained from ready trajectory Atom views; " + "XSkill owns publication." + ), ) diff --git a/examples/kernels/openearth/tests/test_delivery.py b/examples/kernels/openearth/tests/test_delivery.py new file mode 100644 index 00000000..9834ba95 --- /dev/null +++ b/examples/kernels/openearth/tests/test_delivery.py @@ -0,0 +1,46 @@ +"""Checks for the source-private OpenEarth Kernel delivery bundle.""" + +from __future__ import annotations + +import hashlib +from pathlib import Path +from zipfile import ZipFile + + +OPENEARTH = Path(__file__).resolve().parents[1] +WHEEL = ( + OPENEARTH / "wheels" + / "openearth_skill_sdk-0.6.0-py3-none-any.whl" +) + + +def test_openearth_wheel_checksum_and_public_contents(): + expected = (OPENEARTH / "SHA256SUMS").read_text(encoding="utf-8").split()[0] + assert hashlib.sha256(WHEEL.read_bytes()).hexdigest() == expected + + with ZipFile(WHEEL) as archive: + names = set(archive.namelist()) + metadata = archive.read( + "openearth_skill_sdk-0.6.0.dist-info/METADATA" + ).decode("utf-8") + + assert "Version: 0.6.0" in metadata + assert "openearth_skill_sdk/xskill.py" in names + assert not any( + forbidden in name + for name in names + for forbidden in ("/gate.py", "/experiment.py", "/tests/") + ) + + +def test_openearth_delivery_documents_atom_score_sources(): + readme = (OPENEARTH / "README.md").read_text(encoding="utf-8") + usage = ( + OPENEARTH / "docs" / "sdk-usage.md" + ).read_text(encoding="utf-8") + + assert "atom.ux_score" in readme + assert "OpenEarth oracle score" in readme + assert "record_oracle_score" in usage + assert "context.trajectories.create_temp" in usage + assert "openearth-skill-level-classifications.json" in usage diff --git a/examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl b/examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..1c1b5b7de76d7c4e05ecc2479166bbc866d0c2b4 GIT binary patch literal 30738 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 Date: Wed, 29 Jul 2026 15:40:20 +0800 Subject: [PATCH 2/5] fix(kernel): honor full rebuild trajectory scope --- examples/kernels/openearth/README.md | 23 ++++++++--- examples/kernels/openearth/SHA256SUMS | 2 +- examples/kernels/openearth/docs/sdk-usage.md | 29 +++++++++++-- examples/kernels/openearth/kernel.py | 39 ++++++++++++++---- .../kernels/openearth/tests/test_delivery.py | 9 ++-- ...penearth_skill_sdk-0.7.0-py3-none-any.whl} | Bin 30738 -> 31198 bytes 6 files changed, 81 insertions(+), 21 deletions(-) rename examples/kernels/openearth/wheels/{openearth_skill_sdk-0.6.0-py3-none-any.whl => openearth_skill_sdk-0.7.0-py3-none-any.whl} (68%) diff --git a/examples/kernels/openearth/README.md b/examples/kernels/openearth/README.md index a3ae33a1..edd4dabf 100644 --- a/examples/kernels/openearth/README.md +++ b/examples/kernels/openearth/README.md @@ -19,8 +19,14 @@ ready TrajectoryResource context.publisher.submit(...) ``` -桥接层仅提交 `atom_split_status == "ready"` 的轨迹。SDK 将每条轨迹展开成 atom,并以 -`#` 作为稳定证据 ID: +桥接层仅提交 `atom_split_status == "ready"` 的轨迹。输入范围严格遵循 +`context.invocation`: + +- `changed_trajectory_ids` 非空时,只处理其中的 ready 轨迹; +- changed 为空且 `full_rebuild=True` 时,处理全部 ready 轨迹; +- changed 为空且非 full rebuild 时直接结束,不读取 Skill、不调用 SDK。 + +SDK 将每条轨迹展开成 atom,并以 `#` 作为稳定证据 ID: - 真实用户轨迹(`source="user"`)直接读取每个 `atom.ux_score`; - 评测轨迹(`source="temp"`)忽略 atom 自身分数,读取 OpenEarth 在 @@ -39,12 +45,13 @@ ready TrajectoryResource 一次 Kernel 运行大致分为以下阶段: 1. **选择输入**:优先处理本轮变化且已经完成 atom 拆分的轨迹;手动全量运行时读取全部 - ready 轨迹。 + ready 轨迹;没有变化且不是全量重建时本轮不做任何训练。 2. **同步已有 Skill**:把 XSkill main Skill 的完整只读快照放入 OpenEarth workspace, 并对没有 `level` 的新增或变化 Skill 做一次批量分类。 3. **构造训练证据**:把每条轨迹展开成 atom,真实数据使用 `atom.ux_score`,临时评测 - 数据使用已记录的 OpenEarth oracle score。未变化的 atom 通过稳定证据 ID 和内容签名 - 去重。 + 数据使用已记录的 OpenEarth oracle score。增量运行通过稳定证据 ID 和内容签名跳过 + 历史未变化 atom;全量重建会重新蒸馏历史 atom,但同一批内仍按 `atom_id` 去重, + 同 ID 不同内容或评分会直接报冲突。 4. **划分反思通道**:UX 7–10 的成功 atom 用于提炼可复用 Planning Skill;UX 1–5 的失败 atom 先检索已有 Planning Skill,找不到时生成临时诊断计划,再反思产生 Functional 修复候选。 @@ -58,6 +65,10 @@ ready TrajectoryResource 当前到第 6 步即结束 OpenEarth 训练,不执行 OpenEarth candidate rollout 或 Gate。 XSkill 自己的 staging/canary 发布机制不受影响。 +全量重建仍以当前 main Skill 作为只读反思上下文,并复用 +`name + main_commit_sha` 分类缓存,因此不会重新分类未变化的 Skill。重建只生成并提交 +本轮草稿,不会删除没有重新生成的旧 Skill。 + ## 安装 SDK 源码位于本地 `sdk/`,由本目录的 `.gitignore` 排除,不会提交到 XSkill 远程仓库。 @@ -65,7 +76,7 @@ SDK 源码位于本地 `sdk/`,由本目录的 `.gitignore` 排除,不会提 ```bash python -m pip install \ - examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl + examples/kernels/openearth/wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl ``` 然后复制 Kernel 目录并创建私有配置: diff --git a/examples/kernels/openearth/SHA256SUMS b/examples/kernels/openearth/SHA256SUMS index 396fdc0b..cd8b7ceb 100644 --- a/examples/kernels/openearth/SHA256SUMS +++ b/examples/kernels/openearth/SHA256SUMS @@ -1 +1 @@ -54973815ff39bb64ec5a7a8aca5e0a8231e9b1d8f8f412359b9eec36ae4e8f81 wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl +7ac712a5d97db4386354b25c407e79d4e7f2cb38e4c3cc352e6f850a7d45244c wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl diff --git a/examples/kernels/openearth/docs/sdk-usage.md b/examples/kernels/openearth/docs/sdk-usage.md index 7fd02635..20bd8ad2 100644 --- a/examples/kernels/openearth/docs/sdk-usage.md +++ b/examples/kernels/openearth/docs/sdk-usage.md @@ -13,6 +13,7 @@ result = train_skills( trajectories=ready_trajectories, existing_skills=existing_skills, run_id=context.run_id, + full_rebuild=context.invocation.full_rebuild, ) ``` @@ -40,6 +41,25 @@ class ScoredAtomInput: 普通接入方不需要自行构造它;它也是 SDK 的公开中间契约,便于脱离 XSkill `TrajectoryResource` 做单元测试或其他 provider 适配。 +## 增量与全量重建 + +Kernel 按下面的三态契约选择 SDK 输入: + +| `changed_trajectory_ids` | `full_rebuild` | 本轮行为 | +| --- | --- | --- | +| 非空 | 任意值 | 只选择 changed 中的 ready 轨迹 | +| 空 | `True` | 选择全部 ready 轨迹 | +| 空 | `False` | 立即结束;不读取 Skill,也不调用 SDK | + +changed 始终拥有输入范围的优先级。`full_rebuild=True` 会传给 SDK,使所选轨迹中的历史 +atom 绕过跨运行签名去重并重新进入蒸馏。无论是否重建,同一训练批次都按稳定 +`atom_id` 去重:ID 和签名都相同只处理一次;同一 ID 对应不同证据、内容或评分时抛出 +冲突错误,避免重复证据产生不一致结果。 + +全量重建仍读取当前 main Skill 作为反思上下文,但 `name + main_commit_sha` 未变化的 +Skill 会命中原分类缓存,不再次调用分类 LLM。SDK 和 Kernel 都不会因为某个旧 Skill +没有在本轮重新生成就将它删除。 + ## 已有 Skill 的分层 Kernel 将 XSkill 的所有已有 main Skill 作为 `ExistingSkillInput` 传入,包括完整 @@ -72,7 +92,9 @@ SDK 按以下顺序决定反思层级: ```text ready trajectories → 展开并标准化 ScoredAtomInput - → 使用 evidence_id + 内容/评分签名过滤未变化 atom + → 批内按 atom_id 去重 + → 增量时使用 evidence_id + 内容/评分签名过滤历史未变化 atom + (full rebuild 绕过这一层) → 按 UX 分数划分 success / failure / deferred → success:提炼 Planning 候选 → failure:检索 Planning 上下文或生成临时诊断计划 @@ -83,8 +105,9 @@ ready trajectories 具体行为: -- **证据去重**:`evidence_id` 由轨迹资源 ID 和 atom ID 组成;内容、评分、评分来源或摘要 - 发生变化时,同一 atom 可以重新进入反思。 +- **证据去重**:批内先按 `atom_id` 去重并拒绝冲突副本。跨运行状态使用 + `evidence_id`(轨迹资源 ID 与 atom ID)及内容/评分签名;增量时内容、评分、评分来源 + 或摘要发生变化才重新进入反思,全量重建则重新处理历史证据。 - **Planning 通道**:成功 atom 用来提炼跨步骤、可复用的规划;本轮新 Planning 候选与 已有 Planning Skill 一起构成失败通道的检索库。 - **Functional 通道**:每个失败 atom 先检索相关 Planning Skill;没有匹配项时只生成 diff --git a/examples/kernels/openearth/kernel.py b/examples/kernels/openearth/kernel.py index ed3b094e..aad3ead1 100644 --- a/examples/kernels/openearth/kernel.py +++ b/examples/kernels/openearth/kernel.py @@ -56,14 +56,34 @@ class OpenEarthKernel(BaseKernel): def run(self, context, run_interval: int = 30) -> KernelRunResult: del run_interval - all_trajectories = context.trajectories.list() - changed = set(context.invocation.changed_trajectory_ids) - selected = [ - trajectory - for trajectory in all_trajectories - if (not changed or trajectory.id in changed) - and trajectory.atom_split_status == "ready" - ] + changed = tuple(context.invocation.changed_trajectory_ids) + full_rebuild = bool(context.invocation.full_rebuild) + if changed: + changed_ids = set(changed) + selected = [ + trajectory + for trajectory in context.trajectories.list() + if trajectory.id in changed_ids + and trajectory.atom_split_status == "ready" + ] + elif full_rebuild: + selected = [ + trajectory + for trajectory in context.trajectories.list() + if trajectory.atom_split_status == "ready" + ] + else: + return KernelRunResult( + metrics={ + "selected_trajectories": 0, + "full_rebuild": False, + "no_changes": True, + }, + notes=( + "No changed trajectories and full_rebuild is false; " + "OpenEarth did not invoke the SDK." + ), + ) result = train_skills( config_path=context.config_path, @@ -71,6 +91,7 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: trajectories=selected, existing_skills=_existing_skills(context), run_id=context.run_id, + full_rebuild=full_rebuild, ) submitted = [] @@ -99,6 +120,8 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: metrics = { **dict(result.metrics), "selected_trajectories": len(selected), + "full_rebuild": full_rebuild, + "no_changes": False, "processed_atoms": len(result.processed_atom_ids), "generated_drafts": len(result.drafts), "published_drafts": len(submitted), diff --git a/examples/kernels/openearth/tests/test_delivery.py b/examples/kernels/openearth/tests/test_delivery.py index 9834ba95..aa6bd46f 100644 --- a/examples/kernels/openearth/tests/test_delivery.py +++ b/examples/kernels/openearth/tests/test_delivery.py @@ -10,7 +10,7 @@ OPENEARTH = Path(__file__).resolve().parents[1] WHEEL = ( OPENEARTH / "wheels" - / "openearth_skill_sdk-0.6.0-py3-none-any.whl" + / "openearth_skill_sdk-0.7.0-py3-none-any.whl" ) @@ -21,10 +21,10 @@ def test_openearth_wheel_checksum_and_public_contents(): with ZipFile(WHEEL) as archive: names = set(archive.namelist()) metadata = archive.read( - "openearth_skill_sdk-0.6.0.dist-info/METADATA" + "openearth_skill_sdk-0.7.0.dist-info/METADATA" ).decode("utf-8") - assert "Version: 0.6.0" in metadata + assert "Version: 0.7.0" in metadata assert "openearth_skill_sdk/xskill.py" in names assert not any( forbidden in name @@ -44,3 +44,6 @@ def test_openearth_delivery_documents_atom_score_sources(): assert "record_oracle_score" in usage assert "context.trajectories.create_temp" in usage assert "openearth-skill-level-classifications.json" in usage + assert "changed_trajectory_ids" in usage + assert "full_rebuild" in usage + assert "atom_id" in usage diff --git a/examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl b/examples/kernels/openearth/wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl similarity index 68% rename from examples/kernels/openearth/wheels/openearth_skill_sdk-0.6.0-py3-none-any.whl rename to examples/kernels/openearth/wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl index 1c1b5b7de76d7c4e05ecc2479166bbc866d0c2b4..6913a3e5183f1ef10bb6931321546494c9b58970 100644 GIT binary patch delta 7096 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 W-`whhsGR?MPe?Cj^i21^=l%=Btx}r+ delta 6546 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 Date: Wed, 29 Jul 2026 16:29:05 +0800 Subject: [PATCH 3/5] feat(kernel): queue drafts behind active staging --- examples/kernels/openearth/README.md | 24 +- examples/kernels/openearth/SHA256SUMS | 2 +- examples/kernels/openearth/docs/sdk-usage.md | 28 +- examples/kernels/openearth/kernel.py | 258 +++++++++++++++--- .../kernels/openearth/tests/test_delivery.py | 9 +- ...penearth_skill_sdk-0.8.0-py3-none-any.whl} | Bin 31198 -> 32401 bytes 6 files changed, 273 insertions(+), 48 deletions(-) rename examples/kernels/openearth/wheels/{openearth_skill_sdk-0.7.0-py3-none-any.whl => openearth_skill_sdk-0.8.0-py3-none-any.whl} (80%) diff --git a/examples/kernels/openearth/README.md b/examples/kernels/openearth/README.md index edd4dabf..7a461766 100644 --- a/examples/kernels/openearth/README.md +++ b/examples/kernels/openearth/README.md @@ -24,7 +24,7 @@ ready TrajectoryResource - `changed_trajectory_ids` 非空时,只处理其中的 ready 轨迹; - changed 为空且 `full_rebuild=True` 时,处理全部 ready 轨迹; -- changed 为空且非 full rebuild 时直接结束,不读取 Skill、不调用 SDK。 +- changed 为空且非 full rebuild 时不蒸馏轨迹、不调用 SDK 训练入口,但仍检查待发布队列。 SDK 将每条轨迹展开成 atom,并以 `#` 作为稳定证据 ID: @@ -60,7 +60,8 @@ SDK 将每条轨迹展开成 atom,并以 `#` 6. **生成草稿**:候选名称与已有 Skill 完全相同时生成 update draft,并保留原 bundle; 否则生成 create draft。 7. **交给 XSkill 发布**:SDK 只返回 `SkillDraft`。Kernel 再通过 - `context.publisher.submit(...)` 发布;已有 active staging 的 Skill 会被跳过。 + `context.publisher.submit(...)` 发布;已有 active staging 的 Skill 会进入 OpenEarth + 待发布队列。 当前到第 6 步即结束 OpenEarth 训练,不执行 OpenEarth candidate rollout 或 Gate。 XSkill 自己的 staging/canary 发布机制不受影响。 @@ -69,6 +70,23 @@ XSkill 自己的 staging/canary 发布机制不受影响。 `name + main_commit_sha` 分类缓存,因此不会重新分类未变化的 Skill。重建只生成并提交 本轮草稿,不会删除没有重新生成的旧 Skill。 +## staging 发布队列 + +XSkill 同一时刻只允许同名 Skill 存在一个 active staging。OpenEarth 不再丢弃因此无法 +提交的新草稿,而是在 `context.workspace/openearth-publication-queue.json` 中为每个 +Skill 保留一个最新 pending draft: + +- active staging 仍存在时继续等待;新 draft 覆盖同名旧 pending draft,避免陈旧版本 + 无限堆积; +- staging 被拒绝、main 未变化时,pending draft 使用原 base commit 直接提交; +- staging 晋升、main 已变化时,先保留最新 main 的 provider 元数据和 bundle 文件, + 再叠加 pending draft 的 OpenEarth 字段和正文,最后以新 main SHA 提交; +- 每次 scheduled/manual 调用都会先执行一次队列 tick,即使本轮没有 changed trajectory; +- 只有 Publisher 成功接受草稿后才从队列移除,发布竞态则重新读取 main/staging 状态。 + +队列只管理尚未进入 XSkill staging 的下一版本;已进入 staging 的版本仍完全由 XSkill +灰度、晋升或拒绝。 + ## 安装 SDK 源码位于本地 `sdk/`,由本目录的 `.gitignore` 排除,不会提交到 XSkill 远程仓库。 @@ -76,7 +94,7 @@ SDK 源码位于本地 `sdk/`,由本目录的 `.gitignore` 排除,不会提 ```bash python -m pip install \ - examples/kernels/openearth/wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl + examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl ``` 然后复制 Kernel 目录并创建私有配置: diff --git a/examples/kernels/openearth/SHA256SUMS b/examples/kernels/openearth/SHA256SUMS index cd8b7ceb..d5041028 100644 --- a/examples/kernels/openearth/SHA256SUMS +++ b/examples/kernels/openearth/SHA256SUMS @@ -1 +1 @@ -7ac712a5d97db4386354b25c407e79d4e7f2cb38e4c3cc352e6f850a7d45244c wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl +190c3544a54403c20cd22c12ae547e37c0ce59076525012ce170501da0d3db85 wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl diff --git a/examples/kernels/openearth/docs/sdk-usage.md b/examples/kernels/openearth/docs/sdk-usage.md index 20bd8ad2..188db716 100644 --- a/examples/kernels/openearth/docs/sdk-usage.md +++ b/examples/kernels/openearth/docs/sdk-usage.md @@ -49,7 +49,7 @@ Kernel 按下面的三态契约选择 SDK 输入: | --- | --- | --- | | 非空 | 任意值 | 只选择 changed 中的 ready 轨迹 | | 空 | `True` | 选择全部 ready 轨迹 | -| 空 | `False` | 立即结束;不读取 Skill,也不调用 SDK | +| 空 | `False` | 不训练轨迹;只执行待发布队列 tick | changed 始终拥有输入范围的优先级。`full_rebuild=True` 会传给 SDK,使所选轨迹中的历史 atom 绕过跨运行签名去重并重新进入蒸馏。无论是否重建,同一训练批次都按稳定 @@ -60,6 +60,9 @@ atom 绕过跨运行签名去重并重新进入蒸馏。无论是否重建,同 Skill 会命中原分类缓存,不再次调用分类 LLM。SDK 和 Kernel 都不会因为某个旧 Skill 没有在本轮重新生成就将它删除。 +队列为空时,上表最后一行不会读取轨迹或 Skill,也不会调用 SDK 训练入口;队列非空时 +只读取对应 Skill 的当前 main/staging 状态,不执行轨迹反思或 LLM 调用。 + ## 已有 Skill 的分层 Kernel 将 XSkill 的所有已有 main Skill 作为 `ExistingSkillInput` 传入,包括完整 @@ -196,5 +199,24 @@ oracle 分数保存在: - `candidate_dir`:OpenEarth workspace 中的候选目录。 SDK 不直接写 XSkill Skill 仓库。`kernel.py` 通过 -`context.publisher.submit(SkillSubmission(...))` 发布,并在已有 Skill 存在 active -staging 时跳过该草稿。本版本暂时不执行 Gate。 +`context.publisher.submit(SkillSubmission(...))` 发布。本版本暂时不执行 Gate。 + +### active staging 与排队 + +已有 Skill 存在 active staging 时,Kernel 将草稿写入: + +```text +/openearth-publication-queue.json +``` + +这是按 Skill 名称组织的 latest-wins 队列:一个 Skill 最多保留一个尚未提交的 pending +draft,新 draft 会替换旧 pending draft。每次 Kernel 调用先检查队列: + +1. staging 仍存在:继续等待; +2. staging 被拒绝且 main SHA 未变化:按原 base commit 提交; +3. staging 晋升且 main SHA 已变化:确定性 rebase 到最新 main,再提交; +4. Publisher 成功后删除 pending;竞态导致 staging/main 改变时刷新状态后等待或重试。 + +rebase 不调用 LLM,也不是把旧 bundle 整体覆盖到新 main。它保留最新 main 的 +provider-owned frontmatter 和附件,应用 pending draft 的 description、OpenEarth +optimizer 字段和正文,并把 `base_commit_sha` 更新为最新 main SHA。 diff --git a/examples/kernels/openearth/kernel.py b/examples/kernels/openearth/kernel.py index aad3ead1..2c76240e 100644 --- a/examples/kernels/openearth/kernel.py +++ b/examples/kernels/openearth/kernel.py @@ -7,10 +7,15 @@ from __future__ import annotations +import json +from pathlib import Path + try: from openearth_skill_sdk import ( ExistingSkillInput, + SkillDraft, __version__, + rebase_skill_draft, train_skills, ) except ImportError as exc: @@ -27,21 +32,188 @@ ) +_QUEUE_SCHEMA = 1 +_QUEUE_FILENAME = "openearth-publication-queue.json" + + +def _skill_snapshot(skill) -> ExistingSkillInput: + files = { + relative_path: skill.read_text(relative_path) + for relative_path in skill.list_files() + if relative_path != "SKILL.md" + } + return ExistingSkillInput( + name=skill.name, + skill_md=skill.read_text(), + files=files, + version_token=skill.main_commit_sha, + ) + + def _existing_skills(context) -> tuple[ExistingSkillInput, ...]: - snapshots = [] - for skill in context.skills.list(): - files = { - relative_path: skill.read_text(relative_path) - for relative_path in skill.list_files() - if relative_path != "SKILL.md" - } - snapshots.append(ExistingSkillInput( - name=skill.name, - skill_md=skill.read_text(), - files=files, - version_token=skill.main_commit_sha, - )) - return tuple(snapshots) + return tuple(_skill_snapshot(skill) for skill in context.skills.list()) + + +def _queue_path(context) -> Path: + return Path(context.workspace) / _QUEUE_FILENAME + + +def _load_queue(context) -> dict: + path = _queue_path(context) + if not path.is_file(): + return {"schema": _QUEUE_SCHEMA, "pending": {}} + value = json.loads(path.read_text(encoding="utf-8")) + if ( + not isinstance(value, dict) + or value.get("schema") != _QUEUE_SCHEMA + or not isinstance(value.get("pending"), dict) + ): + raise ValueError(f"invalid OpenEarth publication queue: {path}") + return value + + +def _save_queue(context, queue: dict) -> None: + path = _queue_path(context) + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_name(f".{path.name}.tmp") + temporary.write_text( + json.dumps(queue, ensure_ascii=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + temporary.replace(path) + + +def _draft_to_queue_entry(draft, *, run_id: str) -> dict: + return { + "name": draft.name, + "skill_md": draft.skill_md, + "files": dict(draft.files), + "source_trajectory_ids": list(draft.source_trajectory_ids), + "action": draft.action, + "base_version_token": draft.base_version_token, + "generated_run_id": run_id, + } + + +def _draft_from_queue_entry(name: str, entry: dict) -> SkillDraft: + if not isinstance(entry, dict) or entry.get("name") != name: + raise ValueError(f"invalid queued OpenEarth Skill draft: {name!r}") + files = entry.get("files", {}) + source_ids = entry.get("source_trajectory_ids", []) + if not isinstance(files, dict) or not isinstance(source_ids, list): + raise ValueError(f"invalid queued OpenEarth Skill bundle: {name!r}") + return SkillDraft( + name=name, + skill_md=str(entry.get("skill_md") or ""), + files=files, + source_trajectory_ids=tuple(str(item) for item in source_ids), + action=str(entry.get("action") or "update"), + base_version_token=entry.get("base_version_token"), + ) + + +def _current_skill(context, name: str): + try: + return context.skills.get(name) + except KeyError: + return None + + +def _prepare_for_current_main(draft, current): + if current is None: + return draft, False + if draft.base_version_token == current.main_commit_sha: + return draft, False + return rebase_skill_draft(draft, _skill_snapshot(current)), True + + +def _submit(context, draft, current, *, origin_run_id: str): + prepared, rebased = _prepare_for_current_main(draft, current) + published = context.publisher.submit(SkillSubmission( + name=prepared.name, + skill_md=prepared.skill_md, + files=prepared.files, + source_trajectory_ids=prepared.source_trajectory_ids, + message=f"OpenEarth run {origin_run_id}", + base_commit_sha=( + current.main_commit_sha if current is not None else None + ), + )) + return published, rebased + + +def _submit_or_observe_busy(context, draft, current, *, origin_run_id: str): + """Submit once, refreshing main if canary changed during the attempt.""" + for attempt in range(2): + if current is not None and current.staging_commit_sha: + return None, False, True + current_token = ( + current.main_commit_sha if current is not None else None + ) + try: + published, rebased = _submit( + context, + draft, + current, + origin_run_id=origin_run_id, + ) + return published, rebased, False + except RuntimeError: + refreshed = _current_skill(context, draft.name) + if refreshed is not None and refreshed.staging_commit_sha: + return None, False, True + refreshed_token = ( + refreshed.main_commit_sha if refreshed is not None else None + ) + if attempt == 0 and refreshed_token != current_token: + current = refreshed + continue + raise + raise AssertionError("unreachable publication retry state") + + +def _drain_publication_queue(context, queue: dict): + submitted = [] + metrics = { + "queue_pending_before": len(queue["pending"]), + "queue_waiting": 0, + "queue_missing_skill": 0, + "queue_drained": 0, + "queue_rebased": 0, + } + for name in sorted(tuple(queue["pending"])): + entry = queue["pending"][name] + draft = _draft_from_queue_entry(name, entry) + current = _current_skill(context, name) + if current is None and draft.base_version_token is not None: + metrics["queue_missing_skill"] += 1 + continue + _published, rebased, busy = _submit_or_observe_busy( + context, + draft, + current, + origin_run_id=str(entry.get("generated_run_id") or context.run_id), + ) + if busy: + metrics["queue_waiting"] += 1 + continue + if rebased: + metrics["queue_rebased"] += 1 + submitted.append(name) + metrics["queue_drained"] += 1 + del queue["pending"][name] + _save_queue(context, queue) + return submitted, metrics + + +def _enqueue(context, queue: dict, draft) -> bool: + superseded = draft.name in queue["pending"] + queue["pending"][draft.name] = _draft_to_queue_entry( + draft, + run_id=context.run_id, + ) + _save_queue(context, queue) + return superseded class OpenEarthKernel(BaseKernel): @@ -56,6 +228,8 @@ class OpenEarthKernel(BaseKernel): def run(self, context, run_interval: int = 30) -> KernelRunResult: del run_interval + queue = _load_queue(context) + submitted, queue_metrics = _drain_publication_queue(context, queue) changed = tuple(context.invocation.changed_trajectory_ids) full_rebuild = bool(context.invocation.full_rebuild) if changed: @@ -74,14 +248,21 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: ] else: return KernelRunResult( + submitted_skills=tuple(submitted), metrics={ "selected_trajectories": 0, "full_rebuild": False, "no_changes": True, + "generated_drafts": 0, + "published_drafts": len(submitted), + "queued_drafts": 0, + "queue_superseded": 0, + "queue_pending": len(queue["pending"]), + **queue_metrics, }, notes=( - "No changed trajectories and full_rebuild is false; " - "OpenEarth did not invoke the SDK." + "No trajectory training input; OpenEarth only reconciled " + "its publication queue and did not invoke the SDK." ), ) @@ -94,27 +275,24 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: full_rebuild=full_rebuild, ) - submitted = [] - skipped_active_staging = [] + queued = [] + superseded = [] + rebased_immediate = 0 for draft in result.drafts: - base_commit = None - try: - current = context.skills.get(draft.name) - except KeyError: - current = None - if current is not None: - if current.staging_commit_sha: - skipped_active_staging.append(draft.name) - continue - base_commit = current.main_commit_sha - context.publisher.submit(SkillSubmission( - name=draft.name, - skill_md=draft.skill_md, - files=draft.files, - source_trajectory_ids=draft.source_trajectory_ids, - message=f"OpenEarth run {context.run_id}", - base_commit_sha=base_commit, - )) + current = _current_skill(context, draft.name) + _published, rebased, busy = _submit_or_observe_busy( + context, + draft, + current, + origin_run_id=context.run_id, + ) + if busy: + if _enqueue(context, queue, draft): + superseded.append(draft.name) + queued.append(draft.name) + continue + if rebased: + rebased_immediate += 1 submitted.append(draft.name) metrics = { @@ -125,7 +303,11 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: "processed_atoms": len(result.processed_atom_ids), "generated_drafts": len(result.drafts), "published_drafts": len(submitted), - "skipped_active_staging": len(skipped_active_staging), + "queued_drafts": len(queued), + "queue_superseded": len(superseded), + "queue_pending": len(queue["pending"]), + "queue_rebased_immediate": rebased_immediate, + **queue_metrics, } if result.candidate_dir: metrics["candidate_dir"] = result.candidate_dir @@ -135,7 +317,7 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: metrics=metrics, notes=( "OpenEarth trained from ready trajectory Atom views; " - "XSkill owns publication." + "XSkill owns publication and active staging drafts are queued." ), ) diff --git a/examples/kernels/openearth/tests/test_delivery.py b/examples/kernels/openearth/tests/test_delivery.py index aa6bd46f..246ba5f1 100644 --- a/examples/kernels/openearth/tests/test_delivery.py +++ b/examples/kernels/openearth/tests/test_delivery.py @@ -10,7 +10,7 @@ OPENEARTH = Path(__file__).resolve().parents[1] WHEEL = ( OPENEARTH / "wheels" - / "openearth_skill_sdk-0.7.0-py3-none-any.whl" + / "openearth_skill_sdk-0.8.0-py3-none-any.whl" ) @@ -21,11 +21,12 @@ def test_openearth_wheel_checksum_and_public_contents(): with ZipFile(WHEEL) as archive: names = set(archive.namelist()) metadata = archive.read( - "openearth_skill_sdk-0.7.0.dist-info/METADATA" + "openearth_skill_sdk-0.8.0.dist-info/METADATA" ).decode("utf-8") - assert "Version: 0.7.0" in metadata + assert "Version: 0.8.0" in metadata assert "openearth_skill_sdk/xskill.py" in names + assert "openearth_skill_sdk/drafts.py" in names assert not any( forbidden in name for name in names @@ -47,3 +48,5 @@ def test_openearth_delivery_documents_atom_score_sources(): assert "changed_trajectory_ids" in usage assert "full_rebuild" in usage assert "atom_id" in usage + assert "openearth-publication-queue.json" in usage + assert "latest-wins" in usage diff --git a/examples/kernels/openearth/wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl b/examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl similarity index 80% rename from examples/kernels/openearth/wheels/openearth_skill_sdk-0.7.0-py3-none-any.whl rename to examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl index 6913a3e5183f1ef10bb6931321546494c9b58970..0de8364c1e31fdc2b2c6b4a6f8c1aacd39c3e57e 100644 GIT binary patch delta 4272 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 delta 3219 zcmZ`*c{o&k8=e`vtTlFFP}$93>`T@dYa(PzvM(WH8Owy!izrT$Ok~dzA{xmqWF5O= zP>DCoAiL%bpM2l@e)V0~cg`Q@x$pB__wQWKxvu-UQ}@9$n_xZ*V=8JW2n3=BC8+N^ zgo)f;vH*iXJX9c%AP~UAl`6-Jwa*>Yg)uyQL{O;12}2`*($q86X>pr>^}L)@MF*26 zhRCZ-uHg!aztR7*F9j(w+Kj6q4nVQQOIj_8J#Jv< zT1Q>Mw+7~K{DqsOnRLD{m>_A@h8a6xZLKtI~Cr4wp*l^Twp$35* z7yxEpeyoxVT;{sh&0uLSfA<@*rutUedfHanIBSwWzYkAw-3_~5P-g1eWvoLpDwY4iT- z!U49kJ=jQzq~+x7+=$1Pk8%O3sNuBd-kPm?upC%4Z{(ce%zBlM9<6wQFCJD@d>O3H zf0dKR9R_!o-+PegQT4Eaf|PmI6jB?(6-=brF2s#CUHz55Puv6{SqxLi=RMfi~LA-<@u-!3O#7 z6q+HHI2X@3iMFejEK}Sz?KZ>^oM-%v97;qua`>->!?;ciBwfDXl$n*)Y?1v5^gd>W_C6%iJyZYJC}`WGrn{P+#}a#_lJKQry!(vxDG`!vI6i7o>Xna+zz6q zLa%^~c9ZXj^PIu8f+J3>VaaSKv1F;tIMp6t3#G7^OODxpe6CBLWxSw-4J|cGt?;x; zu6UClx5-e@Qg<~{JO;E_Q@AUb7zPO*a~lj{0{g_#%2y(7&uUm?6$z$3N@O5aU0z!8 zhj`IDSqbz!O5FM*ib2e11kX8vqTn97y^%*V9v&e#RkBz=hY2$TO%%c z@){|`jhXFvyN{L`yiaTjE$iynxBP+XRi$RktEE;&UR8R zcvqtM4AF6fLFihS$8*xt`1#oCDFL1^F=tWK!PDt`dFdBvP%ox=X~>lHs&$o#TYMtz z&pfOv)*aI4t8d!Uj+75!eF&aG1sYQqj138Idu~7W#xU#LL~Q5jpzbOL zE&(q&*4ny+PS$Dc&_3*5cwl(;-Nk`fLl2Z{TOX?{p$D$RwC73Fd{^hHY{Lx~V#;-7 z>`R2UfhEu7F)@jlN<+$n_Se4peV24uCsQ0S@eR)=rTpTr4M}xNVrx$t+NIGA+Ky-! zW$a`PhcG|tkys;h3Q3k2;A=@IXMF9^yU`mY#yU~B zWRmknv;Xd_z!P=AAy5pg1jGl1LGbFp;V}>kQa!e<^ufF_)g#;bLI(r{ z{jY6V>g$?Y>Lug4)AE&}`n%iG%1Pn2TabHnm96&(F1qw=wzt3{Ey~SJj;DtvtQ0s> z)yj$C>eG^V&T+Qjed)F{rKR*m^8;c!$ff*+{3!N4TF;cj@OJtK#?P9brZEaK}w#pVj<1(1?vJ8+pOPeS1`l5*2~gBmAF z1tl)RyG5xb2Y(1FdMz={%a?XJLR(F*FiSAHLj2T*e&A@a9nS^N8Szeu4byCR`R?2? zwNN+RPnZl9T3lz6nn)F}3zm5!}m zHEoJ(x;xFZzm+KTFtVuLS=`#x)guaJ-toz%_S;t2w}N4lRo!S&1pWVXqVngzIVToR+&b8L;fMZ%g6EPCobYzy{len zzO}n2Tz$Amep#p1Rx6V}EoOhY$5>(WmG|9`NobHl<}jU#<&~#|Ayjc5vZE*4VebQ6 zr~;z%Ng3-O&b+p@1HZs_XmV$l9l8BZW8bE=IX@_osPBzjaIw%=p?=J~%;><=gRS^} z8xdbT-4I+2({oYjlGjxn?Utn~c2CJA=`0uL3Im}fC52Y6XLY@Ajy6O{3%o5cQ_&KR zXw08M78Gku{fSK@K+wrckweZ+Un<^rNCypYs?SPJKYcFh-Fr(ZZ-r#7Q9a!Ps)uASO;4QDfV z_nUTf0sXF|l}Eq1>zD3Qa<{YWhU819)`QREqY1d`b0xk8ku zXsG)QKLQRrPx{xFS@l)i)JNBZU-r;^#kMqsm7{y@I5h0frb@BIxOSiTs|KqSxE^xw zZtaO;=_mT<_oJ7Z$G7EbtJurGUoD>r@_*Gx>YPWjGY+gn-}|y2bj!9XAnCol+R1hPUUAiQkH@MEX~1jBh$ z@&GNIw17gG1VoSL2nqxJN979taT9B}GK5YDs1gwbJi{d*H(5>5|FKHztE?E$|H*4_6tQ1BzUn%{W_fAgCqzf_4j0s zo`*3Wo&dWq_mPv^M!_IQwjL414>{@glhgV=ZrT{?>zn+li$4med+G;$oJTr8e*@w- z-B-sNN4gBrXN7)#!he?n2qf`KYfTKV|Igzjen0NtYr?{qhW2MK=g}=i2LfdV{Y3u) DW>~+m From 816c950bed46792f53005f24709c6eb0ff0d2179 Mon Sep 17 00:00:00 2001 From: haironghu <15395595149@163.com> Date: Wed, 29 Jul 2026 17:09:47 +0800 Subject: [PATCH 4/5] feat(kernel): restore OpenEarth benchmark harness --- examples/kernels/openearth/README.md | 29 ++++++++----- examples/kernels/openearth/SHA256SUMS | 2 +- .../kernels/openearth/config.yaml.example | 21 +++++++++ examples/kernels/openearth/docs/sdk-usage.md | 33 +++++++++++++- examples/kernels/openearth/kernel.py | 41 +++++++++++++++++- .../kernels/openearth/tests/test_delivery.py | 11 +++-- ...openearth_skill_sdk-0.8.0-py3-none-any.whl | Bin 32401 -> 0 bytes ...openearth_skill_sdk-0.9.0-py3-none-any.whl | Bin 0 -> 51287 bytes 8 files changed, 119 insertions(+), 18 deletions(-) delete mode 100644 examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl create mode 100644 examples/kernels/openearth/wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl diff --git a/examples/kernels/openearth/README.md b/examples/kernels/openearth/README.md index 7a461766..22213cb2 100644 --- a/examples/kernels/openearth/README.md +++ b/examples/kernels/openearth/README.md @@ -19,6 +19,11 @@ ready TrajectoryResource context.publisher.submit(...) ``` +在启用内部 benchmark 时,full rebuild 会先由 OpenEarth target harness 运行配置的 case, +把每个 rollout 转成一个平台格式 Markdown,记录 oracle score,再调用 +`context.trajectories.create_temp(...)`。临时轨迹进入 XSkill 拆分队列;拆成唯一 Atom +并变为 ready 后,在后续 Kernel 调用中进入上面的统一训练流。 + 桥接层仅提交 `atom_split_status == "ready"` 的轨迹。输入范围严格遵循 `context.invocation`: @@ -44,26 +49,28 @@ SDK 将每条轨迹展开成 atom,并以 `#` 一次 Kernel 运行大致分为以下阶段: -1. **选择输入**:优先处理本轮变化且已经完成 atom 拆分的轨迹;手动全量运行时读取全部 +1. **可选 benchmark 生产**:启用 benchmark 的 full rebuild 先运行尚未登记的 case, + 计算 oracle score,并创建 pending 临时轨迹;这些轨迹等待 XSkill 拆分,不在当轮训练。 +2. **选择输入**:优先处理本轮变化且已经完成 atom 拆分的轨迹;手动全量运行时读取全部 ready 轨迹;没有变化且不是全量重建时本轮不做任何训练。 -2. **同步已有 Skill**:把 XSkill main Skill 的完整只读快照放入 OpenEarth workspace, +3. **同步已有 Skill**:把 XSkill main Skill 的完整只读快照放入 OpenEarth workspace, 并对没有 `level` 的新增或变化 Skill 做一次批量分类。 -3. **构造训练证据**:把每条轨迹展开成 atom,真实数据使用 `atom.ux_score`,临时评测 +4. **构造训练证据**:把每条轨迹展开成 atom,真实数据使用 `atom.ux_score`,临时评测 数据使用已记录的 OpenEarth oracle score。增量运行通过稳定证据 ID 和内容签名跳过 历史未变化 atom;全量重建会重新蒸馏历史 atom,但同一批内仍按 `atom_id` 去重, 同 ID 不同内容或评分会直接报冲突。 -4. **划分反思通道**:UX 7–10 的成功 atom 用于提炼可复用 Planning Skill;UX 1–5 +5. **划分反思通道**:UX 7–10 的成功 atom 用于提炼可复用 Planning Skill;UX 1–5 的失败 atom 先检索已有 Planning Skill,找不到时生成临时诊断计划,再反思产生 Functional 修复候选。 -5. **整理候选**:对本轮 Functional 候选做一次全局 curation,可保留、修改、合并或 +6. **整理候选**:对本轮 Functional 候选做一次全局 curation,可保留、修改、合并或 丢弃候选,同时校验其 atom 来源。 -6. **生成草稿**:候选名称与已有 Skill 完全相同时生成 update draft,并保留原 bundle; +7. **生成草稿**:候选名称与已有 Skill 完全相同时生成 update draft,并保留原 bundle; 否则生成 create draft。 -7. **交给 XSkill 发布**:SDK 只返回 `SkillDraft`。Kernel 再通过 +8. **交给 XSkill 发布**:SDK 只返回 `SkillDraft`。Kernel 再通过 `context.publisher.submit(...)` 发布;已有 active staging 的 Skill 会进入 OpenEarth 待发布队列。 -当前到第 6 步即结束 OpenEarth 训练,不执行 OpenEarth candidate rollout 或 Gate。 +内部 benchmark 只负责产生训练证据,不执行 candidate rollout 或 OpenEarth Gate。 XSkill 自己的 staging/canary 发布机制不受影响。 全量重建仍以当前 main Skill 作为只读反思上下文,并复用 @@ -94,7 +101,7 @@ SDK 源码位于本地 `sdk/`,由本目录的 `.gitignore` 排除,不会提 ```bash python -m pip install \ - examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl + examples/kernels/openearth/wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl ``` 然后复制 Kernel 目录并创建私有配置: @@ -129,5 +136,5 @@ python -m pip wheel \ . ``` -每次重建后同步更新 `SHA256SUMS`。wheel 中只应包含运行时代码,不应包含 Gate、 -dataset task/evaluator 或 SDK 测试。 +每次重建后同步更新 `SHA256SUMS`。wheel 包含 benchmark dataset/environment/target +harness 和运行时代码,但不包含 Gate、组合 experiment 或 SDK 测试。 diff --git a/examples/kernels/openearth/SHA256SUMS b/examples/kernels/openearth/SHA256SUMS index d5041028..b3132da7 100644 --- a/examples/kernels/openearth/SHA256SUMS +++ b/examples/kernels/openearth/SHA256SUMS @@ -1 +1 @@ -190c3544a54403c20cd22c12ae547e37c0ce59076525012ce170501da0d3db85 wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl +02f400128049f8a3c54231084f20559bd7a0e569ecc99dbe07d695599f0457b1 wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl diff --git a/examples/kernels/openearth/config.yaml.example b/examples/kernels/openearth/config.yaml.example index aadabfc7..dee77b15 100644 --- a/examples/kernels/openearth/config.yaml.example +++ b/examples/kernels/openearth/config.yaml.example @@ -7,3 +7,24 @@ reflect: timeout: 600 # Optional custom reflection strategy Markdown: # strategy: /absolute/path/to/strategy.md + +# Optional OpenEarth-internal benchmark producer. It runs only on a +# full_rebuild invocation. Empty/disabled configuration keeps live-data-only +# training behavior. +benchmark: + enabled: false + # dataset_dir: /absolute/path/to/officeqa + env: officeqa # officeqa | spreadsheet | livemath + split: train + model: YOUR_TARGET_AGENT_MODEL + binary: opencode + agent_timeout: 900 + parallel: 1 + # Stable cases already registered in the workspace are not rerun. + # n_cases: 10 + # Change this value to intentionally produce a fresh rollout for each case. + # sample_id: default + planning_route_k: 2 + functional_route_k: 2 + # OfficeQA optional document roots: + # officeqa_docs_dirs: /absolute/path/to/parsed/docs diff --git a/examples/kernels/openearth/docs/sdk-usage.md b/examples/kernels/openearth/docs/sdk-usage.md index 188db716..a21fcf18 100644 --- a/examples/kernels/openearth/docs/sdk-usage.md +++ b/examples/kernels/openearth/docs/sdk-usage.md @@ -141,8 +141,28 @@ OpenEarth 不调用 XSkill 内部 UX scorer,也不读取轨迹级分数,因 ## 评测数据和 oracle 分数 -评测器先执行 OpenEarth rollout 并计算 oracle score,再使用同一个稳定 -`trajectory_id` 依次写分和创建临时轨迹: +启用 `benchmark.enabled` 后,Kernel 在 full rebuild 中调用 OpenEarth +`run_benchmark(...)`。harness 从 `benchmark.dataset_dir` 加载 case,运行 target agent +并用环境 oracle 评分,再使用同一个稳定 `trajectory_id` 依次写分和创建临时轨迹。 +Gate 不会运行。 + +```yaml +benchmark: + enabled: true + dataset_dir: /absolute/path/to/officeqa + env: officeqa + split: train + model: deepseek/deepseek-v4-flash + binary: opencode + agent_timeout: 900 + parallel: 1 + n_cases: 10 + # 修改 sample_id 可为同一批 case 主动生成一组新 rollout + sample_id: default + officeqa_docs_dirs: /absolute/path/to/parsed/docs +``` + +Kernel 的登记动作等价于: ```python from openearth_skill_sdk import record_oracle_score @@ -175,6 +195,15 @@ assert temp.atom_split_status == "pending" 调用中以 `ready` 资源出现,此时 SDK 按 `trajectory.trajectory_id` 找到已保存的 oracle 分数并训练。 +benchmark 状态保存在 +`/openearth-benchmark-state.json`。轨迹 ID 由数据路径、环境、split、 +case 内容、target model 和 `sample_id` 生成;相同 case 登记成功后不会在后续 full +rebuild 中重复运行。要主动生成一组新 rollout,可以修改 `benchmark.sample_id`。 + +当前恢复的 harness 支持 `officeqa`、`spreadsheet` 和 `livemath`。其任务工作目录和 +OpenCode 隔离数据位于 `/openearth-benchmark-runs`,完成后清理临时 +目录;正式训练证据仍由 XSkill temp trajectory 保存。 + 一个 oracle 分数必须只对应一个 atom,因此评测 Markdown 应表达一个完整的 User/Assistant rollout。若 ready 临时轨迹被拆成多个 atom,SDK 会报错,避免把一个 case 级分数错误地复制到多段证据。 diff --git a/examples/kernels/openearth/kernel.py b/examples/kernels/openearth/kernel.py index 2c76240e..d2c6676f 100644 --- a/examples/kernels/openearth/kernel.py +++ b/examples/kernels/openearth/kernel.py @@ -16,6 +16,8 @@ SkillDraft, __version__, rebase_skill_draft, + record_oracle_score, + run_benchmark, train_skills, ) except ImportError as exc: @@ -232,6 +234,37 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: submitted, queue_metrics = _drain_publication_queue(context, queue) changed = tuple(context.invocation.changed_trajectory_ids) full_rebuild = bool(context.invocation.full_rebuild) + existing_snapshots = None + benchmark_metrics = { + "benchmark_enabled": False, + "benchmark_selected": 0, + "benchmark_created": 0, + "benchmark_skipped": 0, + } + if full_rebuild: + existing_snapshots = _existing_skills(context) + + def register_benchmark_trajectory(rollout): + record_oracle_score( + workspace=context.workspace, + trajectory_id=rollout.trajectory_id, + ux_score=rollout.ux_score, + case_id=rollout.case_id, + metadata=rollout.metadata, + ) + context.trajectories.create_temp( + markdown=rollout.markdown, + trajectory_id=rollout.trajectory_id, + ) + + benchmark_result = run_benchmark( + config_path=context.config_path, + workspace=context.workspace, + existing_skills=existing_snapshots, + run_id=context.run_id, + on_trajectory=register_benchmark_trajectory, + ) + benchmark_metrics = dict(benchmark_result.metrics) if changed: changed_ids = set(changed) selected = [ @@ -259,6 +292,7 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: "queue_superseded": 0, "queue_pending": len(queue["pending"]), **queue_metrics, + **benchmark_metrics, }, notes=( "No trajectory training input; OpenEarth only reconciled " @@ -270,7 +304,11 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: config_path=context.config_path, workspace=context.workspace, trajectories=selected, - existing_skills=_existing_skills(context), + existing_skills=( + existing_snapshots + if existing_snapshots is not None + else _existing_skills(context) + ), run_id=context.run_id, full_rebuild=full_rebuild, ) @@ -308,6 +346,7 @@ def run(self, context, run_interval: int = 30) -> KernelRunResult: "queue_pending": len(queue["pending"]), "queue_rebased_immediate": rebased_immediate, **queue_metrics, + **benchmark_metrics, } if result.candidate_dir: metrics["candidate_dir"] = result.candidate_dir diff --git a/examples/kernels/openearth/tests/test_delivery.py b/examples/kernels/openearth/tests/test_delivery.py index 246ba5f1..237398cb 100644 --- a/examples/kernels/openearth/tests/test_delivery.py +++ b/examples/kernels/openearth/tests/test_delivery.py @@ -10,7 +10,7 @@ OPENEARTH = Path(__file__).resolve().parents[1] WHEEL = ( OPENEARTH / "wheels" - / "openearth_skill_sdk-0.8.0-py3-none-any.whl" + / "openearth_skill_sdk-0.9.0-py3-none-any.whl" ) @@ -21,12 +21,15 @@ def test_openearth_wheel_checksum_and_public_contents(): with ZipFile(WHEEL) as archive: names = set(archive.namelist()) metadata = archive.read( - "openearth_skill_sdk-0.8.0.dist-info/METADATA" + "openearth_skill_sdk-0.9.0.dist-info/METADATA" ).decode("utf-8") - assert "Version: 0.8.0" in metadata + assert "Version: 0.9.0" in metadata assert "openearth_skill_sdk/xskill.py" in names assert "openearth_skill_sdk/drafts.py" in names + assert "openearth_skill_sdk/benchmark.py" in names + assert "openearth_skill_sdk/environments.py" in names + assert "openearth_skill_sdk/target.py" in names assert not any( forbidden in name for name in names @@ -50,3 +53,5 @@ def test_openearth_delivery_documents_atom_score_sources(): assert "atom_id" in usage assert "openearth-publication-queue.json" in usage assert "latest-wins" in usage + assert "run_benchmark" in usage + assert "openearth-benchmark-state.json" in usage diff --git a/examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl b/examples/kernels/openearth/wheels/openearth_skill_sdk-0.8.0-py3-none-any.whl deleted file mode 100644 index 0de8364c1e31fdc2b2c6b4a6f8c1aacd39c3e57e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32401 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 diff --git a/examples/kernels/openearth/wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl b/examples/kernels/openearth/wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..9a5f8ebabe4b14cb58fac688686e81eba5a2bddb GIT binary patch literal 51287 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 LbsswZ`u6_;bDbzO literal 0 HcmV?d00001 From eb2cd331336899033dc8cac8c660699ff279cb16 Mon Sep 17 00:00:00 2001 From: 370025263 <370025263@qq.com> Date: Thu, 30 Jul 2026 17:20:43 +0800 Subject: [PATCH 5/5] fix(openearth): allow multi-atom oracle-scored temp trajectories XSkill may split a temp trajectory into any number of atoms. Drop the hard ValueError that required exactly one atom; apply the case-level oracle score to each atom and log oracle_multi_atom instead. Co-authored-by: Cursor --- examples/kernels/openearth/SHA256SUMS | 2 +- .../kernels/openearth/tests/test_delivery.py | 93 ++++++++++++++++++ ...openearth_skill_sdk-0.9.0-py3-none-any.whl | Bin 51287 -> 51410 bytes 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/examples/kernels/openearth/SHA256SUMS b/examples/kernels/openearth/SHA256SUMS index b3132da7..423b209a 100644 --- a/examples/kernels/openearth/SHA256SUMS +++ b/examples/kernels/openearth/SHA256SUMS @@ -1 +1 @@ -02f400128049f8a3c54231084f20559bd7a0e569ecc99dbe07d695599f0457b1 wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl +daa898d4dbc1d64729d43d1368736dedd66fa049448d61e75c70643b85b1e223 wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl diff --git a/examples/kernels/openearth/tests/test_delivery.py b/examples/kernels/openearth/tests/test_delivery.py index 237398cb..d6a7a60b 100644 --- a/examples/kernels/openearth/tests/test_delivery.py +++ b/examples/kernels/openearth/tests/test_delivery.py @@ -55,3 +55,96 @@ def test_openearth_delivery_documents_atom_score_sources(): assert "latest-wins" in usage assert "run_benchmark" in usage assert "openearth-benchmark-state.json" in usage + + +def test_oracle_temp_multi_atom_does_not_raise(): + """Platform may split temp trajectories into N atoms; oracle is case-level.""" + import importlib + import sys + import types + from zipfile import ZipFile + + # Load xskill module from the vendored wheel without installing. + with ZipFile(WHEEL) as archive: + source = archive.read("openearth_skill_sdk/xskill.py").decode("utf-8") + assert "exactly one" not in source + assert "oracle_multi_atom" in source + + package = types.ModuleType("openearth_skill_sdk") + package.__path__ = [] # type: ignore[attr-defined] + sys.modules["openearth_skill_sdk"] = package + + # Minimal stubs for relative imports used at module import time. + for name in ( + "analyst", + "backend", + "config", + "contracts", + "service", + ): + stub = types.ModuleType(f"openearth_skill_sdk.{name}") + if name == "contracts": + class ScoredAtomInput: # noqa: D401 - test stub + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + stub.ExistingSkillInput = object + stub.ScoredAtomInput = ScoredAtomInput + stub.TrainingResult = object + if name == "config": + stub.load_config = lambda *a, **k: {} + stub.role_config = lambda *a, **k: {} + if name == "analyst": + stub.Analyst = object + if name == "backend": + stub.OpenCodeBackend = object + if name == "service": + stub.TrajectorySkillDistiller = object + sys.modules[f"openearth_skill_sdk.{name}"] = stub + + spec = importlib.util.spec_from_loader( + "openearth_skill_sdk.xskill", + loader=None, + ) + module = importlib.util.module_from_spec(spec) + sys.modules["openearth_skill_sdk.xskill"] = module + exec(compile(source, "xskill.py", "exec"), module.__dict__) + + atom_a = types.SimpleNamespace( + atom_id="a1", + content="first", + ux_score=None, + intent="", + summary="", + used_skills=(), + offset_start=0, + offset_end=1, + ) + atom_b = types.SimpleNamespace( + atom_id="a2", + content="second", + ux_score=None, + intent="", + summary="", + used_skills=(), + offset_start=1, + offset_end=2, + ) + traj = types.SimpleNamespace( + atom_split_status="ready", + atoms=(atom_a, atom_b), + source="temp", + trajectory_id="traj_temp_case", + id="wd1:traj_temp_case", + ) + oracle = { + "schema": 1, + "trajectories": { + "traj_temp_case": {"ux_score": 9, "case_id": "c1"}, + }, + } + scored = list(module._atom_inputs([traj], oracle_scores=oracle)) + assert len(scored) == 2 + assert {item.atom_id for item in scored} == {"a1", "a2"} + assert all(item.ux_score == 9 for item in scored) + assert all(item.score_source == "oracle" for item in scored) diff --git a/examples/kernels/openearth/wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl b/examples/kernels/openearth/wheels/openearth_skill_sdk-0.9.0-py3-none-any.whl index 9a5f8ebabe4b14cb58fac688686e81eba5a2bddb..1b1138330da5997147f2fc2ea355d0edb821b834 100644 GIT binary patch delta 4676 zcmZ8l2T)U6*A0Z;K^`r1=}n{yNKvVgqDT=b0@9>I=q21m_x<2&5BUgEMiTn=Q|KX86~cX$4v!*_EIX z@5xrh;u~ONMG_lU-8`m*2)D1t)w&f;c;KgwRpy8Ms zv%rLDLNdsebVxnoqbwl+kjeZ<8tUa{XKQaS3xN@v`-Yx2k$!VPQju+&5E!#Qt*`-o8_m(9SDk z(SqJ8a|=UvuhXu289lv~If70^h54$UutV>Az6}KB=CX+xZIMXB=Kky(dgu?o8r{Gr z)YE+sb!uE#vws;0f98AfqC|%RTH=OZx(#vA%IQqdJ}EQy zP{>3~_P%qQP0=+=O?mv`1JwBrPaBnz?jsoq@W_KDHXKH3;pS@^lY9jqKW2pjR7@_Jj(lgF(2u7zIULs*Ktlx zD`5Q*_Xx*lvmBSw&gFe#bKd~H-xI%+6Uji=xzCQFr+_yPIF$z9%0PFmi9-2nEIa+5 ze<>BqXT88Nk*P7?VklCZoHR|d{~9fp_=4+$z3^)ZGUuK#hZk@XY@&8^D^P+?Oq{%M z7|mu5(mn548P(iHp)ED#C4$f>>;f5Yo6+pKR8Td&(t5JQYqi7xjj;~6uIi3XXtLFk3;FX2 zMJWzjJTm(Y-^9XA&djD+u;LR?bUun#nNzYahDY3B@1E|^Koc@q&Dh?O)D;);VV$vY zZYJ8eM}O0=$IH~UaG)9@n@tephi7zGN|f4^`;k8306J0y774W))}tuBxAXPark3nh zn5s|80b6)!(S4Ff>=hQDxlIsnTLRk%%vM#Ozf}zpi0%RN+-R^mS3P>Q^Ggw6ix{Z@t@U8_2qf{QK>(~vfzAWP}ew{ye@?3GPK`w0%E!! z5Ctl4{tDV12`=v+CEGlUDOPy)l8~JvF@8;>Zy^H(m1LC(5z6@79_>GYQS0}ot8T-RXH2#EK9rPokLzEq+5 zf*kxlZ-d4Yy7V&a@#GHurDq2|IF2}JmL8msZpD+)7LQ3{p!I7JGGpiAh{utRf>F!1 zk_^ibrT&%qMJClLz^aLXlxe(mat)ya%KOwB68E5$K8?bVY_->+1bl1tujo!GY=xiH zFOdYI!l7}5qjIb{swe5@O=tk|fhbQKFfV|Bq}KrrUG*{?z|*MlUot=5PG%NfPPYV@ z#z%Yz1YyXYj_4}LO`&VFB^(J{j1*=k!rOvj%SWv#@7vNg2k;C;OITd&1MpijxM!=_ zNuNzad~MpaY88jn)L{lmWLpCa=+SS_UlC(XFj`Y+QqL@lIg&l3j7PsKVl-m|s$?A( z1My!#h4I$wa31ne?ui}ZD329BoKWl9i`0;_#UB*Hcu`wX2q2U;b$##fr!DZOv6rQB zzlkgikugqE5U7)lv{O&zhMdJW9?YaZoo{KxVw$Lw6^<^m{ei~J1uNi>L7K&5KWs_l zKFUj~X&7Mcd+l;(z4(ruGMo&L(13+50gE6P8mapA-Lxb4mNp34YNZQS)?50a`5?1g z2}V&T*56+@AYbF}ANW8nm%V0opgibPAs0=W;;rf+Q`wEUeTIIebbG{BY$_9+iE)@? zbaSHU^f*HRO9f44`1x4ch6Y2+_P7C`Emf-%Ur&6A^e9%r;?#kk-kPxT%=H%Bkio1O zy6Nm(7UeguiA#0&YlKDNKCyc~6`qPooC<%FN88w($Nd*_Yz)McS{#etm!UJ9E?M?v zYz=T_@{I(34}Mw?(Y7s*GTdZ4ODWPX7W^s{;9t=Kzxn(#Qfx}Cc^5G{5KW8AxC6_s zsAq=l^-(?x)psF=)5~w*gn}<}FMg6bL+xMebPqt7Z!<`@&wD}0*0j{T+C?#@zZ?(SLmfV>YzBW$QfK)5JHU z;bFd$xn<^eI&(ypeKr%;pkcVi1oiUHFUdI6ACE%yg&%Yx!mC}vEXVbPw zB423mKOqBsZ@+3$7S>;Ecq5a?&{loe#Z5I(6aih_o|0?}y5v&Y+zmM0hN{Rj+DWAF z9W$D|SxNn-FB0tT_s){OZ-rv8dL)>=ce306g3fLU74?0&pDm>RRTG=uceLE&o%0YJ=omx9xSxL-U%wE2x zL!>aB$v1Uu(r{dk9FoAz>R~`VQewljkC?U()so6NXgNU^7sz2^8S~)1TnEG%Sf$y_ zsqE~9!SSd7xqKRDkV^{-Z>hfn(D@RF(Q3m)dv9o7d-y`K{-WnqzxBhk1jFD z+Q&Mfz7CtOb;AmIYQryNQf^lA^71_7A; z1QZC~8CT9+<%-*tK31P0wcjZJ_TPLEb4W_;9< zMRkurJTul+OO*5__;g}fZNg1BRY)K29|DQ3lBA%BEDv*u%rZ~U1}%LrW@=RpK@rCx z6W5s@a!=iLPdc^TjU%BEX51(9{(%lVLaRx^5Oyj0jL$Y&;6@0&8t0pr-Qqi>7JFNQ z;3{AI0_R{O+-^k{$by^WV_#XK`|=9rI`(zN=5%8vyY}k+qQCu$Inqx=vWuo4uDlfJ zuk?hnDJ$+*Oh|^*ijI?!ecvJd`m6NO4n^m2W#nAw(@$!84J8q1FAw1)j)3tZ7~r1^ zw_Ei2Xi;#csQQ#~s@cs?2oK)-@!+6m!CVyDOavb&cyEpat|%yD#Tm8!8X9*4PLzK8{2In&0ZG;{djLJ%t~yu0&$UWD5> zXq?etH~gKAAq#;q`^Jg6rE9ZX3yrw@xq~yi^-(3~)VH8CctI`Ygd2##q}F@E$L$zf z>-a|rm|sHcZbtqXXb?+GDEp*sjCN&&9gj`%e;hs9bCBQWs4ryr zB(hPl27DXPA*~vy-{;_`lAdq*v~l0n$tzNzf@Lpv>_b!hRH~FFnr69J{!#x(ZmnI2 zQ_GrW3&Vi95D~0Y=S*^6vRO_zlxO)kMtHi;rcv@=3;=R#L6R=(_hW3lm6$&+;gqu`mFqfCq)E zO6)u}d0n9mX0i|DO2!#nxt+rALbp!5s@U2M!__X#E(4@<>v)<|T^2vRV0uA|`Rj)f{TqT-9bB2{JkrPBBG8jBg`S1za+%bD36 zJF48EYPP_zyIH9bKO_hmGoi)^I;BdLK_~X(5P_TkOawtQ2|kj}g@3MDta^KV%xLZj zZ;m0~l7=+7CT9opQh>|SIwWBESYt!~CC;e6{IUG>k;t#S?HxK#ZcE;l$?=o`=Y&Zt9pn-Q;aQXi%N>`c)&;Y z=g29%SV~|X(;3p4+J*=irFntuWOz#fOwqbPnsGsaKtWt(k~fk7xUF%4v=vGv1(K^? zAWx-(fxrj2sX#dgV&D(Z8FEIM5lD$ktoi?ln+oFr015bT2QFqLv$`@KPy?s5(I}w+ z=HYfEd)Q6+*Vf|>&BQ>E!UZzL4Mhpm#AzTwT$DgCE`dg0k^)_E;_&zxQc9f0TC6#2 zriBCmh-AHP3uo&fh0Za}yql>znzKV!hz^e%IiDH|Wqejn!{DR6(%In*gatsA+I1%9 z$J*S*sr$^A$M;^zBqVNmL@17lCU^Ta!*h}@&rV?z(ndMwt>Dj~Gjnf~MouTwXR)MJ zo`gonZ?sIorWRi*IB(03TO(BG;G2O;E=j{3W&lkikB~7ks2P3KZi5-0F<7tULC1`y z2*b1OI1d)!0%D_S^$GDgjcs4_bl|sozs+5hSYI0?_esdqQYS5ea+(v0>&j!%!C|n9 zHW_Oy*DkX<=2h0)2Ye#%^|kyrPeH5<(gOzeME=b5n@cuzNKRI-mPU#K#cq1$=0zGX z5QLczSPM`?AEqC@7&&q;o+y_owFgauA?HF5<^g-WJZH?~#yVCK``t1aqsy(2xy4Ot zlmUJ1k1F{TLxT^nA4--_w_h2UcNpwK>+i^A3m0__YKU4VWvLl{^jml-QRXHP%0gDhwOL#G5Pu0zKXZ!c$Ef`Z z)nU?`bF!~xQLY-QXD)3Ywu1Mj(p0M1DH|iRRw(K*RlQ_s?)XF^ylGW*B=xq5%|D(o zvWbXZ-ajXZl=UKaa1nC6ua?s?sF!KE&)(<@1dlibaUviaVohV?ZH1;|s6(s<)=yAR}?1O{VP1)f`j=mfd{L6e~)nXD&9VUg?6Ua2_#>2g+@aVSm1t zI#T7z{23aP?Oyx}rZu`b#rp6yT$o#wT}(9vDrZc@J~Vg3+j@qj(D%DRivL6Mg9bQ+-R)OJ`O<&UwXDc zuOQrbhLtvqB;_oz`}LDB8>kZ~=7x|;6xT2fO4}_BO|+ZH=g&FoXUf4-qyt=5dk!cb z4KoSvKS5+w=F1U7Uy4mXiGlg8Qam_N|)9LtbQZ& zxjFSZ zG`aS$Mw;~aCqMr43^Q7m%bBsI?NiZI#$qdJWBEC3;=B3`XcA0IoM7xFI{+1UQha7a z81WNoNlPkR6J#I4`S`)5);g3~9^!5tkLicdq8pgEvPvwHM(Y+g0!X);7AK**PPDxI zL7_|qf%GDR5+4G=3$x1pl4*QiHpm1|QLt{r3u2a+p^{=8)weK+Gvj0dj39lSAwl@? z_ZA--XL}Mlx||oPiV1s?9saNJ7*<^j!--YHTFH7m+gE+8*D3D7Pun|owh?S%E2&NZ zyLY^m;VhTfj4@MX#p(9c_cG9DB$d|6bCc065$;{RWvicCQnrSOAd&$}Ig{sDe`$m1 z@;ekJR!6K-i*S#4|HKV#A?;VsT*)78t_oGb03NZDBxCejD^gxqHJW$rnfbRIIDk4C z!K1K9{+qxfiwF)xOb~N0KIfBR4h)y$^LCz3)se1@P^lY|Bn%^Wmd^J`H*or}7ci)z`ju-ENl1`CIK1=g@njJ+d&oZ3;xd+dw0ze&pXwRBK)v8;lG z$qIIh!tjWlAtV}Th~n-3s2}6W&olMJH)b@1;~i!5omVRJE84vdO`PP;4bg9FBs8Ag zQmWga6{l>KYOQ_hxMW5LNQ-AWIkY zX=?9Xn~${m#cykC##-_(9z}hCuQR2O8{$bdj&91;`Vv%fF-{LN{&KVQSyj{_JvJDU zdv_tzpTNYskw-Sdvq9O`IT)#XeD@Y_weCiPAH`xy^cr!sKO3ie7j(&6!G!3rFfXGM zMy^7bLO2nO*~yHr-Ybn{Zqv;j=~$sSatvGQ9JET0nV0Z#1YcA~4N$+!7QQHwfBBZR zP<(AQ`nf}?JYO~&Ogb0?ES2V_I}4?+nm?%RxVn9EWwHE%&PF}lS(RDO=;54)TkrN& zHG|kz_M!&C^l+r8IkZdZmQa6!e&uScO<2LG!>46`o+9a*#x`$iy3tcGd8f)<`}&Ab)4N%iijHhCk;Q(J)CrPRfp05N`y4!SHE)HIND;Qnm#9b4eiFG0`7tN!J{LPK z@z~~juMSw4bfAd4l;vH&4_4#A5l=%iotTV-SMh{uvSKQ*m4TOzdb5S$q30m`c(Zh` zm@#Q@>)3a*WinqzP#O^#DmpD0AG|kfO*kJnx>Q_+E_G(COQ4AYA3513HlQNq@=Z_H z!KvTw&||p*GkI~*SmLr`xt#Z?id0eiu#n*eJuj9s zg{|+lTu361)m%Wvl-RNCW!P|-P*2;gy0*Rb+I`lsvRb^l5PV8xfM`+BufS2M<6nGA zCu6Q8ezpUA#|CQa@@ns;ZUS1I#A;@@YicFGFJWKkwKt z4lHCy4F<%%755A*j8dkcSNQs0QP6eQpRgpi`SiCj7XI@7{~H=T!x?fcmmeQ^o!}K9 z{!UVhcJP4`kTYa#Ng>`}iBxIx-xX|m89p!vcWEoEA}6{|;I(yGz-(N!sDDI>8(VW} z@sewZgpHg@!(rXQo#az)m$<9Q|4!)B23u z8YX|QGWzwW{=8HK{zo(~pD*gN#C8#%pvQdXg}sGQaJLV=hJMhUK$j*H=zK`?cyf{D zK#SSNftuM^kz+^6eZw#8H{xbV+Noj^smi4HrdIstSqk~)Ti2IVN}gxTP2Vb%kBcMz z4oD5~(UB0ZTy^teA~VV`2Pi4Fl$RNVa}7SycXON;7<*#hY5z%gm*eh9tXq(~GcrH& zHj|2xP?t#J3Uj^&7WR@(54z2ap7kq1uvlLljeN+oVik9cERAb0x(6la%*V7QXYoI+ zpHH23pbp$7o+K^v&EEK<3`05fWSmqV`K6EO1_NUao6R$X!5` zkN~os8L*Nh^A1Q{H$tuc?Qea{wIK^m2$m`?xqq6C}<5y;ZyRM)j!PU{c#_d01%y4d`1WIYEwcAW! zx@+W3KA#HYneRiX3L)xqKzgHlRp5-`3=#DYcj(=-kkEtKR;a*X+nz#R!4zJxUJX#r zD%Dohpd1}A7Rr(#H2KlHS09yM@U*a;>vB@vEWXU^M|KktyKL0Rq%*hOV}f3>-nXtC zY^(aA_9e(BZ(-iUdv1m!!3~8Bq#`Eyg}!6%JFwcw!1PzYM=)VYy~1~MJ;eyL)7!jv zAkDTC++PbfUO<MbiQ)kp5Cu7HbatE`5+8h=zV(SrtxHTP$I1?9t$-ViFCZqT{> z;h}gYuj#tzm8#|>xDiG$hW_%o?)mpNtx{6hiQ3^C>9A^wKjU%F7MQ{jWERZ8p{1`B zztgHg$T)vR{hSv&-_9Hy7i3tHX#9*t%i9l|SBOEgUeJEwSlNzcWR4P#fp69uA-7hL zUG$Bb`~+zEdH1yKdY|!wByuYf)17`b*i82-0<-2 znL`8XN0d_Vu#k!*MoHT~C0T5zwd&?#hS|xhQA=G9=!$^wD*OCq)k_&CjW z`!g)3>pJvBu|e;JaU+;_YMijPPb9_+XlUr$y`XXw-G6ebzGShML3VE_alZeGE^O$j z;BTGtJI#qX+h&9hHk+x^1d9^|FYMIQWqCP1+QMZXxmuu(qHiS&bd!tu0dmb*^J&hbC&&ll8$$_0WLgYHI1uuc(mWnV1Kpv$8)$( z-#Vk;`@GDnm6o#Ne4}8;rO|erM?UJtnxK1TWr?2C?Xq$~XdtV(qE%miVT`vFq``j_jmt=-Ic=+_dzfl~BV{tgF`N1_1!~2`HuTdP%+@gI=;&A5{oof(> zSGQ6k8e&DFy8wdah z;C>?f^SOfQ=E8?s7~th4WN`lu#%sfZzw2PZ56b^n8Pmb^SC6*en66@c0APsVzk5&@ z{Y(74h>Ysr4+v*>aOF-Ge7ur>NZ9^oItw>l^5q}Xba0Q-|LKs}#e(ly{x5|{1*Oox z?{RI9pBwbbI&k)wPYwVG{qyZ`@e0OP1sCt;fzMWvz{|SG|MFH8uAgw-bvdz{h8^d! u)_)W(F$_5Q^;Z@DkL82cI;GqHX~F+_0M#&QxL7wYytbQx=(y>6_5T53ux!l$