kermt-monitor
Companion skill for any KERMT workflow that runs detached: the three pretrain
skills (kermt-continue-pretrain, kermt-pretrain-scratch,
kermt-add-cmim-pretrain) plus kermt-finetune. kermt-infer and
kermt-embed run blocking by default and don't need this skill, but if a
user launches them detached on purpose the monitor still works (the
workflow-dispatch in step 4 handles unknown workflows by tailing the
most-recent log file in the run dir). Reads the run directory's run.json,
queries docker for the container's state, surfaces the latest progress,
and either tails or follows the log.
Hardware requirements
None. This skill only reads disk + queries docker; no GPU compute.
Inputs
One of:
<run-dir> — a positional argument pointing at the directory containing
run.json (e.g. runs/continue-pretrain_2026-05-17T10-23Z). Preferred.
--container <name-or-id> — direct container reference; the skill still
reads run.json from the run dir referenced inside the container's
inspect output if available, but works degraded-mode without it.
Optional:
--lines N — number of trailing log lines to print (default 50).
--follow — stream docker logs -f until ^C. Useful for "watch the
loss". Without it, the skill is one-shot and exits.
--json — emit a structured status report instead of human-readable text.
Useful when the parent agent wants to take downstream action.
Workflow
Let RUN_DIR=$1 (or whatever path the user supplies).
Locate the manifest.
MANIFEST=$RUN_DIR/run.json
Refuse to proceed if it doesn't exist; surface a helpful message
pointing the user at the run-dir convention (runs/<workflow>_<ts>/).
Parse the manifest (Python helper):
workflow=$(jq -r .workflow $MANIFEST)
container_name=... # not directly in run.json today; the skill that
# launched stored it in run.json under
# container.name during launch (see below note).
logs_dir=$(jq -r .logs_dir $MANIFEST)
image_tag=$(jq -r .container.image_tag $MANIFEST)
started_at=$(jq -r .started_at $MANIFEST)
Query docker for container state.
docker ps --filter "name=$container_name" --format \
'{{.ID}}\t{{.Status}}\t{{.CreatedAt}}'
If absent, fall back to docker inspect $container_name --format '{{.State.Status}} (exit {{.State.ExitCode}})' to see whether the
container exited (ok or failed) or was removed (--rm after exit).
Find the live log file.
case "$workflow" in
continue-pretrain|pretrain-scratch) LOG=$logs_dir/pretrain_ddp.log ;;
finetune) LOG=$logs_dir/finetune.log ;;
*) LOG=$(ls -1t $logs_dir/*.log 2>/dev/null | head -n 1) ;;
esac
The manifest's workflow field disambiguates pretrain (pretrain_ddp.log)
from finetune (finetune.log). Other workflows fall back to the
most-recently-modified .log in $logs_dir.
Show the latest progress.
Final test-metrics block (finetune, on completion). If workflow is
finetune AND the container has exited cleanly (State.Status=exited,
ExitCode=0) AND $RUN_DIR/ckpt/fold_*/test_result.csv exists, parse it
and emit a per-task metric table:
Final test metrics (per task):
Target MAE
HLM_clearance 0.187
RLM_clearance 0.213
MDR1-MDCK_efflux 0.241
solubility_pH6.8 0.156
The metric column matches args_applied.metric (mae for regression, auc
for classification, etc.). For multi-fold or ensemble runs, average across
folds/models and note ± std if std > 0. Skip silently if no
test_result.csv exists (run incomplete or no test split was emitted).
If --follow, stream live logs.
docker logs -f $container_name
Wraps until ^C.
Stop / cleanup hints (printed at end of one-shot mode):
To stop: docker stop $container_name
To remove: docker rm $container_name
To re-run: `$(jq -r .cmd_replay $MANIFEST)`
Hard rules
- Read-only on the user's data. Never modify
run.json, never touch the
container's checkpoint dir. The monitor only inspects.
- Don't kill the container without explicit user instruction. If the
user asks to stop, run
docker stop; if they ask to abandon, leave it
running and just exit.
- Don't pull or modify the kermt image. The monitor only reads.
- JSON output mode is non-interactive. Skip the "press ^C to exit"
prompts and emit a single JSON document so the parent agent can pipe it.
Note on container_name plumbing
The run.json schema as currently written does not yet include the launched
container name — kermt_run_detached prints it to stdout but the runner
script doesn't capture it into run.json. The monitor falls back to a
filesystem-based lookup: list runs/<workflow>_*/ directories and match by
mtime; or accept --container <name> explicitly. Follow-up: have the
launching skill record container name into run.json before exiting.
Output (text mode, default)
KERMT continue-pretrain · runs/continue-pretrain_2026-05-17T10-23Z
Container : kermt-continue-pretrain-… (Up 1 hour, status: running)
Image : kermt:latest@sha256:…
Repo : 2fe00f9 (clean)
Started : 2026-05-17T10:23:14Z (1h 23m ago)
Workflow : continue-pretrain, pretrain_mode=hybrid, world_size=2
Latest log (last 50 lines from $LOG):
[Epoch 12/100] step 4523/9000 loss 0.832 lr 1.2e-4
[val] step 4100 val_loss 0.821 (new best)
...
Progress: epoch 12/100, ~12% done. ETA ~6h.
TensorBoard: tensorboard --logdir $RUN_DIR/logs/tb
Replay command: $(jq -r .cmd_replay $RUN_DIR/run.json)
1---2name: kermt-monitor3description: Check progress for a detached KERMT run (pretrain, finetune, or any kermt_run_detached invocation). Reads run.json, queries docker for container state, tails the pretrain/finetune log, and parses progress lines (epoch, step, val loss).4license: Apache-2.05---67# kermt-monitor89Companion skill for any KERMT workflow that runs detached: the three pretrain10skills (`kermt-continue-pretrain`, `kermt-pretrain-scratch`,11`kermt-add-cmim-pretrain`) plus `kermt-finetune`. `kermt-infer` and12`kermt-embed` run blocking by default and don't need this skill, but if a13user launches them detached on purpose the monitor still works (the14workflow-dispatch in step 4 handles unknown workflows by tailing the15most-recent log file in the run dir). Reads the run directory's `run.json`,16queries docker for the container's state, surfaces the latest progress,17and either tails or follows the log.1819## Hardware requirements2021None. This skill only reads disk + queries docker; no GPU compute.2223## Inputs2425One of:2627- `<run-dir>` — a positional argument pointing at the directory containing28 `run.json` (e.g. `runs/continue-pretrain_2026-05-17T10-23Z`). Preferred.29- `--container <name-or-id>` — direct container reference; the skill still30 reads `run.json` from the run dir referenced inside the container's31 inspect output if available, but works degraded-mode without it.3233Optional:3435- `--lines N` — number of trailing log lines to print (default 50).36- `--follow` — stream `docker logs -f` until ^C. Useful for "watch the37 loss". Without it, the skill is one-shot and exits.38- `--json` — emit a structured status report instead of human-readable text.39 Useful when the parent agent wants to take downstream action.4041## Workflow4243Let `RUN_DIR=$1` (or whatever path the user supplies).44451. **Locate the manifest.**46 ```47 MANIFEST=$RUN_DIR/run.json48 ```49 Refuse to proceed if it doesn't exist; surface a helpful message50 pointing the user at the run-dir convention (`runs/<workflow>_<ts>/`).51522. **Parse the manifest** (Python helper):53 ```54 workflow=$(jq -r .workflow $MANIFEST)55 container_name=... # not directly in run.json today; the skill that56 # launched stored it in run.json under57 # container.name during launch (see below note).58 logs_dir=$(jq -r .logs_dir $MANIFEST)59 image_tag=$(jq -r .container.image_tag $MANIFEST)60 started_at=$(jq -r .started_at $MANIFEST)61 ```62633. **Query docker for container state.**64 ```65 docker ps --filter "name=$container_name" --format \66 '{{.ID}}\t{{.Status}}\t{{.CreatedAt}}'67 ```68 If absent, fall back to `docker inspect $container_name --format69 '{{.State.Status}} (exit {{.State.ExitCode}})'` to see whether the70 container exited (ok or failed) or was removed (`--rm` after exit).71724. **Find the live log file.**73 ```74 case "$workflow" in75 continue-pretrain|pretrain-scratch) LOG=$logs_dir/pretrain_ddp.log ;;76 finetune) LOG=$logs_dir/finetune.log ;;77 *) LOG=$(ls -1t $logs_dir/*.log 2>/dev/null | head -n 1) ;;78 esac79 ```80 The manifest's `workflow` field disambiguates pretrain (`pretrain_ddp.log`)81 from finetune (`finetune.log`). Other workflows fall back to the82 most-recently-modified `.log` in `$logs_dir`.83845. **Show the latest progress.**85 - `tail -n $LINES $LOG` for the raw recent output.86 - Parse the last few progress lines and surface a human-friendly87 summary. The format differs per workflow:88 - Pretrain: epoch / step / val_loss89 ```90 Current epoch: 12/100 step: 4523/9000 val_loss: 0.832 (best 0.821 @ step 4100)91 ```92 - Finetune: fold / epoch / val_<metric> (e.g. val_mae for regression,93 val_auc for classification — read `args_applied.metric` from run.json)94 ```95 Fold 0 epoch 12/30 val_mae 0.187 (best 0.182 @ epoch 9)96 ```97 ```98 Wall-clock: 1h 23m since started_at; ETA ~6h remaining.99 ```1001016. **Final test-metrics block (finetune, on completion).** If `workflow` is102 `finetune` AND the container has exited cleanly (`State.Status=exited`,103 `ExitCode=0`) AND `$RUN_DIR/ckpt/fold_*/test_result.csv` exists, parse it104 and emit a per-task metric table:105 ```106 Final test metrics (per task):107 Target MAE108 HLM_clearance 0.187109 RLM_clearance 0.213110 MDR1-MDCK_efflux 0.241111 solubility_pH6.8 0.156112 ```113 The metric column matches `args_applied.metric` (mae for regression, auc114 for classification, etc.). For multi-fold or ensemble runs, average across115 folds/models and note `± std` if std > 0. Skip silently if no116 `test_result.csv` exists (run incomplete or no test split was emitted).1171187. **If `--follow`, stream live logs.**119 ```120 docker logs -f $container_name121 ```122 Wraps until ^C.1231248. **Stop / cleanup hints** (printed at end of one-shot mode):125 ```126 To stop: docker stop $container_name127 To remove: docker rm $container_name128 To re-run: `$(jq -r .cmd_replay $MANIFEST)`129 ```130131## Hard rules132133- **Read-only on the user's data.** Never modify `run.json`, never touch the134 container's checkpoint dir. The monitor only inspects.135- **Don't kill the container without explicit user instruction.** If the136 user asks to stop, run `docker stop`; if they ask to abandon, leave it137 running and just exit.138- **Don't pull or modify the kermt image.** The monitor only reads.139- **JSON output mode is non-interactive.** Skip the "press ^C to exit"140 prompts and emit a single JSON document so the parent agent can pipe it.141142## Note on container_name plumbing143144The run.json schema as currently written does not yet include the launched145container name — `kermt_run_detached` prints it to stdout but the runner146script doesn't capture it into run.json. The monitor falls back to a147filesystem-based lookup: list `runs/<workflow>_*/` directories and match by148mtime; or accept `--container <name>` explicitly. Follow-up: have the149launching skill record container name into run.json before exiting.150151## Output (text mode, default)152153```154KERMT continue-pretrain · runs/continue-pretrain_2026-05-17T10-23Z155 Container : kermt-continue-pretrain-… (Up 1 hour, status: running)156 Image : kermt:latest@sha256:…157 Repo : 2fe00f9 (clean)158 Started : 2026-05-17T10:23:14Z (1h 23m ago)159 Workflow : continue-pretrain, pretrain_mode=hybrid, world_size=2160161 Latest log (last 50 lines from $LOG):162 [Epoch 12/100] step 4523/9000 loss 0.832 lr 1.2e-4163 [val] step 4100 val_loss 0.821 (new best)164 ...165166 Progress: epoch 12/100, ~12% done. ETA ~6h.167 TensorBoard: tensorboard --logdir $RUN_DIR/logs/tb168 Replay command: $(jq -r .cmd_replay $RUN_DIR/run.json)169```