runops 2.2.0__tar.gz → 2.3.1__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.2.0 → runops-2.3.1}/PKG-INFO +3 -1
- {runops-2.2.0 → runops-2.3.1}/README.md +2 -0
- {runops-2.2.0 → runops-2.3.1}/SPEC.md +21 -7
- {runops-2.2.0 → runops-2.3.1}/docs/guide.md +20 -1
- {runops-2.2.0 → runops-2.3.1}/docs/reference.md +8 -5
- {runops-2.2.0 → runops-2.3.1}/pyproject.toml +1 -1
- {runops-2.2.0 → runops-2.3.1}/src/runops/__init__.py +1 -1
- {runops-2.2.0 → runops-2.3.1}/src/runops/cli.py +54 -29
- {runops-2.2.0 → runops-2.3.1}/src/runops/harness.py +33 -25
- {runops-2.2.0 → runops-2.3.1}/src/runops/manifest.py +10 -1
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/submit.py +18 -1
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/tidy.py +195 -48
- {runops-2.2.0 → runops-2.3.1}/src/runops/project.py +41 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/skills/runops-check-status/SKILL.md +14 -11
- {runops-2.2.0 → runops-2.3.1}/src/runops/skills/runops-create-run/SKILL.md +10 -7
- {runops-2.2.0 → runops-2.3.1}/src/runops/skills/runops-reference/SKILL.md +7 -6
- {runops-2.2.0 → runops-2.3.1}/src/runops/skills/runops-tidy/SKILL.md +4 -0
- runops-2.3.1/tests/test_archive_series.py +117 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_lifecycle.py +16 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_mv_naming.py +40 -0
- runops-2.3.1/tests/test_project_scope.py +50 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_skills.py +18 -0
- {runops-2.2.0 → runops-2.3.1}/uv.lock +1 -1
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/check/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/improve-harness/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/agents/openai.yaml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/examples/refactor-request.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/references/anti-patterns.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/references/api-compatibility.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/references/packaging-modernization.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/references/refactor-playbook.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/references/verification.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/scripts/api_surface_snapshot.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/scripts/import_smoke.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/scripts/inspect_python_package.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/python-package-refactor/scripts/refactor_quality_gate.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/release/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.agents/skills/sync-docs/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/agents/spec-reviewer.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/rules/architecture.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/rules/dev-workflow.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/rules/gotchas.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/settings.json +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/check/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/improve-harness/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/release/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.claude/skills/sync-docs/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.codex/README.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/.codex/agents/spec-reviewer.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.codex/config.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.codex/rules/runops.rules +0 -0
- {runops-2.2.0 → runops-2.3.1}/.dockerignore +0 -0
- {runops-2.2.0 → runops-2.3.1}/.gitattributes +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/assets/conversation-dark.svg +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/assets/conversation-light.svg +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/assets/hero-dark.svg +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/assets/hero-light.svg +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/assets/make_readme_art.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/workflows/ci.yml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.github/workflows/publish.yml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.gitignore +0 -0
- {runops-2.2.0 → runops-2.3.1}/.harness/manifest.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.pre-commit-config.yaml +0 -0
- {runops-2.2.0 → runops-2.3.1}/.vscode/settings.json +0 -0
- {runops-2.2.0 → runops-2.3.1}/AGENTS.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/CLAUDE.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/LICENSE +0 -0
- {runops-2.2.0 → runops-2.3.1}/docs/adapter.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/adapters/__init__.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/adapters/base.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/adapters/generic.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/adapters/registry.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/case.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/config.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/errors.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/jobgen.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/launchers.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/legacy_adapters/__init__.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/legacy_adapters/beach.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/legacy_adapters/emses.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/__init__.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/common.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/create.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/listing.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/migrate.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/notes.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/setup.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/skills.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/ops/sync.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/runtime.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/skills/runops-maintain/SKILL.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/slurm.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/src/runops/util.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/__init__.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/conftest.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/.codex/rules/a.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/.runops/test-runs/T20260701-0001/receipt.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/.runops/work/beach-x-abc/bin/beach +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/AGENTS.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/CLAUDE.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/cases/beach/plane/beach.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/cases/beach/plane/case.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/cases/old/beach.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/cases/old/case.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/launchers.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/research/CURRENT.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runops.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/AGENTS.md +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/_archive/plane/R20260601-0001/manifest.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/input/beach.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/manifest.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/plane/R20260702-0001/manifest.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/scan/R20260701-0002/manifest.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/runs/scan/survey.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/simulators.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/fixtures/v1-project/site.toml +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/golden/mpi_standard.sh +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/golden/openmp_rsc.sh +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_adapters.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_build.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_dev_harness.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_jobgen.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_legacy_adapters.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_manifest_config.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_migrate.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_safety.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_scratch_tidy.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_setup_misc.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/tests/test_sweep.py +0 -0
- {runops-2.2.0 → runops-2.3.1}/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.3.1
|
|
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
|
|
@@ -74,11 +74,13 @@ runo init
|
|
|
74
74
|
| 様子を見る | 先週投げた run の状況を見て。失敗していたら原因をノートに残して |
|
|
75
75
|
| 続きを流す | R20261015-0003 の続きを 2 倍の step で |
|
|
76
76
|
| 片付ける | 古い run を整理して。消すものと archive するものを先に見せて |
|
|
77
|
+
| 作り直した旧系列を片付ける | 旧系列は新系列で置き換えたので、対応を記録して archive して |
|
|
77
78
|
| simulator を足す | パッチを当てた ~/BEACH-dev をビルドして、別の simulator として登録して |
|
|
78
79
|
| 更新する | runops と simulator を最新にして |
|
|
79
80
|
| v1 から移る | この project を runops v2 に移行して(`runo migrate-v1` を dry-run で確認してから) |
|
|
80
81
|
|
|
81
82
|
投入、削除、整理、移行の前には、エージェントが対象と資源量を示して確認を取ります。
|
|
83
|
+
範囲を言わない依頼(「全部再投入して」など)は、エージェントを起動した project の run だけが対象です。
|
|
82
84
|
場面ごとの流れは [使い方ガイド](https://github.com/Nkzono99/runops/blob/main/docs/guide.md) にあります。
|
|
83
85
|
|
|
84
86
|
## project の中身
|
|
@@ -43,11 +43,13 @@ runo init
|
|
|
43
43
|
| 様子を見る | 先週投げた run の状況を見て。失敗していたら原因をノートに残して |
|
|
44
44
|
| 続きを流す | R20261015-0003 の続きを 2 倍の step で |
|
|
45
45
|
| 片付ける | 古い run を整理して。消すものと archive するものを先に見せて |
|
|
46
|
+
| 作り直した旧系列を片付ける | 旧系列は新系列で置き換えたので、対応を記録して archive して |
|
|
46
47
|
| simulator を足す | パッチを当てた ~/BEACH-dev をビルドして、別の simulator として登録して |
|
|
47
48
|
| 更新する | runops と simulator を最新にして |
|
|
48
49
|
| v1 から移る | この project を runops v2 に移行して(`runo migrate-v1` を dry-run で確認してから) |
|
|
49
50
|
|
|
50
51
|
投入、削除、整理、移行の前には、エージェントが対象と資源量を示して確認を取ります。
|
|
52
|
+
範囲を言わない依頼(「全部再投入して」など)は、エージェントを起動した project の run だけが対象です。
|
|
51
53
|
場面ごとの流れは [使い方ガイド](https://github.com/Nkzono99/runops/blob/main/docs/guide.md) にあります。
|
|
52
54
|
|
|
53
55
|
## project の中身
|
|
@@ -325,6 +325,7 @@ scratch = false
|
|
|
325
325
|
expires_at = "" # scratch の場合のみ。ISO 8601
|
|
326
326
|
pinned = false
|
|
327
327
|
tags = ["sheath", "2d"]
|
|
328
|
+
superseded_by = [] # archive --superseded-by で記録した置き換え先の run id
|
|
328
329
|
|
|
329
330
|
[path]
|
|
330
331
|
run_dir = "runs/flat_surface/R20261015-0003--wc0147_phi45" # project root 相対。絶対 path は書かない
|
|
@@ -378,6 +379,7 @@ slurm_state = "COMPLETED"
|
|
|
378
379
|
exit_code = 0 # exit record から
|
|
379
380
|
exe_hash = "sha256:..." # submit 時点の executable の hash
|
|
380
381
|
elapsed = "15:42:10" # sacct の Elapsed、無ければ exit record の開始・終了から
|
|
382
|
+
cancel_reason = "" # cancel --why の理由。retry はこれがあれば確認を取る
|
|
381
383
|
|
|
382
384
|
[params]
|
|
383
385
|
"tmgrid.nx" = 4000
|
|
@@ -461,6 +463,9 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
461
463
|
見つからなければ exit code 2 でエラー。
|
|
462
464
|
- `RUN` 引数は run id、run directory path、または directory 名の一意な部分文字列を受け付ける。
|
|
463
465
|
複数一致は exit code 2 でエラーにし候補を表示する。
|
|
466
|
+
- path で与える `RUN` と `DIR`(`submit --all`、`list`、`archive`)は、この project の `runs/`
|
|
467
|
+
配下に限る。別 project の path は exit code 2 で拒否する。run id は project ごとの空間なので、
|
|
468
|
+
同じ id が別 project にあっても cwd の project の run を指す。
|
|
464
469
|
- 一覧系 command は `--json` を持ち、agent が機械可読に読める。`new` と `sweep --apply` の
|
|
465
470
|
`--json` は作成した run の同じ summary を返す。summary には `params`、`changed_keys`、
|
|
466
471
|
`parent_run`、`exe_hash`、`input_hash` を含め、run 同士の比較に別 command を要らなくする。
|
|
@@ -494,8 +499,8 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
494
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` と同時のみ有効 |
|
|
495
500
|
| `runo submit RUN... [--partition P] [--qos Q] [--afterok JOBID] [-y]` | job.sh を生成して sbatch。`created` 以外は拒否 |
|
|
496
501
|
| `runo submit --all [DIR] [-y]` | DIR 配下の `created` run をすべて投入。件数と概算 core-hours を表示して確認 |
|
|
497
|
-
| `runo cancel RUN... [-y]` | scancel して sync |
|
|
498
|
-
| `runo retry RUN [--submit]` | `failed`/`cancelled` を `created`
|
|
502
|
+
| `runo cancel RUN... [--why TEXT] [-y]` | scancel して sync。`--why` は `cancelled: ...` の note として残す |
|
|
503
|
+
| `runo retry RUN [--submit] [-y]` | `failed`/`cancelled` を `created` に戻す。直近の cancel に `--why` があれば理由を示して確認を取る |
|
|
499
504
|
| `runo status [RUN...] [--json]` | manifest の状態を表示。Slurm を呼ばない |
|
|
500
505
|
| `runo sync [RUN...] [--json]` | squeue/sacct で状態を更新。引数なしは `submitted`/`running` 全件 |
|
|
501
506
|
| `runo log RUN [--err] [-n N] [-f]` | 最新 job の stdout/stderr を表示。`-f` は tail -f |
|
|
@@ -504,7 +509,7 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
504
509
|
| `runo pin RUN [--off] [--reason TEXT]` | `run.pinned` を切り替える。pinned は `list` に常に出て、`tidy` / `delete` の対象外 |
|
|
505
510
|
| `runo summarize RUN... [--force]` | adapter の `summarize()` を呼び `analysis/summary.json` を書く。既存があれば `--force` なしではスキップ |
|
|
506
511
|
| `runo mv RUN... [--dest DIR] [--label L] [--why TEXT]` | run を `runs/` 配下で改名・移動する。id、入力、履歴は変えない(§7.7) |
|
|
507
|
-
| `runo archive RUN... [-y]` / `runo archive --older-than 90d [-y]` | `runs/_archive/<元の相対 path>` に移動して `archived`
|
|
512
|
+
| `runo archive RUN\|DIR... [--superseded-by RUN\|DIR --why TEXT] [-y]` / `runo archive --older-than 90d [-y]` | `runs/_archive/<元の相対 path>` に移動して `archived` にする。移動前に全対象を検査し、指定した RUN が終端でなければ何も動かさない。DIR は配下の run(系列)を指し、終端でない run と pinned を除いて表示する。`--superseded-by` は各 run に置き換え先(`parent_run` がその run の置き換え先 run、無ければ指定した全 run)を `run.superseded_by` に記録し、`--why` を note に残す |
|
|
508
513
|
| `runo restore RUN` | archive 前の場所に戻す。既に何かあれば拒否 |
|
|
509
514
|
| `runo delete RUN... [-y]` | directory を削除。`created`/`failed`/`cancelled`/scratch のみ。`pinned` は拒否 |
|
|
510
515
|
| `runo tidy [--apply] [--only KIND] [--older-than AGE] [--purge-work RUN...] [--json]` | 整理候補を表示する(§7.5)。`--apply` で実行 |
|
|
@@ -568,6 +573,9 @@ created ──submit──▶ submitted ──(slurm)──▶ running ──▶
|
|
|
568
573
|
- 対象は `created`、`completed`、`failed`、`cancelled`。`submitted` / `running` は拒否する
|
|
569
574
|
(Slurm が run directory の絶対 path を持つ)。`archived` は `restore` を使う。
|
|
570
575
|
- 既存 path は上書きしない(`move_noreplace`)。`created` の run は job.sh を作り直す。
|
|
576
|
+
- 複数 run は `run.lock` を 1 回取り、全対象の状態と移動先を検査してから移す。1 件でも拒否されれば
|
|
577
|
+
どれも動かさない。移動中に filesystem の error が起きたら止まり、移し終えた run id を error に示す
|
|
578
|
+
(exit code 1)。`archive RUN...` も同じ手順で移す。
|
|
571
579
|
- 手で `mv` しても run は id で見つかるが、`run.label` と `[path].run_dir` が古いまま残る。
|
|
572
580
|
|
|
573
581
|
---
|
|
@@ -740,10 +748,12 @@ seal、index を持たない)。
|
|
|
740
748
|
|
|
741
749
|
`init` が生成する agent 向けファイルは次だけ。
|
|
742
750
|
|
|
743
|
-
- `AGENTS.md`: 120 行以内。project 名、simulator、§7.3 の command
|
|
744
|
-
§
|
|
745
|
-
|
|
746
|
-
|
|
751
|
+
- `AGENTS.md`: 120 行以内。project 名、simulator、§7.3 の command 表、作業の進め方、
|
|
752
|
+
§9 のノート規約、展開済み skill の一覧。作業の進め方は次の 5 項目で、それぞれ「何をするか」と
|
|
753
|
+
短い理由で書く(禁止事項の列挙にしない)。依頼が操作の範囲を決める、submit 前に対象と資源量を
|
|
754
|
+
示して確認を取る、操作対象は起動した project の run と job、run の変更は `runo` を通す
|
|
755
|
+
(variation は case ではなく run、§5)、理由を残す(`--why`、`cancel --why`、`--superseded-by`)。
|
|
756
|
+
- skill は手順を書き、AGENTS.md の方針を繰り返さない。
|
|
747
757
|
- harness file は project root にだけ置く。`runs/` や `cases/` 配下には生成しない。
|
|
748
758
|
- `CLAUDE.md`: `@AGENTS.md` の 1 行。
|
|
749
759
|
- `.agents/skills/`、`.claude/skills/`: §8.2、§8.3 の展開結果。
|
|
@@ -957,4 +967,8 @@ v2 の文書は次の 4 ファイルだけにする。
|
|
|
957
967
|
| 整理と命名のしきい値 | `[defaults]` に `tidy_failed_days`、`tidy_archive_days`、`label_max_length` を追加。completed の目安は「`--older-than` の 3 倍」をやめて独立の値に | project ごとに計算期間や命名が違い、エージェントに毎回 option を渡させると揃わない |
|
|
958
968
|
| `runo init` の対話と site の推定 | 端末から引数なしで実行したときだけ site と simulator を尋ねる。site の既定はホスト名(`camphor*`、`laurel*`、`cinnamon*`、`gardenia*` なら `kudpc`)。プリセット名は `kudpc` とし、`camphor` も同じ意味で受け付ける | 人が打つのは `runo init` だけなので、ここで KUDPC 向けの設定を取り違えないようにする。エージェントや CI からの実行(端末でない、`-y`、引数あり)は従来どおり尋ねない |
|
|
959
969
|
| 異常系と同時操作(2026-10-20) | `run.lock` 下で manifest を読み直す、準備の失敗で program を起動しない、adapter を読めなければ purge しない、build cache の key を内容と `build_command` から作る、run の入れ子を禁止、run id を再利用しない(`last_run_id`)、`new --from` で case の変更を警告 | 外部レビューで、二重投入で 1 回目の履歴が消える、準備失敗が completed になる、adapter の故障で保持ファイルが消える、未追跡ファイルの変更で古い binary を使う、削除した id が別の run を指す、を確認した |
|
|
970
|
+
| 操作範囲は起動 project(2026-10-21) | AGENTS.md block と skill に「対象は起動した project の run と job id だけ。アカウント全体の squeue / sacct は診断に見るだけ。自分で別 directory に移らず、生の `sbatch` / `scancel` を使わない」を書く。CLI は別 project の path を拒否する | 範囲を言わない「再投入して」で、エージェントが別 project や runops 管理外のジョブまで再投入した。cwd で project を決める既存の model に、path 引数の抜け道(`submit --all /other/runs` など)があった。他 session が修正のために止めた run を見分けられるよう、`cancel --why` を note に残し、`retry` はその理由を示して確認を取る(#112) |
|
|
971
|
+
| 置き換えた系列の整理(2026-10-21) | `archive` が DIR(系列)を受け、`--superseded-by` で置き換え先を manifest に記録する。新しい command と tidy の種別は足さない。skill は新系列を作ったら旧系列の要否を確かめ、不要なら archive して現用系列・archive 先・件数を報告する | 修正版を作った後に旧系列が作業領域に残り、毎回ユーザーが整理を頼んでいた。`parent_run` だけでは比較用の派生と置き換えを区別できないので、置き換えは明示操作で記録する。旧 `archive` は 1 件ずつ検査していて、途中の running run で一部だけ移動していた(#113) |
|
|
972
|
+
| 生成ハーネスの書き方(2026-10-21) | AGENTS.md の Rules を「作業の進め方」5 項目に絞り、各項目を「何をするか」と短い理由で書く。skill は手順に専念し、AGENTS.md の方針(manifest を手で編集しない等)を繰り返さない | #112・#113 で規則が増え、禁止形(never / do not)が並んでいた。現行の Claude の prompting 指針は、しないことより何をするかを、理由と共に書くことを勧め、強い禁止語は過剰反応を招くとしている |
|
|
973
|
+
| 複数 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 で同じことが起き得た |
|
|
960
974
|
| 使い方ガイド | `docs/guide.md` を追加し、文書を 4 ファイルにする(§13.3) | README の依頼例の表と command 一覧の間を埋める、場面ごとの流れ(case と run の分け方、smoke から本計算、状態の決まり方、整理)の置き場所が無かった |
|
|
@@ -160,11 +160,18 @@ runops が知らない Slurm 状態の job は状態を変えず、`last_slurm_s
|
|
|
160
160
|
| 原因を調べる | R20261016-0003 が落ちた理由を調べて | `runo log RUN --err`、`runo summarize RUN`、`runo note RUN "..."` |
|
|
161
161
|
| 一時的な障害で、同じ入力で流し直す | 同じ入力でもう一度投げて | `runo retry RUN`、`runo submit RUN` |
|
|
162
162
|
| 入力を直して流し直す | dt を半分にして作り直して | `runo new --from RUN --set ... --why "..."` |
|
|
163
|
-
| 止める | R20261016-0004 を止めて | `runo cancel RUN
|
|
163
|
+
| 止める | R20261016-0004 を止めて | `runo cancel RUN`(直すために止めるときは `--why`) |
|
|
164
164
|
|
|
165
165
|
`retry` は `failed` / `cancelled` の run を `created` に戻し、試行回数を 1 つ増やします。
|
|
166
166
|
`work/` は消しません。
|
|
167
167
|
|
|
168
|
+
「消えたジョブを再投入して」「全部止めて」のように範囲を言わない依頼は、エージェントを起動した
|
|
169
|
+
project の run だけが対象です。同じアカウントの別 project のジョブや runops 外のジョブは、
|
|
170
|
+
`squeue` に見えていても対象にせず、該当する run が無ければそう伝えます。別 project の run も
|
|
171
|
+
扱いたいときは、その project か run を名指ししてください。
|
|
172
|
+
入力を直すために止めた run は `runo cancel RUN --why "..."` で理由を残すと、別のセッションが
|
|
173
|
+
「消えたジョブ」として再投入しません(`retry` が理由を示して確認を取ります)。
|
|
174
|
+
|
|
168
175
|
## 5. 続きを流す
|
|
169
176
|
|
|
170
177
|
> R20261015-0003 の続きを 2 倍の step で。
|
|
@@ -211,6 +218,18 @@ run は消さなくても `runo list` が古いものを隠しますが、direct
|
|
|
211
218
|
候補を確認したら `runo tidy --apply` で実行します。archive は `runs/_archive/` への移動で、
|
|
212
219
|
run id で引き続き見つかり、`runo restore` で戻せます。
|
|
213
220
|
|
|
221
|
+
### 置き換えた系列を片付ける
|
|
222
|
+
|
|
223
|
+
不備を直して新しい系列を作り直すと、エージェントは旧系列がまだ要るかを尋ねます。要らなければ
|
|
224
|
+
|
|
225
|
+
> 旧系列は不要。新系列で置き換えたと記録して archive して。
|
|
226
|
+
|
|
227
|
+
で `runo archive runs/<case>/<旧系列> --superseded-by runs/<case>/<新系列> --why "..."` になります。
|
|
228
|
+
系列の directory を渡すと、対象の run を先に表示し、投入中・実行中・作成のみの run と pin した run は
|
|
229
|
+
除きます。各旧 run の `superseded_by` には、その run から `--from` で作った新 run(無ければ新系列の全 run)が
|
|
230
|
+
記録され、理由はノートに残ります。比較用に派生しただけの run は置き換えとは扱わないので、
|
|
231
|
+
旧系列が不要かどうかは毎回確かめます。
|
|
232
|
+
|
|
214
233
|
論文の図に使う run や基準にする run は pin しておくと、`list` に常に表示され、`tidy` や `delete`
|
|
215
234
|
の対象になりません。
|
|
216
235
|
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
## runops.toml
|
|
7
7
|
|
|
8
8
|
project root にあり、`runo` はここを上方向に探して project を特定します。
|
|
9
|
+
command はこの project の `runs/` 配下だけを扱い、別 project の run directory や
|
|
10
|
+
directory を path で渡すとエラーになります。run id は project ごとなので、同じ id が
|
|
11
|
+
別 project にあっても cwd の project の run を指します。
|
|
9
12
|
|
|
10
13
|
```toml
|
|
11
14
|
schema = 2
|
|
@@ -103,11 +106,11 @@ runops だけが書きます。手で編集しないでください。主な fie
|
|
|
103
106
|
|
|
104
107
|
| section | field |
|
|
105
108
|
|---|---|
|
|
106
|
-
| `[run]` | `id`、`label`、`status`、`created_at`、`why`、`scratch`、`expires_at`、`pinned`、`tags`、`status_before_archive
|
|
109
|
+
| `[run]` | `id`、`label`、`status`、`created_at`、`why`、`scratch`、`expires_at`、`pinned`、`tags`、`status_before_archive`、`superseded_by`(置き換え先の run id) |
|
|
107
110
|
| `[path]` | `run_dir`(project 相対)、`archived_from`、`archived_at` |
|
|
108
111
|
| `[origin]` | `case`、`sweep`、`parent_run`、`created_by`、`case_input_hash` |
|
|
109
112
|
| `[simulator]` | `name`、`adapter`、`adapter_source`、`adapter_version`、`executable`、`resolver_mode`、`package_version`、`exe_hash`、`git_commit`、`git_dirty`、`build_dir` |
|
|
110
|
-
| `[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`) |
|
|
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`) |
|
|
111
114
|
| `[params]` | case の params に `--set` を適用した最終値 |
|
|
112
115
|
| `[variation]` | `changed_keys` |
|
|
113
116
|
| `[identity]` | `input_hash` |
|
|
@@ -154,13 +157,13 @@ run id は削除後も再利用しません。`--dest` に既存 run の中は
|
|
|
154
157
|
| `runo new --from RUN [--set k=v]... [--continue]` | 既存 run の params から派生。`--continue` は adapter の restart 対応が必要 |
|
|
155
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 の親 |
|
|
156
159
|
| `runo submit RUN... [-p P] [--qos Q] [--afterok JOB]` / `runo submit --all [DIR] [-y]` | job.sh を生成して sbatch |
|
|
157
|
-
| `runo cancel RUN... [-y]` / `runo retry RUN [--submit]` | scancel
|
|
160
|
+
| `runo cancel RUN... [--why TEXT] [-y]` / `runo retry RUN [--submit] [-y]` | scancel(`--why` は note に残る)/ failed・cancelled を created に戻す(理由付きで cancel された run は確認を取る) |
|
|
158
161
|
| `runo status [RUN...]` / `runo sync [RUN...]` / `runo log RUN [--err] [-n N] [-f]` | 状態表示 / Slurm から更新 / log 表示 |
|
|
159
162
|
| `runo list [PATH] [--all] [--since 30d] [--case] [--sweep] [--status] [--tag] [--scratch] [--archived] [--json]` | run 一覧 |
|
|
160
163
|
| `runo note RUN [TEXT]` / `runo pin RUN [--off] [--reason]` | ノート追記・表示 / pin |
|
|
161
164
|
| `runo summarize RUN... [--force]` | adapter の要約を `analysis/summary.json` に書く |
|
|
162
|
-
| `runo mv RUN... [--dest DIR] [--label L] [--why TEXT]` | `runs/` 配下で改名・移動。id、入力、sweep、ノートは保つ。submitted / running と archived
|
|
163
|
-
| `runo archive RUN... ` / `runo archive --older-than 90d` / `runo restore RUN` | `runs/_archive/` へ移動 /
|
|
165
|
+
| `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/` へ移動 / 戻す。移動前に全対象を検査し、指定した RUN が終了していなければ何も動かさない。DIR は配下の run(系列)で、created / submitted / running と pinned を除いて表示する。`--superseded-by` は置き換え先(その run から `--from` で派生した run、無ければ指定した全 run)を `superseded_by` に記録し、`--why` をノートに残す |
|
|
164
167
|
| `runo delete RUN... [-y]` | created・failed・cancelled・scratch の run を削除 |
|
|
165
168
|
| `runo tidy [--apply] [--only KIND] [--older-than AGE] [--purge-work RUN...]` | 整理候補の表示と実行 |
|
|
166
169
|
| `runo migrate-v1 [--dry-run] [--apply-delete-test-runs] [-y]` | v1 project の変換 |
|
|
@@ -488,7 +488,7 @@ def submit_cmd(
|
|
|
488
488
|
if all_runs:
|
|
489
489
|
if runs and len(runs) > 1:
|
|
490
490
|
raise UsageError("--all takes at most one DIR")
|
|
491
|
-
under =
|
|
491
|
+
under = project.runs_subdir(runs[0]) if runs else project.runs_dir
|
|
492
492
|
targets = [
|
|
493
493
|
m
|
|
494
494
|
for m in project.iter_runs(under)
|
|
@@ -524,6 +524,9 @@ def submit_cmd(
|
|
|
524
524
|
@app.command()
|
|
525
525
|
def cancel(
|
|
526
526
|
runs: list[str] = typer.Argument(...),
|
|
527
|
+
why: str = typer.Option(
|
|
528
|
+
"", "--why", help="Reason, recorded as a note (e.g. 'wrong input; replaced by ...')."
|
|
529
|
+
),
|
|
527
530
|
yes: bool = typer.Option(False, "-y", "--yes"),
|
|
528
531
|
) -> None:
|
|
529
532
|
"""scancel submitted/running runs."""
|
|
@@ -533,7 +536,7 @@ def cancel(
|
|
|
533
536
|
targets = _resolve(project, runs)
|
|
534
537
|
_confirm("Cancel " + ", ".join(f"{m.id} (job {m.job_id})" for m in targets) + "?", yes)
|
|
535
538
|
for m in targets:
|
|
536
|
-
cancel_run(project, m, runner=_runner())
|
|
539
|
+
cancel_run(project, m, runner=_runner(), why=why, by=_actor())
|
|
537
540
|
out.print(f"cancelled {m.id}")
|
|
538
541
|
|
|
539
542
|
|
|
@@ -541,12 +544,17 @@ def cancel(
|
|
|
541
544
|
def retry(
|
|
542
545
|
run: str = typer.Argument(...),
|
|
543
546
|
submit: bool = typer.Option(False, "--submit"),
|
|
547
|
+
yes: bool = typer.Option(False, "-y", "--yes"),
|
|
544
548
|
) -> None:
|
|
545
549
|
"""Reset a failed/cancelled run to created (work/ is kept)."""
|
|
546
|
-
from runops.ops.submit import retry_run
|
|
550
|
+
from runops.ops.submit import cancel_reason, retry_run
|
|
547
551
|
|
|
548
552
|
project = _project()
|
|
549
553
|
m = project.resolve_run(run)
|
|
554
|
+
reason = cancel_reason(m)
|
|
555
|
+
if reason:
|
|
556
|
+
# Someone cancelled it on purpose (e.g. to fix and replace it); do not revive it silently.
|
|
557
|
+
_confirm(f"{m.id} was cancelled: {reason}. Retry anyway?", yes)
|
|
550
558
|
retry_run(project, m)
|
|
551
559
|
out.print(f"{m.id} -> created")
|
|
552
560
|
if submit:
|
|
@@ -687,7 +695,7 @@ def list_cmd(
|
|
|
687
695
|
if s not in {state.value for state in RunState}:
|
|
688
696
|
raise UsageError(f"unknown status {s!r}")
|
|
689
697
|
flt = ListFilter(
|
|
690
|
-
path=
|
|
698
|
+
path=project.runs_subdir(path) if path else None,
|
|
691
699
|
show_all=show_all,
|
|
692
700
|
since=parse_age(since) if since else None,
|
|
693
701
|
cases=cases or [],
|
|
@@ -771,40 +779,59 @@ def summarize(
|
|
|
771
779
|
|
|
772
780
|
@app.command()
|
|
773
781
|
def archive(
|
|
774
|
-
runs: Optional[list[str]] = typer.Argument(
|
|
782
|
+
runs: Optional[list[str]] = typer.Argument(
|
|
783
|
+
None, help="Runs, or directories under runs/ (a series)."
|
|
784
|
+
),
|
|
775
785
|
older_than: str = typer.Option(
|
|
776
786
|
"", "--older-than", help="Archive completed runs older than this (e.g. 90d)."
|
|
777
787
|
),
|
|
788
|
+
superseded_by: str = typer.Option(
|
|
789
|
+
"",
|
|
790
|
+
"--superseded-by",
|
|
791
|
+
help="Run or series directory that replaces these runs; recorded in each manifest.",
|
|
792
|
+
),
|
|
793
|
+
why: str = typer.Option("", "--why", help="Reason, recorded in each run's notes."),
|
|
778
794
|
yes: bool = typer.Option(False, "-y", "--yes"),
|
|
779
795
|
) -> None:
|
|
780
796
|
"""Move terminal runs to runs/_archive/ (same relative path)."""
|
|
781
797
|
from datetime import datetime
|
|
782
798
|
|
|
783
|
-
from runops.ops.tidy import
|
|
799
|
+
from runops.ops.tidy import ArchivePlan, archive_runs, plan_archive
|
|
784
800
|
|
|
785
801
|
project = _project()
|
|
802
|
+
if superseded_by and not why:
|
|
803
|
+
raise UsageError("--superseded-by needs --why")
|
|
786
804
|
if older_than:
|
|
787
|
-
if runs:
|
|
805
|
+
if runs or superseded_by:
|
|
788
806
|
raise UsageError("give RUNs or --older-than, not both")
|
|
789
807
|
cutoff = datetime.now().astimezone() - parse_age(older_than)
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
808
|
+
plan = ArchivePlan(
|
|
809
|
+
targets=[
|
|
810
|
+
m
|
|
811
|
+
for m in project.iter_runs()
|
|
812
|
+
if m.status == RunState.COMPLETED
|
|
813
|
+
and not m.pinned
|
|
814
|
+
and (m.created_datetime() or cutoff) < cutoff
|
|
815
|
+
]
|
|
816
|
+
)
|
|
797
817
|
else:
|
|
798
|
-
|
|
799
|
-
|
|
818
|
+
plan = plan_archive(project, runs or [], superseded_by=superseded_by)
|
|
819
|
+
for m, reason in plan.skipped:
|
|
820
|
+
out.print(f" skip {m.id} {reason} {project.rel(m.run_dir)}")
|
|
821
|
+
if not plan.targets:
|
|
800
822
|
out.print("nothing to archive")
|
|
801
823
|
return
|
|
802
|
-
for m in targets:
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
824
|
+
for m in plan.targets:
|
|
825
|
+
line = f" {m.id} {m.status.value} {project.rel(m.run_dir)}"
|
|
826
|
+
if m.id in plan.superseded_by:
|
|
827
|
+
line += f" -> {', '.join(plan.superseded_by[m.id])}"
|
|
828
|
+
out.print(line)
|
|
829
|
+
_confirm(f"Archive {len(plan.targets)} runs?", yes)
|
|
830
|
+
archived = archive_runs(
|
|
831
|
+
project, plan.targets, superseded_by=plan.superseded_by, why=why, by=_actor()
|
|
832
|
+
)
|
|
833
|
+
for run_id, dst in archived:
|
|
834
|
+
out.print(f"archived {run_id} -> {project.rel(dst)}")
|
|
808
835
|
|
|
809
836
|
|
|
810
837
|
@app.command("mv")
|
|
@@ -817,7 +844,7 @@ def mv_cmd(
|
|
|
817
844
|
why: str = typer.Option("", "--why", help="Reason, recorded in the run's notes."),
|
|
818
845
|
) -> None:
|
|
819
846
|
"""Rename or move runs under runs/, keeping their ids, inputs and history."""
|
|
820
|
-
from runops.ops.tidy import
|
|
847
|
+
from runops.ops.tidy import move_runs
|
|
821
848
|
|
|
822
849
|
if not dest and label is None:
|
|
823
850
|
raise UsageError("give --dest and/or --label")
|
|
@@ -825,12 +852,10 @@ def mv_cmd(
|
|
|
825
852
|
targets = _resolve(project, runs)
|
|
826
853
|
if label is not None and len(targets) > 1:
|
|
827
854
|
raise UsageError("--label applies to one RUN; use --dest to move several")
|
|
828
|
-
|
|
829
|
-
for
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
out.print(f"moved {m.id}: {project.rel(result.src)} -> {project.rel(result.dst)}")
|
|
833
|
-
_warn(warnings)
|
|
855
|
+
results = move_runs(project, targets, dest=dest, label=label, why=why, by=_actor())
|
|
856
|
+
for r in results:
|
|
857
|
+
out.print(f"moved {r.run_id}: {project.rel(r.src)} -> {project.rel(r.dst)}")
|
|
858
|
+
_warn([w for r in results for w in r.warnings])
|
|
834
859
|
|
|
835
860
|
|
|
836
861
|
@app.command()
|
|
@@ -20,12 +20,13 @@ COMMANDS = """\
|
|
|
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 |
|
|
22
22
|
| `runo list [--all] [--case C] [--status S] [--json]` | recent + active runs (old, scratch, archived hidden) |
|
|
23
|
-
| `runo cancel RUN` / `runo retry RUN` | stop a job / reset failed or cancelled to created |
|
|
23
|
+
| `runo cancel RUN [--why T]` / `runo retry RUN` | stop a job / reset failed or cancelled to created |
|
|
24
24
|
| `runo note RUN "text"` / `runo pin RUN --reason TEXT` | append an observation / protect a key run |
|
|
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
|
|
28
|
+
| `runo archive RUN\\|DIR... [--superseded-by RUN\\|DIR --why T]` | move finished runs (or a series) to runs/_archive |
|
|
29
|
+
| `runo restore RUN` / `runo delete RUN` | move back / remove |
|
|
29
30
|
| `runo build SIM` | build a local_source simulator into .runops/build |
|
|
30
31
|
| `runo doctor` / `runo update` | check environment / upgrade simulator packages and skills |"""
|
|
31
32
|
|
|
@@ -42,8 +43,9 @@ def render_block(project: Project, skills: list[str]) -> str:
|
|
|
42
43
|
return f"""{BEGIN}
|
|
43
44
|
# {project.config.name}
|
|
44
45
|
|
|
45
|
-
This is a runops project
|
|
46
|
-
|
|
46
|
+
This is a runops project. Each run is a directory under `runs/` with a `manifest.toml`
|
|
47
|
+
that runops owns. The `runo` CLI creates, submits, tracks and tidies runs, and acts on
|
|
48
|
+
the project found from the current directory.
|
|
47
49
|
|
|
48
50
|
## Simulators
|
|
49
51
|
|
|
@@ -53,30 +55,36 @@ This is a runops project: simulation runs are tracked as directories under
|
|
|
53
55
|
|
|
54
56
|
{COMMANDS}
|
|
55
57
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
submits; "try/test"
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
## How to work here
|
|
59
|
+
|
|
60
|
+
- **Let the request set the actions.** "make/create" means creating runs; "run/submit"
|
|
61
|
+
also submits; "try/test" means a short `--scratch` run that you may submit. Earlier
|
|
62
|
+
runs can supply parameters, while the request decides what happens.
|
|
63
|
+
- **Confirm before compute is spent.** Before any submit, show the runs, partition,
|
|
64
|
+
resources and core-hours, and wait for the go-ahead. Once Slurm accepts the jobs,
|
|
65
|
+
report the job ids and end the turn; Slurm does the waiting. When asked to watch,
|
|
66
|
+
check once early and once near the expected end.
|
|
67
|
+
- **Work within this project.** Requests such as "resubmit everything" mean the runs
|
|
68
|
+
under `runs/` here: find them with `runo sync` and `runo list`. Account-wide `squeue`
|
|
69
|
+
and `sacct` help diagnose, but other projects and unmanaged jobs belong to other
|
|
70
|
+
sessions, so act on them only when the user names them. If nothing here matches, say so.
|
|
71
|
+
- **Change runs through `runo`.** `runo` writes each run's manifest and `input/`, so
|
|
72
|
+
they stay an exact record of what ran.
|
|
73
|
+
Express a change as a new run (`runo new --from RUN --set k=v`, `runo sweep`) and
|
|
74
|
+
rename or regroup with `runo mv`. Variations are runs; a new case is for a different
|
|
75
|
+
structure of the base input.
|
|
76
|
+
- **Leave the reason behind.** Give each new run `--why`. When you cancel runs to fix or
|
|
77
|
+
replace them, add `runo cancel --why`, so later sessions and `runo retry` leave them
|
|
78
|
+
stopped. When a new series replaces an old one, ask whether the old one is still
|
|
79
|
+
needed and archive it with `--superseded-by`.
|
|
72
80
|
|
|
73
81
|
## Notes
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
Notes live in three places, where `runo context` and the next session find them:
|
|
84
|
+
- one run: `runo note RUN "..."`;
|
|
85
|
+
- conclusions across runs and next decisions: `LOG.md`, appended under a dated heading;
|
|
86
|
+
- reusable knowledge (formulas, methods, pitfalls): `notes/<topic>.md`, extending the
|
|
87
|
+
existing file for a topic.
|
|
80
88
|
|
|
81
89
|
## Skills
|
|
82
90
|
|
|
@@ -133,6 +133,14 @@ class Manifest:
|
|
|
133
133
|
def sweep(self) -> str:
|
|
134
134
|
return str(self.section("origin").get("sweep", ""))
|
|
135
135
|
|
|
136
|
+
@property
|
|
137
|
+
def parent_run(self) -> str:
|
|
138
|
+
return str(self.section("origin").get("parent_run", ""))
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def superseded_by(self) -> list[str]:
|
|
142
|
+
return [str(r) for r in self.run.get("superseded_by", [])]
|
|
143
|
+
|
|
136
144
|
@property
|
|
137
145
|
def params(self) -> dict[str, Any]:
|
|
138
146
|
return dict(self.section("params"))
|
|
@@ -190,7 +198,8 @@ class Manifest:
|
|
|
190
198
|
# Enough for an agent to compare runs without another command.
|
|
191
199
|
"params": self.params,
|
|
192
200
|
"changed_keys": list(self.section("variation").get("changed_keys", [])),
|
|
193
|
-
"parent_run":
|
|
201
|
+
"parent_run": self.parent_run,
|
|
202
|
+
"superseded_by": self.superseded_by,
|
|
194
203
|
"exe_hash": str(self.section("simulator").get("exe_hash", "")),
|
|
195
204
|
"input_hash": str(self.section("identity").get("input_hash", "")),
|
|
196
205
|
# Walltime evidence for the next run: elapsed of the latest attempt.
|
|
@@ -172,7 +172,12 @@ def _current_exe_hash(manifest: Manifest) -> str:
|
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
def cancel_run(
|
|
175
|
-
project: Project,
|
|
175
|
+
project: Project,
|
|
176
|
+
manifest: Manifest,
|
|
177
|
+
*,
|
|
178
|
+
runner: CommandRunner = default_runner,
|
|
179
|
+
why: str = "",
|
|
180
|
+
by: str = "",
|
|
176
181
|
) -> None:
|
|
177
182
|
with project.run_lock():
|
|
178
183
|
manifest.reload()
|
|
@@ -187,10 +192,22 @@ def cancel_run(
|
|
|
187
192
|
if attempts:
|
|
188
193
|
attempts[-1]["finished_at"] = now
|
|
189
194
|
attempts[-1]["slurm_state"] = "CANCELLED"
|
|
195
|
+
if why:
|
|
196
|
+
(attempts[-1] if attempts else manifest.job)["cancel_reason"] = why
|
|
197
|
+
manifest.add_note(f"cancelled: {why}", by or "runops")
|
|
190
198
|
manifest.set_status(RunState.CANCELLED)
|
|
191
199
|
manifest.save()
|
|
192
200
|
|
|
193
201
|
|
|
202
|
+
def cancel_reason(manifest: Manifest) -> str:
|
|
203
|
+
"""The ``cancel --why`` reason of a cancelled run, if its latest cancel gave one."""
|
|
204
|
+
if manifest.status != RunState.CANCELLED:
|
|
205
|
+
return ""
|
|
206
|
+
attempts = manifest.job.get("attempts") or []
|
|
207
|
+
record = attempts[-1] if attempts else manifest.job
|
|
208
|
+
return str(record.get("cancel_reason", ""))
|
|
209
|
+
|
|
210
|
+
|
|
194
211
|
def retry_run(project: Project, manifest: Manifest) -> None:
|
|
195
212
|
with project.run_lock():
|
|
196
213
|
manifest.reload()
|