outerloop-science 0.1.0.dev0__py3-none-any.whl → 0.1.0.dev2__py3-none-any.whl
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.
- outerloop/__init__.py +7 -7
- outerloop/appauth.py +3 -3
- outerloop/appmanifest.py +16 -11
- outerloop/attempt.py +80 -52
- outerloop/cli.py +170 -86
- outerloop/climbboard.py +209 -4
- outerloop/compute.py +84 -7
- outerloop/contract.py +3 -2
- outerloop/followup.py +37 -23
- outerloop/github.py +4 -4
- outerloop/harness.py +18 -7
- outerloop/image.py +372 -0
- outerloop/init.py +417 -29
- outerloop/limits.py +1 -1
- outerloop/measure.py +2 -2
- outerloop/orchestrator.py +1 -1
- outerloop/paths.py +14 -1
- outerloop/review.py +1 -1
- outerloop/steward.py +3 -3
- outerloop/syscall.py +1 -0
- outerloop/tick.py +400 -105
- {outerloop_science-0.1.0.dev0.dist-info → outerloop_science-0.1.0.dev2.dist-info}/METADATA +14 -10
- {outerloop_science-0.1.0.dev0.dist-info → outerloop_science-0.1.0.dev2.dist-info}/RECORD +27 -26
- {outerloop_science-0.1.0.dev0.dist-info → outerloop_science-0.1.0.dev2.dist-info}/WHEEL +0 -0
- {outerloop_science-0.1.0.dev0.dist-info → outerloop_science-0.1.0.dev2.dist-info}/entry_points.txt +0 -0
- {outerloop_science-0.1.0.dev0.dist-info → outerloop_science-0.1.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {outerloop_science-0.1.0.dev0.dist-info → outerloop_science-0.1.0.dev2.dist-info}/licenses/NOTICE +0 -0
outerloop/__init__.py
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
__version__ = "0.1.0.
|
|
5
|
+
__version__ = "0.1.0.dev2"
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def _bridge_legacy_env() -> None:
|
|
9
|
-
"""Accept the
|
|
10
|
-
its `
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
"""Accept the pre-rename `AUTORESEARCH_*` names at the process boundary for
|
|
10
|
+
one release: copy each into its `OUTERLOOP_*` twin when the twin is unset,
|
|
11
|
+
so the kernel reads one name. The chain scripts do the same in shell.
|
|
12
|
+
Removed in the release after 0.1."""
|
|
13
13
|
for key, value in list(os.environ.items()):
|
|
14
|
-
if key.startswith("
|
|
15
|
-
os.environ.setdefault("
|
|
14
|
+
if key.startswith("AUTORESEARCH_"):
|
|
15
|
+
os.environ.setdefault("OUTERLOOP_" + key[len("AUTORESEARCH_") :], value)
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
_bridge_legacy_env()
|
outerloop/appauth.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`AppInstallationTokenProvider` satisfies the `TokenProvider` protocol used
|
|
4
4
|
throughout `github.py`. Role CLIs construct bot auth through
|
|
5
5
|
`resolve_bot_auth`, which selects this provider when an App config file is
|
|
6
|
-
given (`--github-app-file` / `
|
|
6
|
+
given (`--github-app-file` / `OUTERLOOP_GITHUB_APP_FILE`) and falls back
|
|
7
7
|
to the PAT file otherwise — the cutover flag, revertible by unsetting the
|
|
8
8
|
env. Each `token()` mints a short-lived JWT (RS256, signed by the App
|
|
9
9
|
private key), exchanges it for a ~1h installation token scoped to the
|
|
@@ -172,8 +172,8 @@ def signer_from_private_key(pem_path: Path) -> Signer:
|
|
|
172
172
|
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
173
173
|
except ImportError as exc: # pragma: no cover - exercised only without the extra
|
|
174
174
|
raise ImportError(
|
|
175
|
-
"GitHub App auth needs the
|
|
176
|
-
"install
|
|
175
|
+
"GitHub App auth needs the cryptography package "
|
|
176
|
+
"(pip install cryptography), a base dependency since 0.1.0.dev2"
|
|
177
177
|
) from exc
|
|
178
178
|
|
|
179
179
|
key = serialization.load_pem_private_key(pem_path.read_bytes(), password=None)
|
outerloop/appmanifest.py
CHANGED
|
@@ -14,7 +14,7 @@ and GitHub redirects back to that page with a one-time code the page displays.
|
|
|
14
14
|
The adopter pastes the code here; we exchange it for the app id + key, write
|
|
15
15
|
`github_app.<slug>.json` + the PEM (both 0600), help install the App, and capture
|
|
16
16
|
the installation id. The written files are what `resolve_bot_auth` reads via
|
|
17
|
-
`
|
|
17
|
+
`OUTERLOOP_GITHUB_APP_FILE`, the same path `outerloop start` uses.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
@@ -25,12 +25,14 @@ import os
|
|
|
25
25
|
import secrets
|
|
26
26
|
import time
|
|
27
27
|
import urllib.error
|
|
28
|
+
import urllib.parse
|
|
28
29
|
import urllib.request
|
|
29
30
|
from collections.abc import Callable
|
|
30
31
|
from pathlib import Path
|
|
31
32
|
from typing import Any
|
|
32
33
|
|
|
33
34
|
from outerloop.appauth import API, build_app_jwt, signer_from_private_key
|
|
35
|
+
from outerloop.paths import write_private
|
|
34
36
|
|
|
35
37
|
# The hosted helper page: it auto-POSTs the manifest to GitHub, then displays the
|
|
36
38
|
# returned code. It is also the manifest's redirect_url, so GitHub sends the code
|
|
@@ -90,10 +92,15 @@ def request_manifest_code(
|
|
|
90
92
|
state = secrets.token_urlsafe(16)
|
|
91
93
|
manifest = build_manifest(name, homepage_url)
|
|
92
94
|
url = build_setup_url(manifest, state, org)
|
|
93
|
-
|
|
95
|
+
where = f"the {org} organization" if org else "your account"
|
|
96
|
+
print_fn("Step 1 of 3: create the GitHub App.")
|
|
97
|
+
print_fn(" Open this link in a browser where you are signed in to GitHub (any machine):")
|
|
94
98
|
print_fn(f"\n {url}\n")
|
|
95
|
-
print_fn("
|
|
96
|
-
|
|
99
|
+
print_fn(f" GitHub shows a page titled 'Create GitHub App' under {where}. Click the")
|
|
100
|
+
host = urllib.parse.urlsplit(SETUP_URL).netloc or SETUP_URL
|
|
101
|
+
print_fn(" green 'Create GitHub App' button. GitHub then sends you to")
|
|
102
|
+
print_fn(f" {host}, which shows a one-time code.")
|
|
103
|
+
return input_fn("Paste that code here: ").strip()
|
|
97
104
|
|
|
98
105
|
|
|
99
106
|
def _default_transport(request: urllib.request.Request) -> Any:
|
|
@@ -136,10 +143,10 @@ def save_app_creds(
|
|
|
136
143
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
137
144
|
slug = str(conversion["slug"])
|
|
138
145
|
pem_path = config_dir / f"{slug}-app.pem"
|
|
139
|
-
pem_path
|
|
140
|
-
pem_path.chmod(0o600)
|
|
146
|
+
write_private(pem_path, str(conversion["pem"]))
|
|
141
147
|
app_json = config_dir / f"github_app.{slug}.json"
|
|
142
|
-
|
|
148
|
+
write_private(
|
|
149
|
+
app_json,
|
|
143
150
|
json.dumps(
|
|
144
151
|
{
|
|
145
152
|
"app_id": int(conversion["id"]),
|
|
@@ -148,9 +155,8 @@ def save_app_creds(
|
|
|
148
155
|
},
|
|
149
156
|
indent=2,
|
|
150
157
|
)
|
|
151
|
-
+ "\n"
|
|
158
|
+
+ "\n",
|
|
152
159
|
)
|
|
153
|
-
app_json.chmod(0o600)
|
|
154
160
|
return pem_path, app_json
|
|
155
161
|
|
|
156
162
|
|
|
@@ -194,5 +200,4 @@ def set_installation_id(app_json: Path, installation_id: int) -> None:
|
|
|
194
200
|
"""Fill the installation id into an already-written github_app.<slug>.json."""
|
|
195
201
|
data = json.loads(app_json.read_text())
|
|
196
202
|
data["installation_id"] = int(installation_id)
|
|
197
|
-
app_json
|
|
198
|
-
app_json.chmod(0o600)
|
|
203
|
+
write_private(app_json, json.dumps(data, indent=2) + "\n")
|
outerloop/attempt.py
CHANGED
|
@@ -44,7 +44,7 @@ from outerloop.github import (
|
|
|
44
44
|
contract_at,
|
|
45
45
|
ensure_regular_git_dir,
|
|
46
46
|
)
|
|
47
|
-
from outerloop.harness import Harness, SessionResult, redact
|
|
47
|
+
from outerloop.harness import Harness, SessionResult, default_binary, redact
|
|
48
48
|
from outerloop.markers import has_marker
|
|
49
49
|
from outerloop.measure import DispatchedMeasurer, DispatchSettings
|
|
50
50
|
from outerloop.orchestrator import (
|
|
@@ -97,8 +97,11 @@ log = logging.getLogger(__name__)
|
|
|
97
97
|
# tick preflights the panel key (and compares it against the author key —
|
|
98
98
|
# role separation) before claiming/submitting.
|
|
99
99
|
PANEL_KEY_DEFAULT = str(CONFIG_DIR / "verifier_key")
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
# One rule for author keys: ~/.config/outerloop/<backend>_key. `harness_key` is
|
|
101
|
+
# the pre-rename name of the claude key; it is still read, never written.
|
|
102
|
+
CLAUDE_KEY_DEFAULT = str(CONFIG_DIR / "claude_key")
|
|
103
|
+
HARNESS_KEY_DEFAULT = str(CONFIG_DIR / "harness_key") # legacy claude key
|
|
104
|
+
CODEX_KEY_DEFAULT = str(CONFIG_DIR / "codex_key")
|
|
102
105
|
|
|
103
106
|
|
|
104
107
|
def resolve_author_key_file(backend: str, explicit: str = "") -> str:
|
|
@@ -106,14 +109,28 @@ def resolve_author_key_file(backend: str, explicit: str = "") -> str:
|
|
|
106
109
|
codex's both on disk), selected by backend — so the author backend is a
|
|
107
110
|
config choice, not a key swap, and an in-flight run of either backend can
|
|
108
111
|
still be woken/serviced after a fleet flip. An explicit path always wins;
|
|
109
|
-
otherwise the per-backend env var, then the
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
otherwise the per-backend env var, then the default path. For claude the
|
|
113
|
+
legacy spellings (`AUTORESEARCH_HARNESS_KEY_FILE`, read through its bridged
|
|
114
|
+
`OUTERLOOP_` twin, and `harness_key`) are still
|
|
115
|
+
honored, so a machine set up before the rename keeps working; the new name
|
|
116
|
+
wins when both exist. The result is always ~-expanded, so every caller gets
|
|
117
|
+
a real path (an env value like "~/.config/..." must not reach the token
|
|
118
|
+
provider verbatim)."""
|
|
112
119
|
if not explicit:
|
|
113
120
|
if backend == "codex":
|
|
114
|
-
explicit = os.environ.get("
|
|
121
|
+
explicit = os.environ.get("OUTERLOOP_CODEX_KEY_FILE") or CODEX_KEY_DEFAULT
|
|
115
122
|
else:
|
|
116
|
-
explicit =
|
|
123
|
+
explicit = (
|
|
124
|
+
os.environ.get("OUTERLOOP_CLAUDE_KEY_FILE")
|
|
125
|
+
or os.environ.get("OUTERLOOP_HARNESS_KEY_FILE")
|
|
126
|
+
or ""
|
|
127
|
+
)
|
|
128
|
+
if not explicit:
|
|
129
|
+
explicit = CLAUDE_KEY_DEFAULT
|
|
130
|
+
if not os.path.exists(os.path.expanduser(explicit)) and os.path.exists(
|
|
131
|
+
os.path.expanduser(HARNESS_KEY_DEFAULT)
|
|
132
|
+
):
|
|
133
|
+
explicit = HARNESS_KEY_DEFAULT
|
|
117
134
|
return os.path.expanduser(explicit)
|
|
118
135
|
|
|
119
136
|
|
|
@@ -124,14 +141,14 @@ def codex_author_config_error(backend: str, model: str, image: str) -> str:
|
|
|
124
141
|
a unit and never a fleet backend against a run's model. codex writes+executes,
|
|
125
142
|
so it must be contained (--image) and needs a non-claude model."""
|
|
126
143
|
if backend not in ("claude", "codex"):
|
|
127
|
-
# a typo'd
|
|
144
|
+
# a typo'd OUTERLOOP_AUTHOR_BACKEND passes the env DEFAULT silently
|
|
128
145
|
# (argparse validates the flag, not its default) and the climb rejects it
|
|
129
146
|
# at build_harness — catch it on the tick host so a claimed intake
|
|
130
147
|
# issue never strands on it
|
|
131
148
|
return f"unknown author backend {backend!r} (expected 'claude' or 'codex')"
|
|
132
149
|
if backend == "claude":
|
|
133
150
|
# symmetric to the codex check: a claude harness 404s on a non-claude
|
|
134
|
-
# model (e.g.
|
|
151
|
+
# model (e.g. OUTERLOOP_AUTHOR_MODEL left on a codex id while the
|
|
135
152
|
# backend is claude) — catch that misconfig before spend
|
|
136
153
|
if model and not model.startswith("claude"):
|
|
137
154
|
return f"author-backend claude needs a claude model (got {model!r})"
|
|
@@ -588,8 +605,15 @@ def _make_launcher(
|
|
|
588
605
|
partially-submitted batch is reaped rather than orphaned. `gpus` is the
|
|
589
606
|
benchmark's: an author's experiments run on the same lane as its evals."""
|
|
590
607
|
account, partition = dispatch.placement(gpus)
|
|
608
|
+
# under launch admission a GPU launch enters the queue held; the tick
|
|
609
|
+
# releases it when the user's GPUs fit under the cap (tick.service_admission)
|
|
610
|
+
from outerloop.tick import max_launch_gpus_from_env
|
|
611
|
+
|
|
612
|
+
hold = gpus > 0 and not local_mode() and max_launch_gpus_from_env() > 0
|
|
591
613
|
|
|
592
614
|
def launcher(sha: str, request: SyscallRequest) -> str:
|
|
615
|
+
from dataclasses import replace as _replace
|
|
616
|
+
|
|
593
617
|
from outerloop.dispatch import eval_job_spec, write_eval_job
|
|
594
618
|
from outerloop.syscall import launch_jobs
|
|
595
619
|
|
|
@@ -611,18 +635,15 @@ def _make_launcher(
|
|
|
611
635
|
artifact_max_bytes=MAX_ARTIFACT_BYTES,
|
|
612
636
|
gpus=gpus,
|
|
613
637
|
)
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
eval_minutes=launch.minutes,
|
|
622
|
-
gpus=gpus,
|
|
623
|
-
)
|
|
624
|
-
)
|
|
638
|
+
spec = eval_job_spec(
|
|
639
|
+
script,
|
|
640
|
+
job_name=f"{run_id}-launch-{job_name}",
|
|
641
|
+
account=account,
|
|
642
|
+
partition=partition,
|
|
643
|
+
eval_minutes=launch.minutes,
|
|
644
|
+
gpus=gpus,
|
|
625
645
|
)
|
|
646
|
+
ids.append(dispatch.compute.submit(_replace(spec, hold=hold)))
|
|
626
647
|
except Exception:
|
|
627
648
|
# a partial batch must not orphan: no park record was written yet,
|
|
628
649
|
# so nothing would ever wake or cancel the jobs that DID submit —
|
|
@@ -2155,7 +2176,7 @@ def _panel_lenses_from_args(args: Any) -> tuple[tuple[PanelLens, ...], tuple[str
|
|
|
2155
2176
|
if backend == "codex":
|
|
2156
2177
|
lens_key = _judge_lens_key(
|
|
2157
2178
|
backend="codex",
|
|
2158
|
-
key_file_env="
|
|
2179
|
+
key_file_env="OUTERLOOP_PANEL_CODEX_KEY_FILE",
|
|
2159
2180
|
author_backend="codex",
|
|
2160
2181
|
claude_panel_path=claude_panel_path,
|
|
2161
2182
|
image=args.image,
|
|
@@ -2169,7 +2190,7 @@ def _panel_lenses_from_args(args: Any) -> tuple[tuple[PanelLens, ...], tuple[str
|
|
|
2169
2190
|
# separate against the codex author key.
|
|
2170
2191
|
lens_key = _judge_lens_key(
|
|
2171
2192
|
backend="hermes",
|
|
2172
|
-
key_file_env="
|
|
2193
|
+
key_file_env="OUTERLOOP_PANEL_HERMES_KEY_FILE",
|
|
2173
2194
|
author_backend="codex",
|
|
2174
2195
|
claude_panel_path=claude_panel_path,
|
|
2175
2196
|
image=args.image,
|
|
@@ -2179,6 +2200,12 @@ def _panel_lenses_from_args(args: Any) -> tuple[tuple[PanelLens, ...], tuple[str
|
|
|
2179
2200
|
else:
|
|
2180
2201
|
lens_key = panel_key
|
|
2181
2202
|
try:
|
|
2203
|
+
if not args.image:
|
|
2204
|
+
log.warning(
|
|
2205
|
+
"panel lens %s runs uncontained (local mode, no image): the judge "
|
|
2206
|
+
"shares this machine with the operator's keys",
|
|
2207
|
+
backend,
|
|
2208
|
+
)
|
|
2182
2209
|
judge = build_harness(
|
|
2183
2210
|
lens_key,
|
|
2184
2211
|
reviewer_spec(),
|
|
@@ -3121,45 +3148,48 @@ def main() -> int:
|
|
|
3121
3148
|
)
|
|
3122
3149
|
parser.add_argument("--base-branch", default="main")
|
|
3123
3150
|
# All three default from the chain env the tick sets on the climb job, so
|
|
3124
|
-
# a contained run with
|
|
3125
|
-
# dispatched measurement without extra flags.
|
|
3126
|
-
# session + inline eval;
|
|
3127
|
-
# regardless of the benchmark's eval hint.
|
|
3151
|
+
# a contained run with OUTERLOOP_{IMAGE,ACCOUNT,PARTITION} set selects
|
|
3152
|
+
# dispatched measurement without extra flags. Only the image is required
|
|
3153
|
+
# for dispatch (it also contains the session + inline eval); without it,
|
|
3154
|
+
# measurement stays inline regardless of the benchmark's eval hint. Empty
|
|
3155
|
+
# account/partition use Slurm's defaults.
|
|
3128
3156
|
parser.add_argument(
|
|
3129
3157
|
"--image",
|
|
3130
|
-
default=os.environ.get("
|
|
3158
|
+
default=os.environ.get("OUTERLOOP_IMAGE", ""),
|
|
3131
3159
|
help="apptainer image for session+eval",
|
|
3132
3160
|
)
|
|
3133
|
-
parser.add_argument("--account", default=os.environ.get("
|
|
3134
|
-
parser.add_argument("--partition", default=os.environ.get("
|
|
3161
|
+
parser.add_argument("--account", default=os.environ.get("OUTERLOOP_ACCOUNT", ""))
|
|
3162
|
+
parser.add_argument("--partition", default=os.environ.get("OUTERLOOP_PARTITION", ""))
|
|
3135
3163
|
# the GPU lane for benchmarks with `gpus > 0` (evals + author launches);
|
|
3136
3164
|
# empty = this deployment cannot place GPU jobs
|
|
3137
|
-
parser.add_argument("--gpu-partition", default=os.environ.get("
|
|
3138
|
-
parser.add_argument("--gpu-account", default=os.environ.get("
|
|
3165
|
+
parser.add_argument("--gpu-partition", default=os.environ.get("OUTERLOOP_GPU_PARTITION", ""))
|
|
3166
|
+
parser.add_argument("--gpu-account", default=os.environ.get("OUTERLOOP_GPU_ACCOUNT", ""))
|
|
3139
3167
|
parser.add_argument(
|
|
3140
3168
|
"--uncontained",
|
|
3141
3169
|
action="store_true",
|
|
3142
3170
|
help="run WITHOUT a container (dev only: sessions can then read "
|
|
3143
3171
|
"same-user files, including credential files)",
|
|
3144
3172
|
)
|
|
3145
|
-
parser.add_argument(
|
|
3173
|
+
parser.add_argument(
|
|
3174
|
+
"--claude-bin",
|
|
3175
|
+
default=default_binary("claude"),
|
|
3176
|
+
help="host claude binary for the claude author (OUTERLOOP_CLAUDE_BIN, which init records)",
|
|
3177
|
+
)
|
|
3146
3178
|
parser.add_argument(
|
|
3147
3179
|
"--codex-bin",
|
|
3148
|
-
default=
|
|
3149
|
-
os.environ.get("AUTORESEARCH_CODEX_BIN") or "~/.local/bin/codex"
|
|
3150
|
-
),
|
|
3180
|
+
default=default_binary("codex"),
|
|
3151
3181
|
help="host codex binary for the codex author; bind-mounted into apptainer "
|
|
3152
3182
|
"(must be an absolute path).",
|
|
3153
3183
|
)
|
|
3154
3184
|
parser.add_argument(
|
|
3155
|
-
"--model", default=os.environ.get("
|
|
3185
|
+
"--model", default=os.environ.get("OUTERLOOP_AUTHOR_MODEL") or "claude-opus-5"
|
|
3156
3186
|
)
|
|
3157
3187
|
parser.add_argument(
|
|
3158
3188
|
"--author-backend",
|
|
3159
3189
|
choices=("claude", "codex"),
|
|
3160
|
-
default=os.environ.get("
|
|
3190
|
+
default=os.environ.get("OUTERLOOP_AUTHOR_BACKEND") or "claude",
|
|
3161
3191
|
help="agent backend for the author/editor role (config-driven: default "
|
|
3162
|
-
"from
|
|
3192
|
+
"from OUTERLOOP_AUTHOR_BACKEND). codex runs contained (apptainer + "
|
|
3163
3193
|
"--sandbox danger-full-access) and REQUIRES --image and a codex/openai "
|
|
3164
3194
|
"--model (e.g. gpt-5.6-terra).",
|
|
3165
3195
|
)
|
|
@@ -3203,7 +3233,7 @@ def main() -> int:
|
|
|
3203
3233
|
parser.add_argument("--pat-file", default=str(CONFIG_DIR / "bot_pat"))
|
|
3204
3234
|
parser.add_argument(
|
|
3205
3235
|
"--github-app-file",
|
|
3206
|
-
default=os.environ.get("
|
|
3236
|
+
default=os.environ.get("OUTERLOOP_GITHUB_APP_FILE", ""),
|
|
3207
3237
|
help="GitHub App config (JSON: app_id, installation_id, private_key); "
|
|
3208
3238
|
"when set, installation tokens replace the PAT",
|
|
3209
3239
|
)
|
|
@@ -3211,7 +3241,7 @@ def main() -> int:
|
|
|
3211
3241
|
"--key-file",
|
|
3212
3242
|
default="",
|
|
3213
3243
|
help="author key file; default resolves per backend (config-driven): "
|
|
3214
|
-
"
|
|
3244
|
+
"OUTERLOOP_CLAUDE_KEY_FILE for claude, OUTERLOOP_CODEX_KEY_FILE for codex",
|
|
3215
3245
|
)
|
|
3216
3246
|
parser.add_argument("--issue", type=int, default=0)
|
|
3217
3247
|
parser.add_argument(
|
|
@@ -3240,12 +3270,11 @@ def main() -> int:
|
|
|
3240
3270
|
# and re-enter the decision. The wake job the WakeDispatcher submits runs
|
|
3241
3271
|
# exactly this.
|
|
3242
3272
|
if args.resume:
|
|
3243
|
-
|
|
3244
|
-
if not (placed and args.image and Path(args.image).is_file()):
|
|
3273
|
+
if not (args.image and Path(args.image).is_file()):
|
|
3245
3274
|
parser.error(
|
|
3246
|
-
"--resume needs the
|
|
3247
|
-
"
|
|
3248
|
-
"
|
|
3275
|
+
"--resume needs --image to rebuild the dispatched measurer "
|
|
3276
|
+
"(account and partition are optional: empty ones use Slurm's "
|
|
3277
|
+
"defaults, and local compute has no placement)"
|
|
3249
3278
|
)
|
|
3250
3279
|
from outerloop.runstate import load_record
|
|
3251
3280
|
|
|
@@ -3410,13 +3439,12 @@ def main() -> int:
|
|
|
3410
3439
|
except ValueError as exc:
|
|
3411
3440
|
parser.error(str(exc))
|
|
3412
3441
|
|
|
3413
|
-
# Dispatched measurement needs
|
|
3414
|
-
#
|
|
3415
|
-
# sets
|
|
3442
|
+
# Dispatched measurement needs a real image file to bind against; without
|
|
3443
|
+
# one the climb measures inline. Account and partition are optional: empty
|
|
3444
|
+
# ones use Slurm's defaults (the tick sets them on the climb job's env from
|
|
3445
|
+
# the chain's), and local compute has no placement.
|
|
3416
3446
|
dispatch: DispatchSettings | None = None
|
|
3417
|
-
if
|
|
3418
|
-
args.image and Path(args.image).is_file()
|
|
3419
|
-
):
|
|
3447
|
+
if args.image and Path(args.image).is_file():
|
|
3420
3448
|
dispatch = _dispatch_settings(args)
|
|
3421
3449
|
try:
|
|
3422
3450
|
try:
|