Record everything now, ask questions later!
+con-duct wraps any command, with no code changes, and records stdout, stderr, and resource statistics. Run duct <cmd> (short for con-duct run <cmd>). Later, you or your agent can find, query, and plot those records with con-duct ls and con-duct plot. Lightweight and open source (MIT).
Search is a Python expression
+Filter on any of 40+ fields with -e, combine them, do arithmetic, match with regular expressions; sort on any field with --sort-by.
What failed?
+$ con-duct ls runs/* --colors -e "exit_code != 0" +PREFIX COMMAND EXIT_CODE WALL_CLOCK_TIME PEAK_RSS +runs/s5cmd_ ../s5cmd/s5cmd --dry-run sync s3://dandiarchive/* dandiarchive/ 137 26657.861 sec 64.5 GB
Over 10 GB and over five hours:
+$ con-duct ls runs/* --colors -F peak_rss wall_clock_time -e "peak_rss > 10e9 and wall_clock_time > 5*3600" +PREFIX PEAK_RSS WALL_CLOCK_TIME +runs/fmriprep-ds000030_ 16.9 GB 18809.983 sec +runs/fmriprep-ds002785_ 25.7 GB 29352.951 sec +runs/mriqc_ 11.5 GB 61324.997 sec +runs/s5cmd_ 64.5 GB 26657.861 sec
Regular expressions on any text field:
+$ con-duct ls runs/* --colors -e "re.search('fmriprep', command)" -F peak_rss
+PREFIX PEAK_RSS
+runs/fmriprep-ds000030_ 16.9 GB
+runs/fmriprep-ds002785_ 25.7 GBTry it
+$ pip install 'con-duct[all]' # or conda +$ duct sleep 2 +2026-09-30T16:03:21-0500 [INFO ] con-duct: duct 0.23.0 is executing 'sleep 2'... +2026-09-30T16:03:21-0500 [INFO ] con-duct: Log files will be written to .duct/logs/2026.09.30T16.03.21-8486_ +2026-09-30T16:03:23-0500 [INFO ] con-duct: Summary: +Exit Code: 0 +Command: sleep 2 +Log files location: .duct/logs/2026.09.30T16.03.21-8486_ +Wall Clock Time: 2.002 sec +Memory Peak Usage (RSS): 2.2 MB +Memory Average Usage (RSS): 2.2 MB +Memory Peak Percentage: 0.00% +Memory Average Percentage: 0.00% +CPU Peak Usage: 0.00% +Average CPU Usage: 0.00%+
You drive, the agent reads the records directly
+$ duct singularity run mycontainer.sif+
Later, ask your agent; it reads the duct records itself.
+mycontainer.sif run exited 1 after 38 min. I found the problem, and the solution is clear: we need to ...The agent drives, you read the summary
+The agent writes a reproducer and runs it under duct, numbering each step in the tag:
+$ duct --f-t 0 -p logs/1_ -m "1: reproduce user's bug" ./repro.sh +$ duct --f-t 0 -p logs/2_ -m "2: test hypothesis" ./repro.sh +$ duct --f-t 0 -p logs/3_ -m "3: verify fix" ./repro.sh+
The agent reports “fixed”. The evidence in the logs:
+$ con-duct ls logs/*info.json --colors -F message exit_code +PREFIX MESSAGE EXIT_CODE +logs/1_ 1: reproduce user's bug 1 +logs/2_ 2: test hypothesis 1 +logs/3_ 3: verify fix 0
Plot resource usage
+$ con-duct plot <prefix>info.json -o plot.png --cpu ps-cpu-timepoint+
DataLad and provenance 
+ datalad run "duct <cmd>" commits the command, the duct record, and the resulting diff together:
$ datalad run "duct bash -c 'echo hello > hello.txt'"
+$ git show --name-only
+commit cbc27e2dc593ac90da0dfb87e8f941e52ad0906a
+Author: Austin Macdonald <austin@dartmouth.edu>
+Date: Wed Sep 30 16:12:52 2026 -0500
+
+ [DATALAD RUNCMD] duct bash -c 'echo hello > hello.txt'
+
+ === Do not change lines below ===
+ {
+ "chain": [],
+ "cmd": "duct bash -c 'echo hello > hello.txt'",
+ "dsid": "a4b98bfd-4329-4253-bc16-738eaee5f995",
+ "exit": 0,
+ "extra_inputs": [],
+ "inputs": [],
+ "outputs": [],
+ "pwd": "."
+ }
+ ^^^ Do not change lines above ^^^
+
+.duct/logs/2026.09.30T16.12.52-13909_info.json
+.duct/logs/2026.09.30T16.12.52-13909_stderr
+.duct/logs/2026.09.30T16.12.52-13909_stdout
+.duct/logs/2026.09.30T16.12.52-13909_usage.jsonl
+hello.txt
+ Adopted inside tools
+Tools wrap their own runs so users get records without asking.
+-
+
- BABS github.com/PennLINC/babs
Wraps every BIDS App job on SLURM and copies the record next to each subject's results. Why: per-subject peak memory and wall time to size the next batch; the app's stdout and stderr, with its warnings and other internals, for inspecting any job later.
+ - ReproNim/containers github.com/ReproNim/containers
REPRONIM_USE_DUCT=1wraps each Singularity container run, e.g. underdatalad containers-run. Why: a resource record for every containerized app run, saved with its results.
+ - ReproStim github.com/ReproNim/reprostim
The video-capture app wraps eachffmpegrecording (on in the default config); the record sits next to the video. Why: resource use for long, unattended capture sessions.
+
Comparison to related tools
+tee | GNU time | duct | workflow managers Snakemake, Nextflow | experiment trackers MLflow, W&B | |
|---|---|---|---|---|---|
| wrap any command, no code changes | |||||
| works anywhere | |||||
| stdout/stderr kept | |||||
| exit code, invocation, environment | |||||
| peak memory, CPU, whole tree | |||||
| time series, not just a peak | |||||
| queryable across runs |
yes · partial · no.
+When the others are better: many steps with declared inputs and outputs (a workflow manager knows the graph, reruns only what changed); comparing runs by parameters and metrics (a tracker keeps them with the artifacts, side by side).