Manage Bohrium compute jobs via bohr CLI or open.bohrium.com API. Use when: user asks about submitting/listing/killing/deleting compute jobs on Bohrium, chec...
---
name: bohrium-job
description: "Manage Bohrium compute jobs via bohr CLI or open.bohrium.com API. Use when: user asks about submitting/listing/killing/deleting compute jobs on Bohrium, checking job logs, or monitoring job status. NOT for: node management, image management, or project management."
---
# SKILL: Bohrium Job Management
## Overview
Manage compute jobs on the Bohrium platform. **Prefer `bohr` CLI**; fall back to the API only for advanced operations not supported by the CLI.
## Authentication
ACCESS_KEY and PROJECT_ID are read from the OpenClaw config `~/.openclaw/openclaw.json`:
```json
"bohrium-job": {
"enabled": true,
"apiKey": "YOUR_ACCESS_KEY",
"env": {
"ACCESS_KEY": "YOUR_ACCESS_KEY",
"PROJECT_ID": "YOUR_PROJECT_ID"
}
}
```
OpenClaw automatically injects `env` variables into the runtime. The `bohr` CLI authenticates via the `ACCESS_KEY` environment variable.
## Prerequisites: Install bohr CLI
```bash
# macOS
/bin/bash -c "$(curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_mac_curl.sh)"
# Linux
/bin/bash -c "$(curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_linux_curl.sh)"
# Verify
source ~/.bashrc # or source ~/.zshrc
export PATH="$HOME/.bohrium:$PATH"
bohr version
```
> The installer auto-configures `OPENAPI_HOST` and `TIEFBLUE_HOST`. If they don't take effect, set manually:
> ```bash
> export OPENAPI_HOST=https://open.bohrium.com
> export TIEFBLUE_HOST=https://tiefblue.dp.tech
> ```
---
## Submit Jobs
### Method 1: CLI Arguments (Recommended)
```bash
bohr job submit \
-m "registry.dp.tech/dptech/deepmd-kit:3.1.1" \
-t "c4_m15_1 * NVIDIA T4" \
-c "python train.py" \
-p ./input_dir/ \
--project_id 154 \
-n "my-job-name"
```
**Parameters:**
| Parameter | Short | Required | Description |
|-----------|-------|----------|-------------|
| `--image_address` | `-m` | Yes | Full image URL (e.g. `registry.dp.tech/dptech/xxx:tag`) |
| `--machine_type` | `-t` | Yes | Machine spec |
| `--command` | `-c` | Yes | Command to execute |
| `--input_directory` | `-p` | No | Input file directory (default `./`) |
| `--project_id` | | Yes | Project ID |
| `--job_name` | `-n` | No | Job name |
| `--log_file` | `-l` | No | Log file path |
| `--result_path` | `-r` | No | Auto-download results path (only `/data`, `/personal`, `/share`) |
| `--job_group_id` | `-g` | No | Job group ID (create with `bohr job_group create`) |
| `--max_run_time` | | No | Max run time (minutes); auto-terminates on timeout |
| `--max_reschedule_times` | | No | Auto-retry count after abnormal interruption |
| `--nnode` | | No | Parallel compute node count (default 1) |
### Method 2: Config File (For Complex Scenarios)
```bash
bohr job submit -i job.json -p ./input_dir/
```
**Full job.json example:**
```json
{
"job_name": "my-training-job",
"command": "python train.py --epochs 10",
"log_file": "train.log",
"backward_files": ["model.pt", "results/"],
"project_id": 154,
"machine_type": "c4_m15_1 * NVIDIA T4",
"image_address": "registry.dp.tech/dptech/deepmd-kit:3.1.1",
"job_type": "container",
"disk_size": 50,
"dataset_path": ["/bohr/my-dataset/v1"],
"result_path": "/personal",
"max_reschedule_times": 2,
"max_run_time": 120,
"nnode": 1
}
```
**job.json field reference:**
| Field | Description | Example |
|-------|-------------|---------|
| `job_name` | Job name | `"DeePMD-kit test"` |
| `command` | Command; use **relative paths** | `"cd se_e2_a && dp train input.json"` |
| `log_file` | Log viewable in real-time | `"train.log"` |
| `backward_files` | Files to download; empty = keep all | `["model.pt", "results/"]` |
| `project_id` | Project ID | `154` |
| `machine_type` | Machine spec | `"c4_m15_1 * NVIDIA T4"` |
| `image_address` | Full image URL (not just name) | `"registry.dp.tech/dptech/deepmd-kit:2.1.5-cuda11.6"` |
| `job_type` | Must be `"container"` | `"container"` |
| `dataset_path` | Mounted dataset paths | `["/bohr/my-dataset/v1"]` |
| `result_path` | Auto-collect results to data disk | `"/personal"` |
| `max_run_time` | Max run time (minutes) | `120` |
| `max_reschedule_times` | Retry count on interruption | `2` |
| `disk_size` | Disk size (GB) | `50` |
### Important Notes
| Topic | Details |
|-------|---------|
| **Working directory** | Bohrium auto-`cd`s into the extracted input dir; use **relative paths** |
| **Do NOT** `cd /root/input` | Actual path is `/home/input_lbg-{userId}-{jobId}/`, unpredictable |
| **image_address format** | Must be full URL `registry.dp.tech/dptech/xxx:tag` |
| **machine_type format** | CPU: `c2_m4_cpu`; GPU: `c4_m15_1 * NVIDIA T4` |
| **WAF blocking** | If command triggers Alibaba Cloud WAF (405), write to script, use `bash run.sh` |
| **Large files in -p** | No output? Check for large hidden files causing slow compression |
| **Auto-retry** | `max_reschedule_times` retries on interruption (full re-run) |
| **job_type** | Must be `"container"`; VM jobs deprecated since 2023 |
---
## View Jobs
```bash
bohr job list -n 10 # Recent 10
bohr job list -n 5 --json # JSON output
bohr job list -r # Running only
bohr job list -f # Failed only
bohr job list -i # Finished only
bohr job list -p # Pending only
bohr job list -j 15954383 # Jobs in a specific group
```
### Job Details
```bash
bohr job describe -j 22153612 --json
bohr job describe -j 22153612 -l # Full details
```
### View/Download Logs
```bash
bohr job log -j 22153612 # View
bohr job log -j 22153612 -o ./logs/ # Download
```
### Download Results
```bash
bohr job download -j 22153612 -o ./results/
bohr job_group download -j 15954383 -o ./results/
```
---
## Manage Jobs
```bash
bohr job terminate 22153612 # Terminate (keep results -> completed)
bohr job kill 22153612 # Force stop (discard results, keep record)
bohr job delete 22153612 # Delete (remove everything)
bohr job terminate 22153612 22153613 # Batch terminate
bohr job delete 22153612 22153613 # Batch delete
```
**terminate vs kill vs delete:**
| Action | Result files | Record | Status |
|--------|-------------|--------|--------|
| `terminate` | Kept | Kept | -> completed |
| `kill` | Discarded | Kept | -> failed |
| `delete` | Removed | Removed | Disappears |
---
## Job Group Management
```bash
bohr job_group list -n 10 --json
bohr job_group list -s 2026-01-01 -e 2026-03-14 # Date range
bohr job_group create -n "experiment-v1" -p 154
bohr job_group terminate 15954383
bohr job_group delete 15954383
bohr job_group download -j 15954383 -o ./results/
```
> **Note**: `job_group_id` from `bohr job_group create` is for CLI only (`bohr job submit -g <id>`). It differs from the web UI job group ID.
---
## Scientific Software Examples
### DeePMD-kit Training
```json
{
"job_name": "DeePMD-kit test",
"command": "cd se_e2_a && dp train input.json > tmp_log 2>&1 && dp freeze -o graph.pb",
"log_file": "se_e2_a/tmp_log",
"backward_files": ["se_e2_a/lcurve.out", "se_e2_a/graph.pb"],
"project_id": 154,
"machine_type": "c4_m15_1 * NVIDIA T4",
"job_type": "container",
"image_address": "registry.dp.tech/dptech/deepmd-kit:2.1.5-cuda11.6"
}
```
### LAMMPS Molecular Dynamics
```json
{
"job_name": "lammps_tutorial",
"command": "mpirun -n 32 lmp_mpi -i in.shear > log",
"log_file": "log",
"backward_files": [],
"project_id": 154,
"machine_type": "c32_m64_cpu",
"job_type": "container",
"image_address": "registry.dp.tech/dptech/lammps:29Sep2021"
}
```
### ABACUS First-Principles
```json
{
"job_name": "ABACUS test",
"command": "OMP_NUM_THREADS=1 mpirun -np 8 abacus > log",
"log_file": "log",
"backward_files": [],
"project_id": 154,
"machine_type": "c16_m32_cpu",
"job_type": "container",
"image_address": "registry.dp.tech/dptech/abacus:3.0.0"
}
```
### GROMACS Molecular Simulation
```json
{
"job_name": "bohrium-gmx-example",
"command": "bash rungmx.sh > log",
"log_file": "log",
"backward_files": [],
"project_id": 154,
"machine_type": "c16_m62_1 * NVIDIA T4",
"job_type": "container",
"image_address": "registry.dp.tech/dptech/gromacs:2022.2"
}
```
### CP2K Quantum Chemistry
```json
{
"job_name": "CP2K_Si_opt",
"command": "source /cp2k-7.1/tools/toolchain/install/setup && mpirun -n 16 --allow-run-as-root --oversubscribe cp2k.popt -i input.inp -o output.log",
"log_file": "output.log",
"backward_files": ["output.log"],
"project_id": 154,
"machine_type": "c16_m32_cpu",
"job_type": "container",
"image_address": "registry.dp.tech/dptech/cp2k:7.1"
}
```
---
## Container / VM Image Mapping
| Software | VM Image | Container Image |
|----------|----------|----------------|
| DeePMD-kit | LBG_DeePMD-kit_2.1.4_v1 | `registry.dp.tech/dptech/deepmd-kit:2.1.5-cuda11.6` |
| DPGEN | LBG_DP-GEN_0.10.6_v3 | `registry.dp.tech/dptech/dpgen:0.10.6` |
| LAMMPS | LBG_LAMMPS_stable_23Jun2022_v1 | `registry.dp.tech/dptech/lammps:29Sep2021` |
| GROMACS | gromacs-dp:2020.2 | `registry.dp.tech/dptech/gromacs:2022.2` |
| Quantum-Espresso | LBG_Quantum-Espresso_7.1 | `registry.dp.tech/dptech/quantum-espresso:7.1` |
| CP2K | - | `registry.dp.tech/dptech/cp2k:7.1` |
| ABACUS | - | `registry.dp.tech/dptech/abacus:3.0.0` |
| Base (CPU) | LBG_Common_v1/v2 | `registry.dp.tech/dptech/ubuntu:20.04-py3.10` |
| Base (GPU) | LBG_base_image_ubun20.04 | `registry.dp.tech/dptech/ubuntu:20.04-py3.10-cuda11.6` |
| Intel OneAPI | LBG_oneapi_2021_v1 | `registry.dp.tech/dptech/ubuntu:20.04-py3.10-intel2022-cuda11.6` |
---
## API Supplement (CLI Unsupported Operations)
```python
import os, requests
AK = os.environ.get("ACCESS_KEY", "")
BASE = "https://open.bohrium.com/openapi/v1"
HEADERS = {"accessKey": AK}
# Filter by status (0=pending, 1=running, 2=finished, 3=scheduling, -1=failed)
r = requests.get(f"{BASE}/job/list", headers=HEADERS,
params={"page": 1, "pageSize": 10, "status": 1})
# Job config (includes file access token)
r = requests.get(f"{BASE}/job/view/conf/{job_id}", headers=HEADERS)
# Returns: {state, baseDir, tempDir, token, host, expires}
# View snapshot
r = requests.get(f"{BASE}/job/{job_id}/snapshot", headers=HEADERS)
# Rename job
requests.post(f"{BASE}/job/{job_id}/modify",
headers={**HEADERS, "Content-Type": "application/json"},
json={"name": "new-name"})
# Rename job group
requests.post(f"{BASE}/job_group/{job_group_id}/modify",
headers={**HEADERS, "Content-Type": "application/json"},
json={"name": "new-group-name"})
```
---
## Job Status Codes
| status | Meaning | CLI Display |
|--------|---------|-------------|
| 0 | Pending | Pending |
| 1 | Running | Running |
| 2 | Finished | Finished |
| 3 | Scheduling | Scheduling |
| -1 | Failed | Failed |
## Common Machine Types
| machine_type | Description |
|--------------|-------------|
| `c2_m4_cpu` | 2 cores, 4G RAM |
| `c4_m8_cpu` | 4 cores, 8G RAM |
| `c8_m32_cpu` | 8 cores, 32G RAM |
| `c16_m32_cpu` | 16 cores, 32G RAM |
| `c32_m64_cpu` | 32 cores, 64G RAM |
| `c4_m15_1 * NVIDIA T4` | 4 cores, 15G + 1x T4 |
| `c16_m62_1 * NVIDIA T4` | 16 cores, 62G + 1x T4 |
| `c8_m32_1 * NVIDIA V100` | 8 cores, 32G + 1x V100 |
| `c32_m128_4 * NVIDIA V100` | 32 cores, 128G + 4x V100 |
## Troubleshooting
| Problem | Cause | Solution |
|---------|-------|----------|
| `cd /root/input: No such file` | Absolute path in command | Use **relative paths** |
| `unsupported protocol scheme ""` | Missing env vars | Set `OPENAPI_HOST` and `TIEFBLUE_HOST` |
| `(200, '/account/login', None)` | Old pip lbg | Use Go CLI (`~/.bohrium/bohr`) |
| WAF 405 | Shell keywords in command | Write to script, use `bash run.sh` |
| `Permission error` | Wrong user | Verify ACCESS_KEY |
| `jobId` vs `jobGroupId` | Different concepts | CLI uses `jobId` for kill/terminate/delete |
| No output after submit | Large hidden files in `-p` dir | Check dir size |
| Variable performance | ~30% algorithm variance | Normal |
| Long scheduling | Image caching or resource shortage | Wait or contact support |
| System disk full | Output to system disk | Write to working directory |
| Abnormal interruption | Machine reclaim (rare) | Set `max_reschedule_times` |
don't have the plugin yet? install it then click "run inline in claude" again.
extracted 11-step procedure with explicit inputs/outputs per step, formalized 9 decision points covering path format, image URLs, machine types, file handling, auth, and API vs CLI choice, added edge cases for rate limits/timeouts/auth failure/empty results, documented all required env vars and bohr CLI setup, clarified output contract with success formats and response types.
submit, monitor, and manage compute jobs on the bohrium platform. use this skill when a user asks to submit jobs (via CLI args or config file), list running/completed jobs, view logs, download results, terminate/kill/delete jobs, or manage job groups. not for node management, image management, or project setup.
environment variables (injected via openclaw config ~/.openclaw/openclaw.json):
ACCESS_KEY: bohrium API key for authentication (required)PROJECT_ID: bohrium project ID (required)OPENAPI_HOST: defaults to https://open.bohrium.com; set manually if install script doesn't auto-configureTIEFBLUE_HOST: defaults to https://tiefblue.dp.tech; set manually if install script doesn't auto-configurebohr CLI (must be installed and in PATH):
curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_mac_curl.sh | bashcurl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_linux_curl.sh | bashbohr versionjob submission inputs (either CLI args or JSON config):
registry.dp.tech/dptech/deepmd-kit:3.1.1), requiredc4_m15_1 * NVIDIA T4), required./external connection:
https://open.bohrium.com/openapi/v1 (requires ACCESS_KEY header)input: shell environment
output: confirmation that bohr command is available and env vars are set
run bohr version to confirm CLI is installed. verify echo $ACCESS_KEY and echo $PROJECT_ID are non-empty. if either env var is missing, check ~/.openclaw/openclaw.json and re-source shell profile or restart terminal.
input: job complexity and user preference output: decision on submission approach
if job is simple and parameters fit CLI flags, use method 1 (CLI args). if job requires multiple output files, datasets, or complex config, use method 2 (JSON config file). config file method is also preferred for reproducibility and archival.
input: image address, machine type, command, project ID, optional: job name, input directory, max run time, max reschedule count, nnode output: job ID (integer)
run:
bohr job submit \
-m "registry.dp.tech/dptech/deepmd-kit:3.1.1" \
-t "c4_m15_1 * NVIDIA T4" \
-c "python train.py" \
-p ./input_dir/ \
--project_id 154 \
-n "my-job-name" \
[--max_run_time 120] \
[--max_reschedule_times 2] \
[--nnode 1]
note: use relative paths in the command (e.g. python train.py, not /home/input_lbg-userId-jobId/python train.py). bohrium auto-extracts input files and cds into that directory.
input: job.json file with all parameters, input directory path output: job ID (integer)
create job.json with fields: job_name, command, log_file, backward_files (list of output files to keep), project_id, machine_type, image_address, job_type (must be "container"), disk_size, dataset_path (optional), result_path (optional: /personal, /share, or /data), max_reschedule_times, max_run_time, nnode. then run:
bohr job submit -i job.json -p ./input_dir/
note: backward_files determines what gets downloaded; empty list means keep all output. result_path auto-collects outputs to data disk (/personal, /share, or /data only).
input: optional filter flags (count, status, job group ID, JSON format) output: table or JSON of jobs with ID, name, status, timestamps
run one of:
bohr job list -n 10 # recent 10 jobs
bohr job list -n 5 --json # JSON output
bohr job list -r # running only
bohr job list -f # failed only
bohr job list -i # finished only
bohr job list -p # pending only
bohr job list -j 15954383 # all jobs in a group
input: job ID output: job metadata including status, machine type, image, command, timestamps, and optionally full details
run:
bohr job describe -j 22153612 [--json] [-l]
the -l flag includes full output. use --json for machine-readable format.
input: job ID, optional output directory for download output: logs printed to stdout or saved to local directory
run:
bohr job log -j 22153612 # print to stdout
bohr job log -j 22153612 -o ./logs/ # download to directory
logs are real-time if job is running. if job is finished, logs are archived and still viewable.
input: job ID, output directory path output: result files saved to local directory
run:
bohr job download -j 22153612 -o ./results/
or for a job group:
bohr job_group download -j 15954383 -o ./results/
downloaded files are those listed in backward_files (if specified) or all output files (if backward_files is empty).
input: job ID or list of job IDs, action (terminate, kill, or delete) output: confirmation message; job status changes
run one of:
bohr job terminate 22153612 # mark done, keep results
bohr job kill 22153612 # force stop, discard results
bohr job delete 22153612 # remove everything
bohr job terminate 22153612 22153613 # batch terminate
bohr job delete 22153612 22153613 # batch delete
terminate: job moves to "completed", results are kept, record is kept. kill: job moves to "failed", results are discarded, record is kept. delete: job record and files are completely removed.
input: optional job group name, project ID, optional date range, optional group ID output: list of groups, created group ID, or confirmation of action
run:
bohr job_group list -n 10 [--json] # list groups
bohr job_group list -s 2026-01-01 -e 2026-03-14 # by date range
bohr job_group create -n "experiment-v1" -p 154 # create group
bohr job_group terminate 15954383 # terminate all jobs in group
bohr job_group delete 15954383 # delete group
bohr job_group download -j 15954383 -o ./results/ # download all results
note: CLI job group IDs are different from web UI IDs. use CLI IDs with bohr job submit -g <id>.
input: ACCESS_KEY env var, job ID or job group ID, optional filter params or JSON body output: JSON response from bohrium API
use only for operations not covered by CLI. examples:
import os, requests
AK = os.environ.get("ACCESS_KEY", "")
BASE = "https://open.bohrium.com/openapi/v1"
HEADERS = {"accessKey": AK}
# filter jobs by status (0=pending, 1=running, 2=finished, 3=scheduling, -1=failed)
r = requests.get(f"{BASE}/job/list", headers=HEADERS,
params={"page": 1, "pageSize": 10, "status": 1})
# get job config (returns file access token and paths)
r = requests.get(f"{BASE}/job/view/conf/{job_id}", headers=HEADERS)
# view job snapshot (state, baseDir, tempDir, token, host, expires)
r = requests.get(f"{BASE}/job/{job_id}/snapshot", headers=HEADERS)
# rename job
requests.post(f"{BASE}/job/{job_id}/modify",
headers={**HEADERS, "Content-Type": "application/json"},
json={"name": "new-name"})
# rename job group
requests.post(f"{BASE}/job_group/{job_group_id}/modify",
headers={**HEADERS, "Content-Type": "application/json"},
json={"name": "new-group-name"})
CLI vs API submission: if job is straightforward (single command, single image), use CLI args (bohr job submit -m ... -t ... -c ...). if job needs multiple outputs, datasets, or complex config, write job.json and use bohr job submit -i job.json. if you need to filter by status or rename jobs, use API calls.
Path format: always use relative paths in commands (e.g. python train.py, cd se_e2_a && dp train input.json). do not use absolute paths like /root/input or /home/input_lbg-userId-jobId/. bohrium auto-cds into extracted input directory.
terminate vs kill vs delete: if job is done and you want to keep results and record, use terminate. if job is stuck and you want to stop it immediately without keeping results, use kill. if you want to erase all traces, use delete. default is terminate.
Image address format: if user provides a short name (e.g. deepmd-kit:3.1.1), expand it to full registry URL: registry.dp.tech/dptech/deepmd-kit:3.1.1. if user provides full URL, use as-is. docker image names without registry default to docker hub, which bohrium does not access.
machine_type format: if user specifies CPU cores and RAM only, map to standard CPU types (e.g. 4 cores, 8G RAM = c4_m8_cpu). if user specifies GPU, use format c{cores}_m{ram}_{count} * NVIDIA {type} (e.g. c4_m15_1 * NVIDIA T4). if unsure, check common machine types table.
large file handling: if bohr job submit appears to hang after specifying -p ./dir/, check if directory contains large hidden files or temp files. compress input dir or remove unnecessary files. if input compresses slowly, submission will timeout.
waf blocking: if command contains pipes, redirects, or special shell syntax and returns HTTP 405 (WAF rejection), write command to a shell script and use bash run.sh as the command instead.
auto-retry on interruption: if job fails due to node preemption or resource reclaim, set max_reschedule_times to a positive integer (e.g. 2). bohrium will auto-resubmit the entire job (not resume from checkpoint). normal job failures do not trigger retry.
job_id vs job_group_id: CLI job operations (kill, terminate, download) use job IDs (e.g. bohr job terminate 22153612). job group operations use group IDs (e.g. bohr job_group terminate 15954383). these are not interchangeable.
env var missing: if $ACCESS_KEY or $PROJECT_ID are empty, check openclaw config at ~/.openclaw/openclaw.json under the bohrium-job section. ensure the keys exist and are not empty strings. re-source shell profile or restart terminal.
api call auth failure: if API call returns 403 or "invalid accessKey", verify os.environ.get("ACCESS_KEY") is populated. if using raw requests, ensure header is {"accessKey": AK}, not {"Authorization": "Bearer ..."}.
empty result set: if bohr job list returns nothing, check filters (e.g. -r for running only). if filtering by status, ensure status code is correct (0=pending, 1=running, 2=finished, 3=scheduling, -1=failed). if job was deleted, it will not appear in any list.
job submission: on success, bohrium returns a job ID (integer, e.g. 22153612). this ID is printed to stdout and is used for all subsequent operations (log, describe, terminate, download).
job list: output is a table with columns: job ID, job name, status, create time, update time. if --json flag is used, output is JSON array of job objects with all metadata.
job describe: output is job metadata including status, machine type, image address, command, input/output paths, create/update/finish times. if -l flag is used, output includes full logs and additional details. if --json is used, output is a single JSON object.
job log: if no download path specified, logs are printed to stdout (real-time for running jobs). if -o ./path/ is specified, logs are saved as text file in that directory.
job download: all result files specified in backward_files (or all files if backward_files is empty) are saved to the output directory. directory structure is preserved.
job management (terminate/kill/delete): on success, a confirmation message is printed (e.g. "Job 22153612 terminated successfully"). job record is updated immediately and visible in subsequent bohr job list calls.
job group operations: create returns the group ID. list returns table or JSON of groups with ID, name, create time, job count. download saves all job results to the output directory in subdirectories per job.
api calls: successful calls return JSON response with status 200. responses are documented in bohrium open API docs at https://open.bohrium.com/openapi/. error responses include status code and error message in JSON.
user knows the skill worked when:
job submitted: bohr job submit prints a job ID to stdout and returns exit code 0. user can then run bohr job describe -j <id> and see the job in "Pending" or "Running" state.
job listed: bohr job list displays a table with at least one row. user sees job names, IDs, and statuses. if filtered (e.g. -r for running), only jobs matching that filter appear.
logs viewed: bohr job log -j <id> prints job output to terminal or saves log file to disk. if job is running, new log lines appear in real-time. if job is finished, full log is available.
results downloaded: bohr job download creates output directory (if not exists) and populates it with result files. user can verify files exist and are non-empty with ls -la ./results/.
job terminated/killed/deleted: bohr job terminate/kill/delete command returns exit code 0. subsequent bohr job describe -j <id> shows status changed to "Completed", "Failed", or job is not found (if deleted). if job was running, it stops within seconds.
job group created: bohr job_group create prints a group ID and returns exit code 0. subsequent bohr job_group list includes the new group. user can submit jobs to this group with bohr job submit -g <id>.
api call successful: Python requests call returns response.status_code == 200. response JSON contains expected fields (e.g. job list contains array of job objects, job config returns token and paths).
credits: original skill authored by clawhub community. enriched per implexa quality standards with explicit decision points, edge case handling, environment setup guidance, and outcome signals.