diff --git a/2026-usrse/.gitignore b/2026-usrse/.gitignore index ec479408..45d040c2 100644 --- a/2026-usrse/.gitignore +++ b/2026-usrse/.gitignore @@ -1,3 +1,4 @@ talk-proposal-draft.html *.pdf 2026-usrse-con-talk-abstract.html +!duct-poster.pdf diff --git a/2026-usrse/duct-poster-assets/fmriprep-ds002785-sub-0001-plot.svg b/2026-usrse/duct-poster-assets/fmriprep-ds002785-sub-0001-plot.svg new file mode 120000 index 00000000..cc333c32 --- /dev/null +++ b/2026-usrse/duct-poster-assets/fmriprep-ds002785-sub-0001-plot.svg @@ -0,0 +1 @@ +../../.git/annex/objects/1W/WW/MD5E-s554396--f1a65fe7d9ded04429d25c9f8916f66f.svg/MD5E-s554396--f1a65fe7d9ded04429d25c9f8916f66f.svg \ No newline at end of file diff --git a/2026-usrse/duct-poster-assets/logos/D-Pine_RGB.svg b/2026-usrse/duct-poster-assets/logos/D-Pine_RGB.svg new file mode 100644 index 00000000..ac6d4c3b --- /dev/null +++ b/2026-usrse/duct-poster-assets/logos/D-Pine_RGB.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/2026-usrse/duct-poster-assets/logos/con_logo_blue_big_tight.svg b/2026-usrse/duct-poster-assets/logos/con_logo_blue_big_tight.svg new file mode 100644 index 00000000..3a023d9d --- /dev/null +++ b/2026-usrse/duct-poster-assets/logos/con_logo_blue_big_tight.svg @@ -0,0 +1 @@ +/annex/objects/MD5E-s125807--60c9b67a52c2afdf00c9ceccea01e6ef.svg diff --git a/2026-usrse/duct-poster-assets/logos/duct_logo.svg b/2026-usrse/duct-poster-assets/logos/duct_logo.svg new file mode 100644 index 00000000..9f330545 --- /dev/null +++ b/2026-usrse/duct-poster-assets/logos/duct_logo.svg @@ -0,0 +1 @@ +/annex/objects/MD5E-s716776--0a66b516151438af418da744424d7f46.svg diff --git a/2026-usrse/duct-poster-assets/qr/qr-github-con-duct.svg b/2026-usrse/duct-poster-assets/qr/qr-github-con-duct.svg new file mode 100644 index 00000000..0edbc2c3 --- /dev/null +++ b/2026-usrse/duct-poster-assets/qr/qr-github-con-duct.svg @@ -0,0 +1,2 @@ + + diff --git a/2026-usrse/duct-poster-assets/qr/qr-zenodo-abstract.svg b/2026-usrse/duct-poster-assets/qr/qr-zenodo-abstract.svg new file mode 100644 index 00000000..02d33343 --- /dev/null +++ b/2026-usrse/duct-poster-assets/qr/qr-zenodo-abstract.svg @@ -0,0 +1,2 @@ + + diff --git a/2026-usrse/duct-poster.html b/2026-usrse/duct-poster.html new file mode 100644 index 00000000..c8d7bc8b --- /dev/null +++ b/2026-usrse/duct-poster.html @@ -0,0 +1,387 @@ + + + + +Agent-friendly Provenance Capture with con-duct + + + + +
+
+ + +
+
+
Austin Macdonald
Cody C. Baker
John A. Lee
Yaroslav O. Halchenko
+
con-duct logo
+
+

Agent-friendly Provenance Capture with con-duct

+

Center for Open Neuroscience, Psychological and Brain Sciences, Dartmouth College  ·  RRID:SCR_025436

+
+
+
QR code: https://github.com/con/ductcode
+
QR code: https://doi.org/10.5281/zenodo.22833931abstract
+
+
+
+ +
+ +
+
+

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).

+
+ +
+ +
+
+
+ + + + + + + + + + + + + +
Capture is easy
+
Wrap any command. It runs unchanged, prints as usual, and leaves four files.
+
you, or your agent
+
$ duct <command>
+
and samples memory and CPU of the whole process tree, every second
+
your terminal,
as before
+
one record per run
+
stdout
everything it printed
+
stderr
every warning and error
+
info.json
command, exit code, peaks
+
usage.jsonl
memory and CPU over time
+
Questions later (Human or Agent)
+
Which runs failed?
con-duct ls -e "exit_code != 0"
+
Why did it fail?
tail <prefix>_stderr
+
How much did it need?
con-duct plot <prefix>info.json
+
a table of just the matching runs
+
just the lines that matter, so the agent's context stays small
+
memory and CPU over time, for any run
+
+
+
+ + +
+ + +
+
+

Try 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.

+
+
user:
my container run failed, why?
+
claude:
$ con-duct ls -e "exit_code != 0"$ tail <prefix>_stderrYour 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
+ con-duct plot of one fMRIPrep run +
+
+ + +
+
+

DataLad and provenance DataLad

+

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=1 wraps each Singularity container run, e.g. under datalad 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 each ffmpeg recording (on in the default config); the record sits next to the video. Why: resource use for long, unattended capture sessions.
  • +
+
+ +
+

Comparison to related tools

+ + + + + + + + + + +
teeGNU timeductworkflow 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).

+
+
+ + + +
+
+ + + + diff --git a/2026-usrse/duct-poster.pdf b/2026-usrse/duct-poster.pdf new file mode 120000 index 00000000..6b5d3b2c --- /dev/null +++ b/2026-usrse/duct-poster.pdf @@ -0,0 +1 @@ +../.git/annex/objects/Qp/Mv/MD5E-s823425--699f5a5711ec3559855a15bba340f3f1.pdf/MD5E-s823425--699f5a5711ec3559855a15bba340f3f1.pdf \ No newline at end of file diff --git a/REUSE.toml b/REUSE.toml index 7e97e6fb..9ca81436 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -169,7 +169,6 @@ precedence = "aggregate" SPDX-FileCopyrightText = "NOASSERTION" SPDX-License-Identifier = "LicenseRef-ThirdPartyMixed" - # === 3. Third-party logos and trademarks =================================== # Funder, institution and collaborating-project marks, reproduced for @@ -210,6 +209,9 @@ path = [ "pics/openneuro.svg", "pics/repronim-logo-vertical.svg", "pics/twitter.png", + # Dartmouth's D-Pine, converted from the EPS in Dartmouth's brand kit for + # the US-RSE'26 duct poster. Dartmouth's guidelines forbid altering it. + "2026-usrse/duct-poster-assets/logos/D-Pine_*.svg", ] precedence = "aggregate" SPDX-FileCopyrightText = "NOASSERTION"