원클릭으로
job-dispatch
Job dispatch infrastructure. Use when packaging a python script for batch / parallel execution, locally or on SLURM.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Job dispatch infrastructure. Use when packaging a python script for batch / parallel execution, locally or on SLURM.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | job-dispatch |
| description | Job dispatch infrastructure. Use when packaging a python script for batch / parallel execution, locally or on SLURM. |
Package an existing Python core/analysis script for batch execution. This skill handles infrastructure only — the worker shell script, the Python submitter, the directory layout. The same worker .sh and submitter run can run locally or on SLURM by switching is a one-line change in the submitter. The core script itself must already conform to script-opinions (argparse, dual-handler logging, run dir). If it doesn't, fix it first.
<scripts_dir>/
├── <script_name>.py # analysis script (argparse-based)
└── runs/
├── run_<script_name>.sh # worker (one file, both backends)
├── submit_<script_name>.py # Python submitter (stdlib-only)
└── logs/ # stdout/stderr
The submitter must import only stdlib so it runs under the base Python environment.
run_<name>.sh)One worker file works for both backends. The #SBATCH header is inert under local dispatch — those lines are bash comments to any non-sbatch invocation. For SLURM, fill in account, partition, and resources — see references.md for ready-to-paste templates.
#!/bin/bash
#SBATCH --job-name=<short_name>
#SBATCH --account=<billing_account>
#SBATCH --partition=<partition>
#SBATCH --gres=gpu:<count>
#SBATCH --cpus-per-task=<cpus>
#SBATCH --mem=<memory>
#SBATCH --time=<time_limit>
#SBATCH --open-mode=append
#SBATCH --requeue
# $1 is ALWAYS the absolute path to the analysis script.
# $4 is ALWAYS --dir_save (the run directory; see script-opinions).
ANALYSIS_SCRIPT=$1
RUN_NAME=$2
ARG_A=$3
DIR_SAVE=$4
[ -z "$ANALYSIS_SCRIPT" ] || [ -z "$DIR_SAVE" ] && { echo "Usage: $0 <script> <run_name> <arg_a> <dir_save> ..."; exit 1; }
[ ! -f "$ANALYSIS_SCRIPT" ] && { echo "Error: $ANALYSIS_SCRIPT not found"; exit 1; }
PYTHON_EXEC="/path/to/.conda/envs/<env_name>/bin/python"
[ ! -x "$PYTHON_EXEC" ] && { echo "Error: $PYTHON_EXEC not executable"; exit 1; }
echo "Running $RUN_NAME (job=${SLURM_JOB_ID:-local})"
$PYTHON_EXEC "$ANALYSIS_SCRIPT" \
--arg-a "$ARG_A" \
--dir_save "$DIR_SAVE"
RET=$?
exit $RET
submit_<name>.py)Backend is a module-level constant — a wrong-backend flip should be a one-line diff in a review, not an invisible argv. Dispatch logic is isolated in named helpers so the main loop reads backend-agnostic; the two helpers are the only places backend-specific argv lives.
#!/usr/bin/env python3
import argparse, subprocess
from pathlib import Path
from typing import Callable, TypedDict
BACKEND: str = "slurm" # "local" or "slurm"
class DatasetSpec(TypedDict):
name: str
arg_a: str
DATASETS: list[DatasetSpec] = [
{"name": "run_001", "arg_a": "/path/to/input_a"},
# ...
]
OUTPUT_ROOT = "/path/to/output/root"
def dispatch_local(worker: Path, worker_args: list[str], _name: str, _logs: Path) -> list[str]:
return ["bash", str(worker), *worker_args]
def dispatch_slurm(worker: Path, worker_args: list[str], name: str, logs: Path) -> list[str]:
return [
"sbatch",
f"--output={logs}/{name}_%j.out",
f"--error={logs}/{name}_%j.err",
str(worker), *worker_args,
]
DISPATCH: dict[str, Callable[[Path, list[str], str, Path], list[str]]] = {
"local": dispatch_local,
"slurm": dispatch_slurm,
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
here = Path(__file__).resolve().parent
worker = here / "run_<name>.sh"
analysis = (here / "../<script_name>.py").resolve()
logs = here / "logs"
assert worker.exists() and analysis.exists()
logs.mkdir(exist_ok=True)
Path(OUTPUT_ROOT).mkdir(parents=True, exist_ok=True)
build_cmd = DISPATCH[BACKEND]
for i, d in enumerate(DATASETS):
dir_save = str(Path(OUTPUT_ROOT) / d["name"])
worker_args = [str(analysis), d["name"], d["arg_a"], dir_save]
cmd = build_cmd(worker, worker_args, d["name"], logs)
print(f"[{i+1}/{len(DATASETS)}] {' '.join(cmd)}")
if not args.dry_run:
subprocess.run(cmd, check=True, capture_output=True,
text=True, stdin=subprocess.DEVNULL)
if __name__ == "__main__":
main()
Short throwaway sweeps: skip the TypedDict; hardcode config at the top of the file and nest loops over your sweep axes. Worker stays unchanged.
$1. Submitter computes it; worker never resolves paths. Relative paths break in SLURM spool dirs.PYTHON_EXEC. Never conda activate in non-interactive bash.exit $?).stdin=subprocess.DEVNULL + capture_output=True + check=True on every subprocess.run. Prevents SLURM hangs; fails loudly.try/except around subprocess.run that hides failures — let CalledProcessError propagate.TypedDict for dataset specs, not tuples.--dry-run on the submitter.| Concern | Local | SLURM |
|---|---|---|
#SBATCH header | Inert (bash comments) | Active; fill account/partition/gres |
| Cancel-all | pkill -f run_<name>.sh | scancel -n <job-name> |
| Use when | Short, I/O-bound, local has appropriate compute resources, smoke tests | Long, GPU-bound, resource-isolated, sweeps |
Applies only when BACKEND = "local".
concurrent.futures.ProcessPoolExecutor.check=True stops the loop at the first failure — intentional; you're usually debugging or smoke-testing.ctrl-C under a pool is flaky. Keep pkill -f run_<name>.sh ready.Applies only when BACKEND = "slurm".
#SBATCH account + partition + resources from references.md. The combo must be valid on the target cluster.--job-name=<prefix> so scancel -n <prefix> cancels the whole sweep at once.sbatch. Array mode needs a different worker/submitter shape than the base pattern above: the submitter writes a lookup table (one row per dataset) and submits a single array job, and the worker reads $SLURM_ARRAY_TASK_ID to pick its row. The throttle %N caps concurrent tasks (e.g., --array=0-99%15) — check references.md for per-account caps before setting it.Both backends:
script-opinions.BACKEND constant set to the intended target.runs/logs/ exists.--dry-run first.SLURM only:
#SBATCH header uses an account + partition combination valid on the target cluster (see references.md).references.md — Cluster cheat sheet: partitions, billing accounts, fairshare guidance, ready-to-paste #SBATCH blocks per partition, scheduler caps. Consult before filling in the worker's #SBATCH header.Style guidelines for writing manuscript text.
Guide for controlling Adobe Illustrator via MCP tools and osascript.
Extract scientific journal article PDFs to lossless Markdown using MinerU. Assumes a pre-existing conda env named "mineru".
Agent roles, models, and the dispatch pattern for delegating to subagents.
Executive role for an agent that dispatches subagents.
Iterative polish of multi-panel publication figures. Patterns for long-lived layout-revision sessions where a human critiques each render.