runops 2.3.1__tar.gz → 2.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runops-2.3.1 → runops-2.5.0}/PKG-INFO +2 -1
- {runops-2.3.1 → runops-2.5.0}/README.md +1 -0
- {runops-2.3.1 → runops-2.5.0}/SPEC.md +37 -7
- {runops-2.3.1 → runops-2.5.0}/docs/adapter.md +6 -1
- {runops-2.3.1 → runops-2.5.0}/docs/guide.md +14 -5
- {runops-2.3.1 → runops-2.5.0}/docs/reference.md +11 -9
- {runops-2.3.1 → runops-2.5.0}/pyproject.toml +1 -1
- {runops-2.3.1 → runops-2.5.0}/src/runops/__init__.py +1 -1
- {runops-2.3.1 → runops-2.5.0}/src/runops/cli.py +49 -6
- {runops-2.3.1 → runops-2.5.0}/src/runops/harness.py +10 -6
- {runops-2.3.1 → runops-2.5.0}/src/runops/manifest.py +26 -2
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/create.py +127 -11
- runops-2.5.0/src/runops/ops/progress.py +197 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/setup.py +4 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/skills.py +57 -4
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/sync.py +8 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/tidy.py +19 -6
- {runops-2.3.1 → runops-2.5.0}/src/runops/skills/runops-check-status/SKILL.md +5 -2
- {runops-2.3.1 → runops-2.5.0}/src/runops/skills/runops-create-run/SKILL.md +7 -4
- {runops-2.3.1 → runops-2.5.0}/src/runops/skills/runops-maintain/SKILL.md +2 -2
- {runops-2.3.1 → runops-2.5.0}/src/runops/skills/runops-reference/SKILL.md +6 -6
- {runops-2.3.1 → runops-2.5.0}/src/runops/skills/runops-tidy/SKILL.md +5 -4
- {runops-2.3.1 → runops-2.5.0}/tests/test_adapters.py +28 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_archive_series.py +37 -1
- runops-2.5.0/tests/test_progress.py +120 -0
- runops-2.5.0/tests/test_run_job.py +82 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_skills.py +38 -0
- {runops-2.3.1 → runops-2.5.0}/uv.lock +1 -1
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/check/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/improve-harness/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/agents/openai.yaml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/examples/refactor-request.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/references/anti-patterns.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/references/api-compatibility.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/references/packaging-modernization.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/references/refactor-playbook.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/references/verification.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/scripts/api_surface_snapshot.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/scripts/import_smoke.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/scripts/inspect_python_package.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/python-package-refactor/scripts/refactor_quality_gate.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/release/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.agents/skills/sync-docs/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/agents/spec-reviewer.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/rules/architecture.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/rules/dev-workflow.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/rules/gotchas.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/settings.json +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/check/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/improve-harness/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/release/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.claude/skills/sync-docs/SKILL.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.codex/README.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/.codex/agents/spec-reviewer.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.codex/config.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.codex/rules/runops.rules +0 -0
- {runops-2.3.1 → runops-2.5.0}/.dockerignore +0 -0
- {runops-2.3.1 → runops-2.5.0}/.gitattributes +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/assets/conversation-dark.svg +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/assets/conversation-light.svg +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/assets/hero-dark.svg +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/assets/hero-light.svg +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/assets/make_readme_art.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/workflows/ci.yml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.github/workflows/publish.yml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.gitignore +0 -0
- {runops-2.3.1 → runops-2.5.0}/.harness/manifest.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.pre-commit-config.yaml +0 -0
- {runops-2.3.1 → runops-2.5.0}/.vscode/settings.json +0 -0
- {runops-2.3.1 → runops-2.5.0}/AGENTS.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/CLAUDE.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/LICENSE +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/adapters/__init__.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/adapters/base.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/adapters/generic.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/adapters/registry.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/case.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/config.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/errors.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/jobgen.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/launchers.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/legacy_adapters/__init__.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/legacy_adapters/beach.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/legacy_adapters/emses.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/__init__.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/common.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/listing.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/migrate.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/notes.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/ops/submit.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/project.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/runtime.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/slurm.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/src/runops/util.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/__init__.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/conftest.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/.codex/rules/a.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/.runops/test-runs/T20260701-0001/receipt.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/.runops/work/beach-x-abc/bin/beach +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/AGENTS.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/CLAUDE.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/cases/beach/plane/beach.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/cases/beach/plane/case.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/cases/old/beach.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/cases/old/case.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/launchers.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/research/CURRENT.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runops.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/AGENTS.md +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/_archive/plane/R20260601-0001/manifest.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/input/beach.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/manifest.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/plane/R20260702-0001/manifest.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/scan/R20260701-0002/manifest.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/runs/scan/survey.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/simulators.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/fixtures/v1-project/site.toml +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/golden/mpi_standard.sh +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/golden/openmp_rsc.sh +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_build.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_dev_harness.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_jobgen.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_legacy_adapters.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_lifecycle.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_manifest_config.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_migrate.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_mv_naming.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_project_scope.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_safety.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_scratch_tidy.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_setup_misc.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_sweep.py +0 -0
- {runops-2.3.1 → runops-2.5.0}/tests/test_sync_evidence.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: runops
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Run ledger and Slurm job submission for HPC simulation projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/Nkzono99/runops
|
|
6
6
|
Project-URL: Documentation, https://github.com/Nkzono99/runops/blob/main/docs/reference.md
|
|
@@ -73,6 +73,7 @@ runo init
|
|
|
73
73
|
| 試してから本計算 | 100 step だけ smoke で試して、通ったら wc を 3 通りに振った候補を見せて |
|
|
74
74
|
| 様子を見る | 先週投げた run の状況を見て。失敗していたら原因をノートに残して |
|
|
75
75
|
| 続きを流す | R20261015-0003 の続きを 2 倍の step で |
|
|
76
|
+
| 資源を変えて作り直す | R20261015-0003 を 256 並列・72 時間で作り直して(case はそのまま) |
|
|
76
77
|
| 片付ける | 古い run を整理して。消すものと archive するものを先に見せて |
|
|
77
78
|
| 作り直した旧系列を片付ける | 旧系列は新系列で置き換えたので、対応を記録して archive して |
|
|
78
79
|
| simulator を足す | パッチを当てた ~/BEACH-dev をビルドして、別の simulator として登録して |
|
|
@@ -42,6 +42,7 @@ runo init
|
|
|
42
42
|
| 試してから本計算 | 100 step だけ smoke で試して、通ったら wc を 3 通りに振った候補を見せて |
|
|
43
43
|
| 様子を見る | 先週投げた run の状況を見て。失敗していたら原因をノートに残して |
|
|
44
44
|
| 続きを流す | R20261015-0003 の続きを 2 倍の step で |
|
|
45
|
+
| 資源を変えて作り直す | R20261015-0003 を 256 並列・72 時間で作り直して(case はそのまま) |
|
|
45
46
|
| 片付ける | 古い run を整理して。消すものと archive するものを先に見せて |
|
|
46
47
|
| 作り直した旧系列を片付ける | 旧系列は新系列で置き換えたので、対応を記録して archive して |
|
|
47
48
|
| simulator を足す | パッチを当てた ~/BEACH-dev をビルドして、別の simulator として登録して |
|
|
@@ -283,6 +283,12 @@ ntasks = 800
|
|
|
283
283
|
adapter の `render_inputs()` が `[params]` を適用して上書きする。
|
|
284
284
|
- `runo new` / `runo sweep` の `--set k=v` は `[params]` の上書きである。
|
|
285
285
|
既存 key でなくても受け付ける(adapter の `validate_params()` が warning を出せる)。
|
|
286
|
+
warning には判定した adapter と package version を付け、executable が package 由来でない
|
|
287
|
+
(`local_source` / `local_executable`)ときは「schema が古いだけかもしれない」と添える。
|
|
288
|
+
- run の `[job]` は、case から作る run では case の `[job]`、`new --from RUN`(`--continue` 含む)
|
|
289
|
+
では **元 run の `[job]`** を引き継ぐ(元 run に無い key は case から)。`--job k=v` で個別に上書きし(`[job]` の key のみ)、
|
|
290
|
+
`--job-from-case` で case の値に戻す。資源の変更に case の編集を要しない。作成時に、元 run
|
|
291
|
+
(または case)に対する params と資源の差分を表示する。
|
|
286
292
|
- **variation は case ではなく run で表現する。** 「同じ物理設定で 1 つの値だけ変える」
|
|
287
293
|
ために新しい case を作らない。`new CASE --set`、`new --from RUN --set`、`sweep` を使う。
|
|
288
294
|
`case new --from CASE` は、base 入力ファイルの構造そのものが変わるときだけ使う。
|
|
@@ -388,6 +394,7 @@ cancel_reason = "" # cancel --why の理由。retry はこれが
|
|
|
388
394
|
|
|
389
395
|
[variation]
|
|
390
396
|
changed_keys = ["plasma.wc", "plasma.phiz"] # case の params との差分 key
|
|
397
|
+
job_changed_keys = ["walltime"] # case の [job] との差分 key(--job / 元 run から)
|
|
391
398
|
|
|
392
399
|
[identity]
|
|
393
400
|
input_hash = "sha256:..." # input/ 配下全ファイルの content hash
|
|
@@ -419,7 +426,7 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
419
426
|
│ └───cancel──────────────┴──────▶ cancelled
|
|
420
427
|
│
|
|
421
428
|
└────────────── archive ─────────────────────────▶ archived
|
|
422
|
-
(completed / failed / cancelled から)
|
|
429
|
+
(created / completed / failed / cancelled から)
|
|
423
430
|
```
|
|
424
431
|
|
|
425
432
|
- `retry` は `failed` / `cancelled` を `created` に戻し `job.attempt` を +1 する。
|
|
@@ -453,6 +460,22 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
453
460
|
|
|
454
461
|
---
|
|
455
462
|
|
|
463
|
+
### 6.4 進捗
|
|
464
|
+
|
|
465
|
+
adapter が `progress(run_dir)` を持つとき、`sync` は running の run と、queue を離れた
|
|
466
|
+
直後の run について呼び、`{done, total, unit, start}` を受け取る。`done` は今の到達点、
|
|
467
|
+
`total` は目標(不明なら省略)、`unit` は数える単位(`batch`、`step` など)、`start` は
|
|
468
|
+
この attempt を始めた地点(resume した run の再開点。新規なら 0)。
|
|
469
|
+
|
|
470
|
+
- 観測は `submit/progress.<job_id>.jsonl` に `{at, done}` として追記する(attempt ごと、
|
|
471
|
+
上限 1000 件程度)。queue を離れたときの観測は終了時刻で記録し、終了後の待ち時間を速度に含めない。
|
|
472
|
+
- 要約を `[[job.attempts]].progress` に置く: `done`、`total`、`unit`、`start`、`fraction`、
|
|
473
|
+
`rate_recent_per_hour`(直近 10 分)、`rate_attempt_per_hour`(program 開始から。起点は
|
|
474
|
+
`start`)、`eta_hours`(running のみ)。program 開始時刻は job.sh が stdout に出す
|
|
475
|
+
`runops: start` 行から取り、`started_at` に入れる。
|
|
476
|
+
- `status` / `list` は要約だけを読む(file を開かない)。read-only command は観測を足さない。
|
|
477
|
+
- 解析は adapter が行い、runops は保存と表示だけを持つ(§1.2 #4)。
|
|
478
|
+
|
|
456
479
|
## 7. CLI
|
|
457
480
|
|
|
458
481
|
### 7.1 共通規則
|
|
@@ -494,9 +517,9 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
494
517
|
| `runo context [--json]` | agent 向け現在地。project 名、simulator、active run 数、直近 run 5 件、scratch 期限切れ数、`LOG.md` 末尾 20 行。配置した runops skill が install 済みの runops より古ければ `runo update --skills-only` を促す warning |
|
|
495
518
|
| `runo case new NAME --simulator SIM [--from CASE]` | case をスキャフォールド。adapter の `case_template()` を展開 |
|
|
496
519
|
| `runo case list` | case 一覧 |
|
|
497
|
-
| `runo new CASE [--why TEXT] [--label L] [--set k=v]... [--tag T]... [--scratch] [--dest DIR] [--submit] [--created-by WHO] [--json]` | 単発 run を作る |
|
|
498
|
-
| `runo new --from RUN [--why] [--set k=v]... [--continue] [--submit]` | 既存 run の params から派生 run
|
|
499
|
-
| `runo sweep CASE --set k=v1,v2,... [--set ...] [--names KEY=n1,n2,...]... [--label L] [--dest DIR] [--why TEXT] [--apply] [--submit] [--limit N] [--json]` | 直積の候補を表示する。`--apply` で生成。`--submit` は `--apply` と同時のみ有効 |
|
|
520
|
+
| `runo new CASE [--why TEXT] [--label L] [--set k=v]... [--job k=v]... [--tag T]... [--scratch] [--dest DIR] [--submit] [--created-by WHO] [--json]` | 単発 run を作る |
|
|
521
|
+
| `runo new --from RUN [--why] [--set k=v]... [--job k=v]... [--job-from-case] [--continue] [--submit]` | 既存 run の params と `[job]` から派生 run を作る(§5)。置き場所の既定は元 run と同じ directory(元 run が archive / scratch なら `runs/<case>/`)。`--continue` は adapter が `extend_inputs()` を実装する場合のみ有効(§8.1) |
|
|
522
|
+
| `runo sweep CASE --set k=v1,v2,... [--set ...] [--names KEY=n1,n2,...]... [--job k=v]... [--label L] [--dest DIR] [--why TEXT] [--apply] [--submit] [--limit N] [--json]` | 直積の候補を表示する。`--apply` で生成。`--submit` は `--apply` と同時のみ有効 |
|
|
500
523
|
| `runo submit RUN... [--partition P] [--qos Q] [--afterok JOBID] [-y]` | job.sh を生成して sbatch。`created` 以外は拒否 |
|
|
501
524
|
| `runo submit --all [DIR] [-y]` | DIR 配下の `created` run をすべて投入。件数と概算 core-hours を表示して確認 |
|
|
502
525
|
| `runo cancel RUN... [--why TEXT] [-y]` | scancel して sync。`--why` は `cancelled: ...` の note として残す |
|
|
@@ -509,7 +532,7 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
509
532
|
| `runo pin RUN [--off] [--reason TEXT]` | `run.pinned` を切り替える。pinned は `list` に常に出て、`tidy` / `delete` の対象外 |
|
|
510
533
|
| `runo summarize RUN... [--force]` | adapter の `summarize()` を呼び `analysis/summary.json` を書く。既存があれば `--force` なしではスキップ |
|
|
511
534
|
| `runo mv RUN... [--dest DIR] [--label L] [--why TEXT]` | run を `runs/` 配下で改名・移動する。id、入力、履歴は変えない(§7.7) |
|
|
512
|
-
| `runo archive RUN\|DIR... [--superseded-by RUN\|DIR --why TEXT] [-y]` / `runo archive --older-than 90d [-y]` | `runs/_archive/<元の相対 path>` に移動して `archived`
|
|
535
|
+
| `runo archive RUN\|DIR... [--superseded-by RUN\|DIR --why TEXT] [-y]` / `runo archive --older-than 90d [-y]` | `runs/_archive/<元の相対 path>` に移動して `archived` にする。対象は created と終端の run(created は job を持たないため)。移動前に全対象を検査し、指定した RUN が対象外なら何も動かさない。DIR は配下の run(系列)を指し、submitted / running と pinned を除いて表示する。created は `--superseded-by` があるときだけ含める。`--superseded-by` は各 run に置き換え先(`parent_run` がその run の置き換え先 run、無ければ指定した全 run)を `run.superseded_by` に記録し、`--why` を note に残す |
|
|
513
536
|
| `runo restore RUN` | archive 前の場所に戻す。既に何かあれば拒否 |
|
|
514
537
|
| `runo delete RUN... [-y]` | directory を削除。`created`/`failed`/`cancelled`/scratch のみ。`pinned` は拒否 |
|
|
515
538
|
| `runo tidy [--apply] [--only KIND] [--older-than AGE] [--purge-work RUN...] [--json]` | 整理候補を表示する(§7.5)。`--apply` で実行 |
|
|
@@ -534,7 +557,7 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
534
557
|
|
|
535
558
|
| kind | 条件 | 動作 |
|
|
536
559
|
|---|---|---|
|
|
537
|
-
| `expired-scratch` | `scratch = true` かつ `expires_at` を過ぎ、`pinned = false
|
|
560
|
+
| `expired-scratch` | `scratch = true` かつ `expires_at` を過ぎ、`pinned = false`、archived でない | delete |
|
|
538
561
|
| `stale-failed` | `failed`/`cancelled` で `defaults.tidy_failed_days`(既定 30)日より古く、note も `why` も空 | delete |
|
|
539
562
|
| `old-completed` | `completed` で `defaults.tidy_archive_days`(既定 90)日より古く、`pinned = false` | archive |
|
|
540
563
|
| `orphan-job` | `submitted`/`running` だが squeue/sacct に job が無い | sync(state 確定) |
|
|
@@ -614,6 +637,7 @@ class SimulatorAdapter(ABC):
|
|
|
614
637
|
def agent_context_dir(self) -> Path | None: return None # §8.2
|
|
615
638
|
def configure(self, sim_config: dict) -> None: ... # runops.toml の [simulators.X] を受け取る
|
|
616
639
|
def job_prelude(self, run_dir: Path) -> list[str]: return [] # job.sh で実行直前に置く shell 行
|
|
640
|
+
def progress(self, run_dir: Path) -> dict | None: return None # §6.4。{done, total, unit, start}
|
|
617
641
|
```
|
|
618
642
|
|
|
619
643
|
- job.sh は run_dir へ `cd` してから実行する(v1 と同じ。v1 adapter の path 規約
|
|
@@ -668,12 +692,16 @@ runops は `init` と `update` で次を行う。
|
|
|
668
692
|
(例: `mpiemses3d-input-review`)。`runops-` で始まる名前は予約されており展開しない。
|
|
669
693
|
2. `references/` を `.agents/skills/<adapter>-references/` にコピーし、
|
|
670
694
|
`SKILL.md` が無ければ reference 一覧を列挙した `SKILL.md` を生成する。
|
|
695
|
+
`docs/` があれば同じく `.agents/skills/<adapter>-docs/` に置く。展開した skill の
|
|
696
|
+
`../../references/`、`../../docs/`(package 内の配置での参照)は `../<adapter>-references/`、
|
|
697
|
+
`../<adapter>-docs/` に書き換える。
|
|
671
698
|
3. 展開先には `.managed-by-runops` marker を置く。marker のある directory は
|
|
672
699
|
`update` 時に **無条件で上書き** する。marker の無い同名 directory は触らず warning。
|
|
673
700
|
4. `.runops/skills.lock` に adapter 名、package version、展開した directory、content hash を記録する。
|
|
674
701
|
|
|
675
702
|
`doctor` は lock の package version と現在 install されている version を比較し、
|
|
676
|
-
差があれば `runo update --skills-only`
|
|
703
|
+
差があれば `runo update --skills-only` を提案する。展開済み skill の相対リンクで
|
|
704
|
+
存在しない file を指すものも報告する。
|
|
677
705
|
|
|
678
706
|
既存の simulator repo(MPIEMSES3D、emout、BEACH)は `plugins/<name>-context/` に
|
|
679
707
|
`skills/` と `references/` を既に持つ。各 package 側で必要な変更は
|
|
@@ -971,4 +999,6 @@ v2 の文書は次の 4 ファイルだけにする。
|
|
|
971
999
|
| 置き換えた系列の整理(2026-10-21) | `archive` が DIR(系列)を受け、`--superseded-by` で置き換え先を manifest に記録する。新しい command と tidy の種別は足さない。skill は新系列を作ったら旧系列の要否を確かめ、不要なら archive して現用系列・archive 先・件数を報告する | 修正版を作った後に旧系列が作業領域に残り、毎回ユーザーが整理を頼んでいた。`parent_run` だけでは比較用の派生と置き換えを区別できないので、置き換えは明示操作で記録する。旧 `archive` は 1 件ずつ検査していて、途中の running run で一部だけ移動していた(#113) |
|
|
972
1000
|
| 生成ハーネスの書き方(2026-10-21) | AGENTS.md の Rules を「作業の進め方」5 項目に絞り、各項目を「何をするか」と短い理由で書く。skill は手順に専念し、AGENTS.md の方針(manifest を手で編集しない等)を繰り返さない | #112・#113 で規則が増え、禁止形(never / do not)が並んでいた。現行の Claude の prompting 指針は、しないことより何をするかを、理由と共に書くことを勧め、強い禁止語は過剰反応を招くとしている |
|
|
973
1001
|
| 複数 run の mv / archive(2026-10-22) | 1 回の `run.lock` の下で全対象を読み直して検査し、通ってから移す。途中の filesystem error では移し終えた run id を示して止まる | `mv A B C --dest X` は 1 件ずつ検査して移していたので、途中の submitted run で一部だけ移動していた。archive は表示の後で lock を取り直していて、その間に再投入された run で同じことが起き得た |
|
|
1002
|
+
| 進捗と系列配置 2.5.0(2026-10-23) | adapter に任意の `progress()`、`sync` が時刻付き観測を保存し速度・残り時間を要約(§6.4)。`new --from` の既定置き場所を元 run と同じ directory に。agent context の `docs/` も展開 | 速度比較のために agent が時刻付き batch 観測を手で保存し、再開点や終了後の待ちを毎回補正していた(4 回)。継続世代が case 直下に並び、同じ計算の系列を見分けにくかった。skill が package の `docs/` を参照していたが展開されていなかった。進捗の解析は simulator 固有なので adapter が持ち、runops は保存と表示だけにする |
|
|
1003
|
+
| FEEDBACK 棚卸し 2.4.0(2026-10-23) | `--job k=v` と `new --from` の `[job]` 継承、created の archive / restore、archived scratch を期限削除しない、展開 skill の references 参照の書き換えと `doctor` のリンク検査、adapter warning への版の併記、生成 AGENTS.md に解析環境の置き場所 | hub の FEEDBACK で、資源変更のための case 編集(3 回)、置換済み draft を archive できず helper で迂回(2 回)、保管目的の archive が期限削除される、skill から reference に辿れない、新版の正規キーが誤記に見える、解析依存の置き場所が project ごとに違う、が記録された。いずれも既存 command の拡張で、command 数は増やさない |
|
|
974
1004
|
| 使い方ガイド | `docs/guide.md` を追加し、文書を 4 ファイルにする(§13.3) | README の依頼例の表と command 一覧の間を埋める、場面ごとの流れ(case と run の分け方、smoke から本計算、状態の決まり方、整理)の置き場所が無かった |
|
|
@@ -49,6 +49,7 @@ class Adapter:
|
|
|
49
49
|
| `keep_patterns() -> list[str]` | `tidy --purge-work` で残す glob |
|
|
50
50
|
| `extend_inputs(src_run, run_dir, params) -> list[str]` | `runo new --from RUN --continue` の restart 設定 |
|
|
51
51
|
| `agent_context_dir() -> Path | None` | 同梱 skill と reference の場所 |
|
|
52
|
+
| `progress(run_dir) -> dict | None` | 進捗 `{"done", "total", "unit", "start"}`。`runo sync` が時刻付きで保存し、速度と残り時間を出す |
|
|
52
53
|
|
|
53
54
|
`runtime` は `executable`、`package_version`、`exe_hash`、`git_commit`、`git_dirty`、
|
|
54
55
|
`env`、`resolver_mode`、`build_dir` を持ちます。
|
|
@@ -76,12 +77,16 @@ project の `adapters/mysim.py` に `Adapter` という名前で置けば使え
|
|
|
76
77
|
<dir>/
|
|
77
78
|
skills/<skill-name>/SKILL.md
|
|
78
79
|
references/*.md
|
|
80
|
+
docs/*.md # 任意。references と同じく展開する
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
`runo init` と `runo update` は `skills/*` を project の `.agents/skills/` と
|
|
82
84
|
`.claude/skills/` に同名でコピーし、`references/` を `<adapter>-references` として置きます。
|
|
83
85
|
skill 名は package 側で名前空間を付けてください(例: `mysim-input-review`)。
|
|
84
|
-
`runops-`
|
|
86
|
+
`runops-` で始まる名前は予約されています。skill から reference へは
|
|
87
|
+
`../<adapter>-references/X.md`(`docs/` なら `../<adapter>-docs/X.md`)と書いてください。
|
|
88
|
+
package 内の配置どおりの `../../references/X.md`、`../../docs/X.md` は展開時にこの形へ書き換えます。`runo doctor` は展開済み skill の
|
|
89
|
+
相対リンク切れを報告します。展開した directory には
|
|
85
90
|
`.managed-by-runops` が置かれ、更新時に上書きされます。利用者が作った同名 directory は
|
|
86
91
|
上書きしません。
|
|
87
92
|
|
|
@@ -105,8 +105,11 @@ run id、入力、sweep の帰属、ノートはそのままで、移動元は
|
|
|
105
105
|
投入、削除、整理、移行の前には、エージェントが対象の run、partition、資源量、core-hours を示して
|
|
106
106
|
確認を取ります。`runo` 自体も、複数 run の投入や削除の前に確認を求めます。
|
|
107
107
|
|
|
108
|
-
資源量(partition、walltime、並列数)は case の `[job]` に書きます。
|
|
109
|
-
|
|
108
|
+
資源量(partition、walltime、並列数)は case の `[job]` に書きます。run ごとに変えるときは
|
|
109
|
+
「R… を 256 並列・72 時間で作り直して」と頼めば `runo new --from RUN --job processes=256 --job walltime=72:00:00`
|
|
110
|
+
になり、case は変えません。`--from` で作る run は元 run の資源を引き継ぎ、作成時に元 run との
|
|
111
|
+
params と資源の差分を表示します。1 回だけ partition を変えるなら「今回は gr20001b で投げて」と頼めば
|
|
112
|
+
`runo submit RUN --partition gr20001b` になります。
|
|
110
113
|
|
|
111
114
|
walltime は、同じ case・同じ資源で終わった run の実時間を元に決めます。`runo list --json` と
|
|
112
115
|
`runo status` は、各 run の `elapsed`(最新の実行の実時間)、`walltime`、使った割合を示します。
|
|
@@ -128,6 +131,11 @@ walltime は、同じ case・同じ資源で終わった run の実時間を元
|
|
|
128
131
|
`runo sync` は、active な run が何件あっても `squeue` と `sacct` を 1 回ずつしか呼びません。
|
|
129
132
|
エージェントには `squeue` を直接叩かせず、`runo sync` を使わせてください。
|
|
130
133
|
|
|
134
|
+
adapter が進捗に対応していれば(BEACH の batch、MPIEMSES3D の step)、`sync` のたびに時刻付きの
|
|
135
|
+
観測を残し、`runo list` / `status` に `62% 0.198 s/batch eta 3.1h` のように進み具合、直近 10 分の
|
|
136
|
+
速度、残り時間を出します。resume した run は再開点から数え、終わった run は終了時刻までで速度を出すので、
|
|
137
|
+
版を切り替えた前後の比較にそのまま使えます(`--json` の `progress`)。
|
|
138
|
+
|
|
131
139
|
### 状態の決まり方
|
|
132
140
|
|
|
133
141
|
run の状態は `created → submitted → running → completed | failed | cancelled` と進みます。
|
|
@@ -177,6 +185,7 @@ project の run だけが対象です。同じアカウントの別 project の
|
|
|
177
185
|
> R20261015-0003 の続きを 2 倍の step で。
|
|
178
186
|
|
|
179
187
|
`runo new --from RUN --continue` で、終わった run の出力から再開する run を作ります。
|
|
188
|
+
`--from` で作る run は元 run と同じ directory に置かれるので、同じ計算の継続は 1 つの directory に並びます。
|
|
180
189
|
再開の方法は simulator ごとに違い、adapter が対応している場合だけ使えます
|
|
181
190
|
(現在は MPIEMSES3D が対応し、`SNAPSHOT1` から再開します)。新しい run は元の run を `parent_run` として記録します。
|
|
182
191
|
|
|
@@ -209,7 +218,7 @@ run は消さなくても `runo list` が古いものを隠しますが、direct
|
|
|
209
218
|
|
|
210
219
|
| 種類 | 条件 | 処理 |
|
|
211
220
|
|---|---|---|
|
|
212
|
-
| `expired-scratch` | 期限切れの scratch run | 削除 |
|
|
221
|
+
| `expired-scratch` | 期限切れの scratch run(archive したものは除く) | 削除 |
|
|
213
222
|
| `stale-failed` | 30 日より古い failed / cancelled で、`why` もノートも無い | 削除 |
|
|
214
223
|
| `old-completed` | 90 日より古い completed | archive |
|
|
215
224
|
| `orphan-job` | 投入済みなのに Slurm が知らない | `sync` で状態を確定 |
|
|
@@ -225,8 +234,8 @@ run id で引き続き見つかり、`runo restore` で戻せます。
|
|
|
225
234
|
> 旧系列は不要。新系列で置き換えたと記録して archive して。
|
|
226
235
|
|
|
227
236
|
で `runo archive runs/<case>/<旧系列> --superseded-by runs/<case>/<新系列> --why "..."` になります。
|
|
228
|
-
系列の directory を渡すと、対象の run
|
|
229
|
-
|
|
237
|
+
系列の directory を渡すと、対象の run を先に表示し、投入中・実行中の run と pin した run は
|
|
238
|
+
除きます。投入前の run(created)も `--superseded-by` を付けたときは archive でき、`runo restore` で戻せます。各旧 run の `superseded_by` には、その run から `--from` で作った新 run(無ければ新系列の全 run)が
|
|
230
239
|
記録され、理由はノートに残ります。比較用に派生しただけの run は置き換えとは扱わないので、
|
|
231
240
|
旧系列が不要かどうかは毎回確かめます。
|
|
232
241
|
|
|
@@ -110,9 +110,9 @@ runops だけが書きます。手で編集しないでください。主な fie
|
|
|
110
110
|
| `[path]` | `run_dir`(project 相対)、`archived_from`、`archived_at` |
|
|
111
111
|
| `[origin]` | `case`、`sweep`、`parent_run`、`created_by`、`case_input_hash` |
|
|
112
112
|
| `[simulator]` | `name`、`adapter`、`adapter_source`、`adapter_version`、`executable`、`resolver_mode`、`package_version`、`exe_hash`、`git_commit`、`git_dirty`、`build_dir` |
|
|
113
|
-
| `[job]` | case の `[job]` に加え `job_id`、`attempt`、`submitted_at`、`finished_at`、`last_slurm_state`、`exit_code`、`[[job.attempts]]`(attempt ごとの `job_id`、`started_at`、`exit_code`、`exe_hash`、`elapsed`、`cancel --why` の `cancel_reason
|
|
113
|
+
| `[job]` | case の `[job]` に加え `job_id`、`attempt`、`submitted_at`、`finished_at`、`last_slurm_state`、`exit_code`、`[[job.attempts]]`(attempt ごとの `job_id`、`started_at`、`exit_code`、`exe_hash`、`elapsed`、`cancel --why` の `cancel_reason`、`runo sync` が付ける `progress`(§進捗)) |
|
|
114
114
|
| `[params]` | case の params に `--set` を適用した最終値 |
|
|
115
|
-
| `[variation]` | `changed_keys` |
|
|
115
|
+
| `[variation]` | `changed_keys`、`job_changed_keys`(case の `[job]` と違う key) |
|
|
116
116
|
| `[identity]` | `input_hash` |
|
|
117
117
|
| `[storage]` | `work_purged_at` |
|
|
118
118
|
| `[[notes]]` | `at`、`by`、`text` |
|
|
@@ -139,7 +139,9 @@ run id は削除後も再利用しません。`--dest` に既存 run の中は
|
|
|
139
139
|
## command
|
|
140
140
|
|
|
141
141
|
`list --json` と `status --json` は、walltime の根拠として `walltime`、最新 attempt の `elapsed`、
|
|
142
|
-
`walltime_used`(elapsed / walltime)、`slurm_state`
|
|
142
|
+
`walltime_used`(elapsed / walltime)、`slurm_state` も返します。adapter が `progress()` を持てば
|
|
143
|
+
`progress`(`done`、`total`、`unit`、`fraction`、`rate_recent_per_hour`、`rate_attempt_per_hour`、
|
|
144
|
+
`eta_hours`)も返し、表の `progress` 列に `62% 0.198 s/batch eta 3.1h` と出します。表では `TIMEOUT` などを
|
|
143
145
|
`failed (TIMEOUT)` と示します。
|
|
144
146
|
|
|
145
147
|
全 command は `--help` で option を確認できます。`RUN` には run id、run directory、
|
|
@@ -148,14 +150,14 @@ run id は削除後も再利用しません。`--dest` に既存 run の中は
|
|
|
148
150
|
| command | 動作 |
|
|
149
151
|
|---|---|
|
|
150
152
|
| `runo init [SIM...] [--site kudpc\|standard] [--no-install] [-y]` | project を作る。`SIM` は `emses` や `name=package-spec`。端末から引数なしで実行すると site と simulator を尋ねる。`--site` の既定はホスト名から推定(KUDPC のログインノードなら `kudpc`、`camphor` も同義) |
|
|
151
|
-
| `runo doctor [--json]` | 設定、adapter の解決経路、executable、sbatch、skill
|
|
153
|
+
| `runo doctor [--json]` | 設定、adapter の解決経路、executable、sbatch、skill の鮮度とリンク切れを点検 |
|
|
152
154
|
| `runo update [--skills-only] [-y]` | simulator package を更新し skill と AGENTS.md の管理 block を再生成 |
|
|
153
155
|
| `runo build SIM [--force]` | `local_source` の simulator を build |
|
|
154
156
|
| `runo context [--json]` | agent 向けの現在地 |
|
|
155
157
|
| `runo case new NAME [-s SIM] [--from CASE]` / `runo case list` | case の作成と一覧 |
|
|
156
|
-
| `runo new CASE [--why] [--label] [--set k=v]... [--tag] [--scratch] [--dest DIR] [--submit] [--json]` | run を 1
|
|
157
|
-
| `runo new --from RUN [--set k=v]... [--continue]` | 既存 run の params
|
|
158
|
-
| `runo sweep CASE --set k=v1,v2 ... [--names k=n1,n2...] [--label] [--dest DIR] [--limit N] [--apply] [--submit] [--json]` | 直積の候補表示。`--apply` で作成。`--names` は各値の短い label、`--dest` は sweep directory の親 |
|
|
158
|
+
| `runo new CASE [--why] [--label] [--set k=v]... [--job k=v]... [--tag] [--scratch] [--dest DIR] [--submit] [--json]` | run を 1 つ作る。`--job` はこの run の `[job]` の値 |
|
|
159
|
+
| `runo new --from RUN [--set k=v]... [--job k=v]... [--job-from-case] [--continue]` | 既存 run の params と `[job]` から派生(`--job-from-case` で `[job]` は case から)。置き場所の既定は元 run と同じ directory。`--continue` は adapter の restart 対応が必要 |
|
|
160
|
+
| `runo sweep CASE --set k=v1,v2 ... [--names k=n1,n2...] [--job k=v]... [--label] [--dest DIR] [--limit N] [--apply] [--submit] [--json]` | 直積の候補表示。`--apply` で作成。`--names` は各値の短い label、`--dest` は sweep directory の親 |
|
|
159
161
|
| `runo submit RUN... [-p P] [--qos Q] [--afterok JOB]` / `runo submit --all [DIR] [-y]` | job.sh を生成して sbatch |
|
|
160
162
|
| `runo cancel RUN... [--why TEXT] [-y]` / `runo retry RUN [--submit] [-y]` | scancel(`--why` は note に残る)/ failed・cancelled を created に戻す(理由付きで cancel された run は確認を取る) |
|
|
161
163
|
| `runo status [RUN...]` / `runo sync [RUN...]` / `runo log RUN [--err] [-n N] [-f]` | 状態表示 / Slurm から更新 / log 表示 |
|
|
@@ -163,7 +165,7 @@ run id は削除後も再利用しません。`--dest` に既存 run の中は
|
|
|
163
165
|
| `runo note RUN [TEXT]` / `runo pin RUN [--off] [--reason]` | ノート追記・表示 / pin |
|
|
164
166
|
| `runo summarize RUN... [--force]` | adapter の要約を `analysis/summary.json` に書く |
|
|
165
167
|
| `runo mv RUN... [--dest DIR] [--label L] [--why TEXT]` | `runs/` 配下で改名・移動。id、入力、sweep、ノートは保つ。submitted / running と archived は不可。全対象を検査してから移し、1 件でも不可なら何も動かさない |
|
|
166
|
-
| `runo archive RUN\|DIR... [--superseded-by RUN\|DIR --why TEXT]` / `runo archive --older-than 90d` / `runo restore RUN` | `runs/_archive/` へ移動 /
|
|
168
|
+
| `runo archive RUN\|DIR... [--superseded-by RUN\|DIR --why TEXT]` / `runo archive --older-than 90d` / `runo restore RUN` | `runs/_archive/` へ移動 / 戻す。対象は created と終了した run。移動前に全対象を検査し、対象外の RUN があれば何も動かさない。DIR は配下の run(系列)で、submitted / running と pinned を除き、created は `--superseded-by` のときだけ含めて表示する。`--superseded-by` は置き換え先(その run から `--from` で派生した run、無ければ指定した全 run)を `superseded_by` に記録し、`--why` をノートに残す |
|
|
167
169
|
| `runo delete RUN... [-y]` | created・failed・cancelled・scratch の run を削除 |
|
|
168
170
|
| `runo tidy [--apply] [--only KIND] [--older-than AGE] [--purge-work RUN...]` | 整理候補の表示と実行 |
|
|
169
171
|
| `runo migrate-v1 [--dry-run] [--apply-delete-test-runs] [-y]` | v1 project の変換 |
|
|
@@ -175,7 +177,7 @@ scratch と archived は表示しません。
|
|
|
175
177
|
|
|
176
178
|
| kind | 条件 | 動作 |
|
|
177
179
|
|---|---|---|
|
|
178
|
-
| `expired-scratch` | 期限切れの scratch run | delete |
|
|
180
|
+
| `expired-scratch` | 期限切れの scratch run(archived は除く) | delete |
|
|
179
181
|
| `stale-failed` | `tidy_failed_days`(既定 30)日より古い failed / cancelled で、why も note も無い | delete |
|
|
180
182
|
| `old-completed` | `tidy_archive_days`(既定 90)日より古い completed | archive |
|
|
181
183
|
| `orphan-job` | Slurm が知らない submitted / running | sync |
|
|
@@ -312,9 +312,13 @@ def case_list(as_json: bool = typer.Option(False, "--json")) -> None:
|
|
|
312
312
|
# ---------------------------------------------------------------------------
|
|
313
313
|
|
|
314
314
|
|
|
315
|
-
def _print_created(
|
|
315
|
+
def _print_created(
|
|
316
|
+
project: Project, manifests: list[Manifest], changes: list[str] | None = None
|
|
317
|
+
) -> None:
|
|
316
318
|
for m in manifests:
|
|
317
319
|
out.print(f"created {m.id} {project.rel(m.run_dir)}")
|
|
320
|
+
for line in changes or []:
|
|
321
|
+
out.print(f" {line}", markup=False)
|
|
318
322
|
|
|
319
323
|
|
|
320
324
|
def _submit_each(project: Project, manifests: list[Manifest], *, quiet: bool = False) -> None:
|
|
@@ -341,16 +345,24 @@ def new(
|
|
|
341
345
|
continue_run: bool = typer.Option(
|
|
342
346
|
False, "--continue", help="Continue from --from RUN (adapter support needed)."
|
|
343
347
|
),
|
|
348
|
+
job: Optional[list[str]] = typer.Option(
|
|
349
|
+
None, "--job", help="key=value for this run's [job] (walltime, processes, ...)."
|
|
350
|
+
),
|
|
351
|
+
job_from_case: bool = typer.Option(
|
|
352
|
+
False, "--job-from-case", help="With --from: take [job] from the case, not the run."
|
|
353
|
+
),
|
|
344
354
|
submit: bool = typer.Option(False, "--submit"),
|
|
345
355
|
created_by: str = typer.Option("", "--created-by"),
|
|
346
356
|
as_json: bool = typer.Option(False, "--json", help="Print the created run as JSON."),
|
|
347
357
|
) -> None:
|
|
348
358
|
"""Create one run."""
|
|
349
|
-
from runops.ops.create import CreateRequest, create_run, parse_sets
|
|
359
|
+
from runops.ops.create import CreateRequest, create_run, parse_job, parse_sets
|
|
350
360
|
|
|
351
361
|
project = _project()
|
|
352
362
|
if case and from_run:
|
|
353
363
|
raise UsageError("give either CASE or --from RUN, not both")
|
|
364
|
+
if job_from_case and not from_run:
|
|
365
|
+
raise UsageError("--job-from-case needs --from RUN")
|
|
354
366
|
request = CreateRequest(
|
|
355
367
|
case=case or "",
|
|
356
368
|
sets=parse_sets(sets or []),
|
|
@@ -362,13 +374,15 @@ def new(
|
|
|
362
374
|
created_by=created_by or _actor(),
|
|
363
375
|
from_run=from_run,
|
|
364
376
|
continue_run=continue_run,
|
|
377
|
+
job=parse_job(job or []),
|
|
378
|
+
job_from_case=job_from_case,
|
|
365
379
|
)
|
|
366
380
|
result = create_run(project, request)
|
|
367
381
|
_warn(result.warnings)
|
|
368
382
|
if not why:
|
|
369
383
|
_warn(["no --why given; consider `runo note RUN` to record the purpose"])
|
|
370
384
|
if not as_json:
|
|
371
|
-
_print_created(project, result.manifests)
|
|
385
|
+
_print_created(project, result.manifests, result.changes)
|
|
372
386
|
if submit:
|
|
373
387
|
_submit_each(project, result.manifests, quiet=as_json)
|
|
374
388
|
if as_json:
|
|
@@ -388,6 +402,9 @@ def sweep(
|
|
|
388
402
|
dest: str = typer.Option(
|
|
389
403
|
"", "--dest", help="Parent of the sweep directory (default runs/<case>)."
|
|
390
404
|
),
|
|
405
|
+
job: Optional[list[str]] = typer.Option(
|
|
406
|
+
None, "--job", help="key=value for the [job] of every point (walltime, processes, ...)."
|
|
407
|
+
),
|
|
391
408
|
why: str = typer.Option("", "--why"),
|
|
392
409
|
apply: bool = typer.Option(False, "--apply", help="Create the runs."),
|
|
393
410
|
submit: bool = typer.Option(False, "--submit", help="Submit after --apply."),
|
|
@@ -401,6 +418,7 @@ def sweep(
|
|
|
401
418
|
from runops.ops.create import (
|
|
402
419
|
CreateRequest,
|
|
403
420
|
apply_sweep,
|
|
421
|
+
parse_job,
|
|
404
422
|
parse_sweep_names,
|
|
405
423
|
parse_sweep_sets,
|
|
406
424
|
plan_sweep,
|
|
@@ -418,6 +436,7 @@ def sweep(
|
|
|
418
436
|
limit=limit,
|
|
419
437
|
dest=dest,
|
|
420
438
|
names=parse_sweep_names(names or [], axes),
|
|
439
|
+
job=parse_job(job or []),
|
|
421
440
|
)
|
|
422
441
|
if as_json and not apply:
|
|
423
442
|
_print_json(
|
|
@@ -429,6 +448,7 @@ def sweep(
|
|
|
429
448
|
{"ref": p.ref, "label": p.label, "values": p.values} for p in plan.points
|
|
430
449
|
],
|
|
431
450
|
"core_hours": plan.core_hours,
|
|
451
|
+
"job": plan.job,
|
|
432
452
|
}
|
|
433
453
|
)
|
|
434
454
|
return
|
|
@@ -446,6 +466,15 @@ def sweep(
|
|
|
446
466
|
f"{len(plan.points)} runs -> {project.rel(plan.parent)}/ "
|
|
447
467
|
f"(~{plan.core_hours:.1f} core-hours)"
|
|
448
468
|
)
|
|
469
|
+
overridden = {k: v for k, v in plan.job.items() if plan.case.job.get(k) != v}
|
|
470
|
+
if overridden:
|
|
471
|
+
out.print(
|
|
472
|
+
" job vs case: "
|
|
473
|
+
+ ", ".join(
|
|
474
|
+
f"{k} {plan.case.job.get(k, '-')} -> {v}" for k, v in overridden.items()
|
|
475
|
+
),
|
|
476
|
+
markup=False,
|
|
477
|
+
)
|
|
449
478
|
if not apply:
|
|
450
479
|
out.print("preview only; add --apply to create")
|
|
451
480
|
return
|
|
@@ -458,7 +487,7 @@ def sweep(
|
|
|
458
487
|
result = apply_sweep(project, plan, request)
|
|
459
488
|
_warn(result.warnings)
|
|
460
489
|
if not as_json:
|
|
461
|
-
_print_created(project, result.manifests)
|
|
490
|
+
_print_created(project, result.manifests, result.changes)
|
|
462
491
|
if submit:
|
|
463
492
|
_submit_each(project, result.manifests, quiet=as_json)
|
|
464
493
|
if as_json:
|
|
@@ -563,9 +592,20 @@ def retry(
|
|
|
563
592
|
|
|
564
593
|
def _status_table(project: Project, manifests: list[Manifest]) -> Table:
|
|
565
594
|
from runops.ops.listing import relative_age
|
|
595
|
+
from runops.ops.progress import format_progress
|
|
566
596
|
|
|
567
597
|
table = Table(
|
|
568
|
-
"id",
|
|
598
|
+
"id",
|
|
599
|
+
"status",
|
|
600
|
+
"label",
|
|
601
|
+
"case",
|
|
602
|
+
"sweep",
|
|
603
|
+
"created",
|
|
604
|
+
"elapsed",
|
|
605
|
+
"progress",
|
|
606
|
+
"note",
|
|
607
|
+
"why",
|
|
608
|
+
box=None,
|
|
569
609
|
)
|
|
570
610
|
for m in manifests:
|
|
571
611
|
s = m.to_summary(project.root)
|
|
@@ -586,6 +626,8 @@ def _status_table(project: Project, manifests: list[Manifest]) -> Table:
|
|
|
586
626
|
status += " [dim](scratch)[/]"
|
|
587
627
|
if s["pinned"]:
|
|
588
628
|
status += " [dim](pinned)[/]"
|
|
629
|
+
if s["superseded_by"]:
|
|
630
|
+
status += f" [dim](-> {', '.join(s['superseded_by'])})[/]"
|
|
589
631
|
table.add_row(
|
|
590
632
|
s["id"],
|
|
591
633
|
status,
|
|
@@ -594,6 +636,7 @@ def _status_table(project: Project, manifests: list[Manifest]) -> Table:
|
|
|
594
636
|
s["sweep"],
|
|
595
637
|
relative_age(s["created_at"]),
|
|
596
638
|
elapsed,
|
|
639
|
+
format_progress(s["progress"]),
|
|
597
640
|
s["last_note"][:60],
|
|
598
641
|
s["why"][:60],
|
|
599
642
|
)
|
|
@@ -793,7 +836,7 @@ def archive(
|
|
|
793
836
|
why: str = typer.Option("", "--why", help="Reason, recorded in each run's notes."),
|
|
794
837
|
yes: bool = typer.Option(False, "-y", "--yes"),
|
|
795
838
|
) -> None:
|
|
796
|
-
"""Move
|
|
839
|
+
"""Move created or finished runs to runs/_archive/ (same relative path)."""
|
|
797
840
|
from datetime import datetime
|
|
798
841
|
|
|
799
842
|
from runops.ops.tidy import ArchivePlan, archive_runs, plan_archive
|
|
@@ -14,8 +14,8 @@ COMMANDS = """\
|
|
|
14
14
|
|---|---|
|
|
15
15
|
| `runo context --json` | where the project stands: active runs, recent runs, LOG.md tail |
|
|
16
16
|
| `runo case list` / `runo case new NAME --simulator SIM [--from CASE]` | list / scaffold cases |
|
|
17
|
-
| `runo new CASE --why TEXT [--set k=v]... [--scratch] [--submit]` | create one run |
|
|
18
|
-
| `runo new --from RUN --why TEXT [--set k=v]... [--continue]` | derive a run
|
|
17
|
+
| `runo new CASE --why TEXT [--set k=v]... [--job k=v]... [--scratch] [--submit]` | create one run |
|
|
18
|
+
| `runo new --from RUN --why TEXT [--set k=v]... [--job k=v]... [--continue]` | derive a run (keeps its params and [job]) |
|
|
19
19
|
| `runo sweep CASE --set k=v1,v2 [--names k=n1,n2] [--apply] [--submit]` | preview / create a parameter product |
|
|
20
20
|
| `runo submit RUN... [--partition P]` / `runo submit --all DIR` | sbatch created runs |
|
|
21
21
|
| `runo sync` / `runo status RUN` / `runo log RUN [--err]` | refresh state from Slurm / show / read logs |
|
|
@@ -25,7 +25,7 @@ COMMANDS = """\
|
|
|
25
25
|
| `runo summarize RUN` | adapter summary into analysis/summary.json |
|
|
26
26
|
| `runo tidy` / `runo tidy --apply` | list / apply cleanup (expired scratch, stale failures, old runs) |
|
|
27
27
|
| `runo mv RUN... [--dest DIR] [--label L] --why TEXT` | rename / move runs, keeping ids and inputs |
|
|
28
|
-
| `runo archive RUN\\|DIR... [--superseded-by RUN\\|DIR --why T]` | move finished runs (or a series) to runs/_archive |
|
|
28
|
+
| `runo archive RUN\\|DIR... [--superseded-by RUN\\|DIR --why T]` | move created or finished runs (or a series) to runs/_archive |
|
|
29
29
|
| `runo restore RUN` / `runo delete RUN` | move back / remove |
|
|
30
30
|
| `runo build SIM` | build a local_source simulator into .runops/build |
|
|
31
31
|
| `runo doctor` / `runo update` | check environment / upgrade simulator packages and skills |"""
|
|
@@ -70,9 +70,9 @@ the project found from the current directory.
|
|
|
70
70
|
sessions, so act on them only when the user names them. If nothing here matches, say so.
|
|
71
71
|
- **Change runs through `runo`.** `runo` writes each run's manifest and `input/`, so
|
|
72
72
|
they stay an exact record of what ran.
|
|
73
|
-
Express a change as a new run (`runo new --from RUN --set k=v`,
|
|
74
|
-
rename or regroup with `runo mv`. Variations are
|
|
75
|
-
structure of the base input.
|
|
73
|
+
Express a change as a new run (`runo new --from RUN --set k=v`, `--job walltime=...`
|
|
74
|
+
for resources, `runo sweep`) and rename or regroup with `runo mv`. Variations are
|
|
75
|
+
runs; a new case is for a different structure of the base input.
|
|
76
76
|
- **Leave the reason behind.** Give each new run `--why`. When you cancel runs to fix or
|
|
77
77
|
replace them, add `runo cancel --why`, so later sessions and `runo retry` leave them
|
|
78
78
|
stopped. When a new series replaces an old one, ask whether the old one is still
|
|
@@ -86,6 +86,10 @@ Notes live in three places, where `runo context` and the next session find them:
|
|
|
86
86
|
- reusable knowledge (formulas, methods, pitfalls): `notes/<topic>.md`, extending the
|
|
87
87
|
existing file for a topic.
|
|
88
88
|
|
|
89
|
+
Analysis tools the simulator package does not bring go in `.runops/analysis-env/`
|
|
90
|
+
(`uv venv .runops/analysis-env`), so `.venv` stays as `runo update` manages it. Run heavy
|
|
91
|
+
reading and plotting where the site allows compute jobs.
|
|
92
|
+
|
|
89
93
|
## Skills
|
|
90
94
|
|
|
91
95
|
{skill_lines}
|
|
@@ -27,7 +27,7 @@ ACTIVE_STATES = frozenset({RunState.SUBMITTED, RunState.RUNNING})
|
|
|
27
27
|
TERMINAL_STATES = frozenset({RunState.COMPLETED, RunState.FAILED, RunState.CANCELLED})
|
|
28
28
|
|
|
29
29
|
_TRANSITIONS: dict[RunState, frozenset[RunState]] = {
|
|
30
|
-
RunState.CREATED: frozenset({RunState.SUBMITTED}),
|
|
30
|
+
RunState.CREATED: frozenset({RunState.SUBMITTED, RunState.ARCHIVED}),
|
|
31
31
|
RunState.SUBMITTED: frozenset(
|
|
32
32
|
{RunState.RUNNING, RunState.COMPLETED, RunState.FAILED, RunState.CANCELLED}
|
|
33
33
|
),
|
|
@@ -37,10 +37,24 @@ _TRANSITIONS: dict[RunState, frozenset[RunState]] = {
|
|
|
37
37
|
RunState.COMPLETED: frozenset({RunState.ARCHIVED}),
|
|
38
38
|
RunState.FAILED: frozenset({RunState.CREATED, RunState.ARCHIVED}),
|
|
39
39
|
RunState.CANCELLED: frozenset({RunState.CREATED, RunState.ARCHIVED}),
|
|
40
|
-
RunState.ARCHIVED: frozenset(TERMINAL_STATES),
|
|
40
|
+
RunState.ARCHIVED: frozenset({*TERMINAL_STATES, RunState.CREATED}),
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
# Shown with each run so resource differences are visible next to parameter differences.
|
|
45
|
+
_RESOURCE_KEYS = (
|
|
46
|
+
"partition",
|
|
47
|
+
"qos",
|
|
48
|
+
"walltime",
|
|
49
|
+
"nodes",
|
|
50
|
+
"ntasks",
|
|
51
|
+
"cpus_per_task",
|
|
52
|
+
"processes",
|
|
53
|
+
"threads",
|
|
54
|
+
"cores",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
44
58
|
class Manifest:
|
|
45
59
|
"""A run manifest backed by a plain dict so unknown fields survive."""
|
|
46
60
|
|
|
@@ -198,14 +212,24 @@ class Manifest:
|
|
|
198
212
|
# Enough for an agent to compare runs without another command.
|
|
199
213
|
"params": self.params,
|
|
200
214
|
"changed_keys": list(self.section("variation").get("changed_keys", [])),
|
|
215
|
+
"job_changed_keys": list(self.section("variation").get("job_changed_keys", [])),
|
|
216
|
+
"resources": {k: self.job[k] for k in _RESOURCE_KEYS if k in self.job},
|
|
201
217
|
"parent_run": self.parent_run,
|
|
202
218
|
"superseded_by": self.superseded_by,
|
|
203
219
|
"exe_hash": str(self.section("simulator").get("exe_hash", "")),
|
|
204
220
|
"input_hash": str(self.section("identity").get("input_hash", "")),
|
|
205
221
|
# Walltime evidence for the next run: elapsed of the latest attempt.
|
|
206
222
|
**self.timing(),
|
|
223
|
+
"progress": self.progress,
|
|
207
224
|
}
|
|
208
225
|
|
|
226
|
+
@property
|
|
227
|
+
def progress(self) -> dict[str, Any] | None:
|
|
228
|
+
"""Progress of the latest attempt as `runo sync` last saw it (adapter ``progress()``)."""
|
|
229
|
+
attempts = self.job.get("attempts") or []
|
|
230
|
+
value = attempts[-1].get("progress") if attempts else None
|
|
231
|
+
return dict(value) if isinstance(value, dict) else None
|
|
232
|
+
|
|
209
233
|
def timing(self) -> dict[str, Any]:
|
|
210
234
|
"""Walltime, latest attempt elapsed, the fraction used, and the last Slurm state."""
|
|
211
235
|
walltime = str(self.job.get("walltime", ""))
|