8.3. Run directory
|
|
Experimental Feature
The run directory’s contents are not settled. What a run writes today is
narrower than what this section describes: a run matching the layout below
produces the per-label directories and Treat the directory as a place to find logs, not as a stable on-disk format to parse. The file names and the layout may change. |
Every persistent-logging invocation of a morloc-built executable is
one run. The run gets a unique id and a directory on disk holding
the run’s artifacts: a top-level log file teed from stderr, per-label
log files for log: true labels, and a summary.json sentinel.
The directory is opt-in. A bare ./my_program … invocation never
creates one; pass --log-dir <path> (or set MORLOC_LOG_DIR) to
activate. Interactive use stays clutter-free; cron jobs and servers
opt in and get the structured artifacts.
The directory layout, after a run with --log-dir runs/ and at least
one labeled term with log: true, looks like:
. runs/20260608T172304Z-a3f9c41b/
|
|-- a
| `-- log
|-- b
| `-- log
|-- log
`-- summary.json
The run id is {utc-iso8601-second}-{8-hex-random} — lexically
sortable, so ls orders runs chronologically. The top-level log is
the prologue / epilogue / per-label-line tee; per-label log files
hold only the per-label start / pass / fail emissions for that label.
8.3.1. Activation knobs
| Knob | Effect |
|---|---|
|
Creates a per-run subdir under |
|
Writes the structured |
|
Suppress all morloc-emitted log lines — prologue, epilogue, per
-label start / pass / fail — at the source. Lines are never
generated, so they neither hit stderr nor tee into the rundir’s
|
8.3.2. summary.json
Presence of summary.json means the run reached a clean exit (good
or bad). The fields are minimal and stable:
{
"status": "ok",
"exit_code": 0,
"command": "align_reads",
"run_id": "20260608T172304Z-a3f9c41b",
"started_at": "2026-06-08T17:23:04Z",
"finished_at": "2026-06-08T17:25:18.231Z",
"wall_ms": 134231,
"morloc_version": "0.88.0",
"error": null
}
On a failing run, status is "fail", exit_code is nonzero, and
error carries the error packet’s message (often a foreign-language
traceback). The write is atomic (tmp + rename) so a watcher polling
for the file never sees a partial JSON.
A wrapper that runs morloc as a workflow step can poll for --summary
existence to detect completion and read status to branch:
./align --summary $WORK/align.summary.json --log-dir $WORK/logs @ \
reads.fastq.gz reference.fa
case "$(jq -r .status $WORK/align.summary.json)" in
ok) ./next_step ;;
fail) echo "align failed: $(jq -r .error $WORK/align.summary.json)" ;;
esac
SIGKILL / OOM / kernel panic bypass the writer; the wrapper should have a timeout fallback for those.
8.3.3. Where the directory lives
Resolution order, highest precedence first:
| Source | Notes |
|---|---|
|
Activation knob and base directory. The run lands at
|
Inheritance from a parent morloc process |
If a morloc-built program launches another morloc-built program,
the child reuses the parent’s run dir so logs interleave naturally.
The check requires the parent’s owning PID to match |
There is no fallback default base directory: persistent logging is
strictly opt-in. A MORLOC_RUN_DIR set without the matching
MORLOC_RUN_PARENT_PID is treated as stale and ignored.
8.3.4. Cleanup
Morloc never deletes a past run directory. Old runs accumulate under
the base until you remove them. A simple housekeeping cron (or a
one-off find <log-base> -mtime +30 -delete) is sufficient.
8.3.5. Prologue and epilogue
Two top-level YAML keys add run-scope log entries. They behave like
the per-label log-template: always emitted to stderr when defined,
tee’d to the rundir’s log when --log-dir is active, suppressed
entirely under --quiet.
prologue: "[{c:bold}morloc{c:reset}] {name} v{version} start {started_at}"
epilogue:
ok: "[morloc] {name} ok in {runtime}s"
fail: "[morloc] {name} FAILED ({exit_code}) in {runtime}s: {error}"
Two epilogue branches so the success line doesn’t have to render an
empty {error} and the failure line can include fields the success
line lacks. The compiler picks the matching branch based on the
run’s exit status.
Available placeholders:
| Placeholder | Where it comes from |
|---|---|
|
Entry-point morloc module name. Substituted at compile time. |
|
Program version from |
|
Compiler version. Compile time. |
|
Subcommand the user invoked. Runtime. |
|
Per-run unique id. Empty when no rundir is materialized. Runtime. |
|
ISO 8601 timestamps. Runtime. |
|
Wall seconds, six decimal places. Runtime (epilogue only). |
|
Nexus PID. Runtime. |
|
|
|
Integer exit code. Runtime ( |
|
Error packet contents (may be multi-line). Runtime ( |
|
ANSI color codes. Compile time. The runtime strips them when stderr is not a TTY or |
8.3.6. Nested invocations
If a morloc-built program launches another morloc-built program, the
child inherits the parent’s run directory when the parent activated
one. Both programs' logs land under the same run id, so the user’s
tail -f / grep tooling sees the full workflow as one entity rather
than two. Pool processes are children of the nexus and use the same
mechanism, which is why every pool’s log emission ends up in the
expected per-label log file.
The inheritance check is robust against a stale MORLOC_RUN_DIR left
over in a shell environment from a previous run: the child only
inherits when the run-dir’s owning PID matches its actual parent. A
mismatched PID falls back to no run dir (the child is then a normal,
non-persistent invocation).