Configuration¶
One YAML file describes a whole BacFlux run. There are no command-line switches for analysis parameters, so the config is a complete record of what was done — worth archiving next to the results.
The shipped template is config/config.yaml. It is a template: the empty strings
are placeholders that must be filled in before anything runs. This page follows
the file's own order, and every section is tagged with the modes that read it —
[all modes], [nanopore|hybrid] and so on. Keys belonging to a mode that is
not running are ignored entirely.
Terms used on this page
| YAML | the config file's plain-text format; indentation is significant and tabs are refused |
| EFSA | European Food Safety Authority, whose reporting thresholds the ABRicate leg applies |
| GTDB | Genome Taxonomy Database, the reference GTDB-Tk places each genome in |
| AMR | antimicrobial resistance |
| IS | insertion sequence, the smallest kind of mobile element |
| ICE | integrative and conjugative element — sits in the chromosome and can move itself to another cell |
| IME | integrative mobilisable element — the same, but needs a helper element to move |
| AICE | actinomycete integrative and conjugative element, a third class |
| att site | the short repeat marking an integrated element's ends |
| HMM | hidden Markov model, a statistical profile used to recognise a protein family |
How the file is loaded¶
The Snakefile declares no configfile: of its own, so a config must be named
on the command line:
Start it as a copy of the template, which .gitignore already covers so your
edits survive a git pull:
Your file is the whole config — nothing fills in a missing key
Snakemake merges a hard-coded configfile: with whatever --configfile
supplies rather than replacing it. Had the Snakefile named the shipped
template as a default, every key you deleted from your own copy would be
quietly refilled from the template — with its empty-string placeholders and
its default download links — and the run would carry on against a path that
does not exist.
So there is no base layer. Delete a required key and the run stops naming it, which is the intended behaviour. Copy the whole template and edit it; do not write a short config from scratch.
Launched with no config at all, the run stops in seconds:
[BacFlux] No configuration supplied. Pass one explicitly, e.g.:
snakemake --sdm conda --cores N --configfile config/config.yaml
A single top-level key can still be overridden per run without editing the file
— --config mode=contigs — but anything nested belongs in the file.
Paths. Every path in the config, directories.output_dir included,
resolves against the directory you launched Snakemake from — not against the
config file. Since you are told to run from the repository root, a relative
output_dir puts your results inside your clone:
output_dir |
where the results go |
|---|---|
/data/results/run1 |
exactly there |
results/run1 |
inside the clone, at BacFlux/results/run1 |
Neither is wrong, but only one of them is usually what you meant. output_dir is
the one path BacFlux expands to an absolute form for you, so it behaves
predictably once set; the rest (input.*_dir, directories.*_db, the
decontamination file paths) are used exactly as written. Give all of them as
absolute paths unless you are certain.
SPAdes k-mer sizes¶
Applies to illumina and hybrid, the two modes that run SPAdes. Leave it on auto
unless you have a reason not to.
auto passes no -k at all, which is what lets SPAdes size the ladder from the read
length it measures for itself:
| read length | ladder |
|---|---|
| ≥ 250 bp | 21, 33, 55, 77, 99, 127 |
| ≥ 150 bp | 21, 33, 55, 77 |
| shorter | 21, 33, 55 |
Those three presets have been in SPAdes since at least v3.9 (2016) and are unchanged in v4.3.0, the version BacFlux pins.
Giving an explicit list disables that selection. The list is then used whatever the
reads look like, and the cost is easy to miss: k-mer coverage is not read coverage but
read_cov × (L − k + 1) / L. With 145 bp reads, k = 127 keeps only about
13% of your coverage, and SPAdes prints the k-mer coverage it saw for each k in
its own log — worth reading once on your own data:
Assembling dataset ... with K=77
Average coverage = ...
Assembling dataset ... with K=127
Average coverage = ... <- typically a small fraction of the K=21 figure
SPAdes assembles iteratively and the final contigs come from the largest k, so an over-long ladder means the graph you deliver is the least supported one in the run.
When to set it explicitly
Two good reasons: pinning the ladder so an assembly is reproducible against a future SPAdes whose defaults have moved, or overriding SPAdes on data whose read length misrepresents its usable length. Values must be odd, ascending and below 128 — SPAdes' own limit — and are checked before any job starts.
Whichever path is taken, the ladder that ran is recorded in the assembly log.
What is checked before anything starts¶
BacFlux validates the config while Snakemake is still reading the workflow —
seconds in, before a single job is scheduled. A dry run (snakemake -n)
therefore catches almost every config mistake. The checks, in the order the file
lists the keys:
| Key | Check | What happens |
|---|---|---|
mode |
present, and one of the four | Missing: stops with a KeyError. Present but unrecognised: stops, listing the four |
input.<mode>_dir |
set, and an existing directory | Stops naming the key |
| input files | at least one, all sharing one extension, extension recognised | Stops listing the extensions it found |
| sample names | none of the refused characters listed under Input | Stops naming the offending sample |
| Illumina samples | every R1 has its R2 | Stops naming the missing mate |
| hybrid samples | present in both input directories | Stops listing samples missing from either side |
directories.output_dir and the five required databases |
present | Stops with a KeyError naming the key |
directories.checkv_db |
holds exactly one */genome_db/checkv_reps.faa |
Stops, and says to give the parent directory |
directories.vs2_db, antismash_db, dbcan_db, card_db, genomad_db |
a directory, holding a file only that database has | Stops naming the missing probe file |
directories.genomad_db |
every file readable by you; metadata table has the current column set | Stops listing unreadable files, or explaining the schema mismatch |
links.dbcan_link |
set, and a .tar.gz |
Stops |
links.card_link, links.phix_link |
set whenever the mode has short reads | Stops naming the key and the mode |
links.checkv_link, links.genomad_link |
a .tar.gz if set |
Stops |
parameters.eggnog.dbmem |
resources.ram_gb can hold one job |
Stops, and names the number to raise |
parameters.spades_kmers |
auto, or odd ascending integers below 128 |
Stops, naming the offending value |
parameters.decontamination.mode |
one of auto, include, exclude, off |
Stops |
parameters.<mode>.flye_input_mode |
one of auto, nano-raw, nano-hq |
Stops |
phage.caller |
virsorter2 or genomad |
Stops |
mobilome.coverage_profile |
a fraction in (0, 1] |
Stops, and says it is a fraction, not a percentage |
mobilome.icescan.run |
has a url or a dir to use |
Stops |
mobilome.isosdb.fasta_url |
family_map_url set alongside it |
Stops |
| any boolean key | recognisable as true/false | Stops naming the value |
Database contents are not opened at parse time, so a placeholder path in
bakta_db or blast_db passes a dry run and fails later. The optional
directories.*_db overrides are the exception — each is validated the moment it
is set, which is why config/config_v2_test_examples.yaml leaves them out of its
four minimal per-mode dry-run configs.
What the run prints back while parsing is worth reading: the active mode, the phage caller, the decontamination policy, one line per discovered sample, and — where they apply — the eggNOG memory flag to add and the mobilome module's banner.
The keys that define a run¶
Most of the file can stay at its defaults. These are the ones that describe what the analysis actually is:
| Setting | Choices |
|---|---|
mode |
illumina, nanopore, hybrid, contigs |
input.illumina_dir / nanopore_dir / contigs_dir |
where the raw inputs are, per technology |
directories.output_dir |
where results go |
directories.bakta_db, blast_db, eggnog_db, gtdbtk_db, platon_db |
the five databases you download yourself |
links.dbcan_link, and phix_link + card_link in the short-read modes |
the downloads the workflow performs |
Mode [all modes]¶
| Key | What it does | Default | Notes |
|---|---|---|---|
mode |
Selects the front-end rules (workflow/rules/<mode>/*.smk) and the sample-discovery logic |
illumina |
Matched case-insensitively. Anything other than the four names stops the run at parse time. |
The mode is never inferred from the data. Everything from taxonomy onwards is one shared set of rules, so the mode changes only how the genome is produced — see choosing a mode.
Input [all modes]¶
input:
illumina_dir: "" # {sample}_R1.<ext> + {sample}_R2.<ext>
nanopore_dir: "" # {sample}_ont.<ext>
contigs_dir: "" # {sample}.<fasta|fa|fna>
| Key | Read by | What it holds | Notes |
|---|---|---|---|
illumina_dir |
illumina, hybrid |
Paired-end reads named {sample}_R1.<ext> and {sample}_R2.<ext> |
Discovery keys off R1; the missing mate of any sample stops the run. |
nanopore_dir |
nanopore, hybrid |
Oxford Nanopore Technologies (ONT) reads named {sample}_ont.<ext> |
|
contigs_dir |
contigs |
Finished assemblies named {sample}.<ext> |
A mode reads only the directories it needs; the others can stay empty.
Extensions. Reads may be fastq, fq, fastq.gz or fq.gz; assemblies may
be fasta, fa or fna. Every file of one kind in one batch must share a
single extension, because {sample}.{extension} would otherwise be ambiguous —
a mixture stops the run and prints the extensions it found.
Sample names. The name becomes a filename, a Snakemake wildcard, a Bakta locus tag and a label in the report, so a handful of characters are refused outright, anywhere in the name:
* # @ % ^ / ! (space) ? & : ; | < >
Everything else is accepted, underscores included. In contigs mode the last
dot splits off the extension, so my.genome.fasta is read as sample
my.genome; earlier dots stay part of the name and flow safely through paths and
locus tags.
Directories [all modes]¶
directories:
output_dir: ""
bakta_db: ""
blast_db: ""
eggnog_db: ""
gtdbtk_db: ""
platon_db: ""
# optional local copies, below
Output¶
| Key | What it does | Notes |
|---|---|---|
output_dir |
Root for every result and log, plus every database the workflow downloads for itself | Created if absent. May be relative — it is the one path resolved to an absolute one automatically. |
Reusing one output directory across runs is what stops the self-downloaded
databases being fetched again. Conda environments do not live here: Snakemake
puts them under .snakemake/conda in the directory you launch from, so launching
from the same place each time is what avoids rebuilding them. See
running BacFlux.
The five databases you provide¶
Required in every mode. None of them has a fallback behind it, so a missing key stops the run rather than quietly resolving to something wrong.
| Key | What it points at |
|---|---|
bakta_db |
A Bakta database, light or full. Also supplies the AMRFinderPlus database at amrfinderplus-db/latest/, which the mobilome module uses. |
blast_db |
The directory holding the NCBI nucleotide database; the subfolder named by parameters.nt_version is what the contamination screen blasts against. |
eggnog_db |
The eggNOG-mapper database directory. BacFlux pins eggnog-mapper 2.1.15, which annotates against eggNOG 5.0, database release emapperdb-5.0.2. The version is fixed by the tool rather than chosen by you: download_eggnog_data.py builds its URL from __DB_VERSION__ in the installed release, so the database cannot drift away from the pin. |
gtdbtk_db |
A GTDB-Tk release directory. |
platon_db |
The Platon plasmid database, release v1.5.0 (DOI 10.5281/zenodo.4066768, 2020-10-05). It is versioned separately from Platon itself: v1.5.0 is the current database for every Platon from 1.5.0 onwards, including the 1.8 pinned here, so the database number trailing the tool number is expected rather than a sign of drift. |
Versions and download recipes are on reference databases.
Optional local copies of the downloaded databases¶
Six databases the workflow can fetch itself. Point the matching key at a copy you
already hold and nothing is downloaded — the key overrides both the links.*
entry and, where the tool has one, its own downloader. These paths are only ever
read: BacFlux builds a local symlink view rather than touching your directory.
| Key | Point it at | Read when | Probe file checked at parse time |
|---|---|---|---|
checkv_db |
The parent directory holding the versioned folder, e.g. /path/to/checkv/ containing checkv-db-v1.5/ |
all modes | exactly one */genome_db/checkv_reps.faa |
vs2_db |
What virsorter setup produced |
phage.caller: virsorter2 |
Done_all_setup |
antismash_db |
What download-antismash-databases produced |
all modes | clusterblast/ |
dbcan_db |
An extracted dbCAN database matching links.dbcan_link |
all modes | dbCAN.hmm |
card_db |
An extracted CARD database | illumina, hybrid |
aro_index.tsv |
genomad_db |
The genomad_db/ directory genomad download-database produced |
phage.caller: genomad |
version.txt |
Why CheckV gets a local view rather than being used in place
CheckV needs a DIAMOND index (genome_db/checkv_reps.dmnd) that the official
archive does not ship — it is built locally after unpacking. Whatever index
sits in a shared database was built by whichever DIAMOND that site happened
to have, and DIAMOND refuses to run a format it does not recognise: CheckV
then dies at "Running DIAMOND blastp search… DIAMOND task failed", after
the contamination stage has already succeeded, which reads like a CheckV bug.
A shared database is also usually not writable, so rebuilding in place is not
an option. BacFlux therefore symlinks the big read-only files and builds the
index with its own DIAMOND — about 950 MB and a couple of minutes, once per
output directory.
geNomad's database gets two extra checks
A shared copy can be partly readable: on this machine 11 of 27 files were
mode 0640 — version.txt, both hallmark tables and the whole
genomad_integrase_db set — while the big files beside them were
world-readable. A file can be listed and still refuse to open, so checking
that it exists is not enough: the run would start and die minutes later on a
permission error. Every file is therefore checked for read access, and the
offenders are named.
The second check is on the database schema. A geNomad database is coupled
to the geNomad release, and geNomad does not verify this: it parses
genomad_marker_metadata.tsv positionally from the last columns, so an older
database shifts every field by one and geNomad dies on
ValueError: invalid literal for int() with base 10: '1398618at2' — a marker
accession being read as a hallmark count. Verified here on 2026-07-24 with
database v1.7 against geNomad 1.12.0. BacFlux tests for the trailing
PREVIOUS_MARKER_ACCESSION column instead of guessing at version numbers.
Links [all modes]¶
links:
phix_link: ""
dbcan_link: "https://zenodo.org/records/18622157/files/dbcan_db_v5.1.2.tar.gz"
card_link: ""
checkv_link: "https://zenodo.org/records/21510554/files/checkv-db-v1.5.tar.gz"
genomad_link: "https://zenodo.org/records/14886553/files/genomad_db_v1.9.tar.gz"
genomad_md5: "67244b528bb8bed464d1ca147136d33e"
| Key | Required in | Default | Notes |
|---|---|---|---|
phix_link |
illumina, hybrid |
unset | The PhiX genome Illumina spikes into nearly every lane. Those reads are real sequence from another organism and are mapped out before assembly. |
dbcan_link |
all modes | dbCAN 5.1.2 on Zenodo | Must be a .tar.gz: the checksum URL and the extracted folder name are both derived from it, so the folder always matches the release you pointed at. |
card_link |
illumina, hybrid |
unset | CARD, for the read-mapping AMR leg. Reads are immune to assembly collapse, so this leg sees determinants the contig-based leg can miss. |
checkv_link |
optional, all modes | a Zenodo mirror of CheckV's own database | Empty means CheckV downloads its own. Ignored when directories.checkv_db is set — and BacFlux says so rather than ignoring the key silently. |
genomad_link |
optional, phage.caller: genomad |
the geNomad authors' own Zenodo copy | Must be the database archive (genomad_db_v*.tar.gz), not the HMM or MSA archive beside it on the same record. Empty falls back to geNomad's own downloader. |
genomad_md5 |
with genomad_link |
the MD5 of the default archive | Zenodo publishes an MD5 rather than the .sha256 sidecar the other mirrors carry, so it is configured rather than derived. Change it whenever you change the link — a mismatch stops the run, and a stale hash is worse than none. |
The CheckV and geNomad mirrors exist for one reason: both databases are served
from portal.nersc.gov (NERSC), which is unreachable
often enough to cost real time (it
was down for the whole of 2026-07-22, blocking three validation runs here), and
geNomad's downloader has no option to point elsewhere. Several of these
databases carry terms of their own; BacFlux ships none of the data, and the
licence of each is tabulated on licensing.
Resources [all modes]¶
| Key | What it does | Default | Notes |
|---|---|---|---|
threads |
How many cores the machine has to give. Every CPU-bound rule builds its own request against this number. | 16 |
Nothing needs configuring per rule. |
ram_gb |
The memory budget handed to the rules that can be told a limit. | 64 |
Not enforced by Snakemake — see below. |
These describe your machine. What actually enforces the CPU budget at run time is
--cores N on the command line; set it to the same value as threads, and see
running BacFlux for why --jobs must not be added alongside it.
How threads reaches a rule¶
Rules ask either for all of threads, or for min(threads, N) where their tool
stops scaling at N. Both go into Snakemake's built-in threads:, which is what
makes a plain --cores N a real ceiling.
| Request | Rules |
|---|---|
all of threads |
map_phix, illumina_assembly, ont_assembly, map_contigs, map_sel_contigs |
min(threads, 24) |
blast_contigs, blast_final_contigs, genome_assembly_evaluation, completeness_and_contamination, map_evaluation, taxonomic_assignment, annotation, functional_annotation, secondary_metabolites_analysis, cazyme_gene_cluster, map_amr_db, plasmid_search, viral_identification_virsorter2, viral_quality, genomad_end_to_end, fix_start, short_read_correction, compare_hybrid_assemblies, long_read_consensus (nanopore) |
min(threads, 16) |
trim_adapters, assembly_depth, isosdb_map |
min(threads, 8) |
the NanoPlot rules, long_read_consensus (hybrid), checkv_db, checkv_db_local, and the mobilome rules amrfinderplus, isescan, conjscan, icescan, tncentral_blast, iceberg_blast |
min(threads, 4) |
virsorter2_db |
| none declared | both ABRicate rules, and the small parsing rules that run plain Python |
How ram_gb reaches a rule¶
| Rule | What it gets |
|---|---|
illumina_assembly |
SPAdes -m, a hard ceiling, uncapped |
map_evaluation |
Qualimap's JVM heap, min(ram_gb, 64) GB |
map_amr_db, assembly_depth, isosdb_map |
BBMap's -Xmx, min(ram_gb, 32) GB |
functional_annotation |
42 GB, only when parameters.eggnog.dbmem is on |
ram_gb is a number handed to tools, not a budget Snakemake polices
Snakemake enforces the CPU side on its own, because rules declare the
built-in threads:. Memory figures are gigabytes rather than core counts, so
they sit in resources: under three different names, and are scheduled
against only if the launch line passes the matching one — --resources
mem_gb=N for eggNOG, java_mem=N for Qualimap, ram=N for SPAdes. Without
that, two large jobs running at once can each take their full share. eggNOG
with --dbmem is the one case where this matters enough that BacFlux prints
the exact flag to add.
The three BBMap legs (map_amr_db, assembly_depth, isosdb_map) are not
schedulable at all: their figure is a params: value handed straight to
-Xmx, not a declared resource.
Parameters¶
nt_version [all modes]¶
| Key | What it does | Default | Notes |
|---|---|---|---|
nt_version |
Which subfolder of directories.blast_db the contamination screen blasts against |
core_nt |
nt_prok is the prokaryote-only build: smaller and faster, and enough when contaminants are expected to be bacterial. A config omitting the key falls back to core_nt. |
eggNOG --dbmem [all modes] — optional¶
| Key | What it does | Default | Notes |
|---|---|---|---|
dbmem |
Loads the 39 GB eggnog.db wholly into RAM instead of reading it from disk |
false |
Costs about 42 GB per concurrent eggNOG job. |
eggNOG-mapper's annotation phase does random-access lookups into that SQLite
database once per seed ortholog, which is why functional_annotation is
routinely the last rule still running. --dbmem makes those lookups in-memory.
Two things happen when you switch it on. BacFlux refuses at parse time if
resources.ram_gb cannot hold even one such job, naming the number to raise. And
it prints the exact --resources mem_gb=N flag, with your own ram_gb filled
in, so Snakemake will cap concurrent eggNOG jobs at what your memory actually
holds. Without that flag the run still works; concurrency is then bounded by
cores rather than by RAM. More on the annotation stage:
annotation.
Long-read QC [nanopore|hybrid]¶
This is the biggest lever on small-plasmid recovery. Filtlong ranks reads by
and --keep_percent deletes the bottom of that ranking. A small plasmid cannot
produce reads longer than itself, so its reads sit near the bottom by
construction and are discarded first.
| Key | What it does | Default | Reaches Filtlong in |
|---|---|---|---|
min_length |
Reads shorter than this are discarded outright | 1000 |
both long-read modes |
keep_percent |
Keep the best N% of bases by the score above | 95 |
both long-read modes |
length_weight |
How much length dominates the ranking | 1 (Filtlong's own default) |
hybrid only |
Measured here on Klebsiella pneumoniae TUM24772 (BioProject PRJNA1168299,
closed genome GCA_043950115.1) — a clinical test isolate, not one of the mobilome
positive controls. Its 5,596 bp Col2 plasmid (CP171790.1) was missing from the
assembly while sitting complete in the Illumina data. Of the 603 raw ONT reads
mapping to it (minimap2 -x map-ont, alignment block ≥ 1 kb), the number
surviving each setting pair:
length_weight: 10 |
length_weight: 1 |
|
|---|---|---|
keep_percent: 90 (the old value) |
93 | 413 |
keep_percent: 95 |
500 | 502 |
keep_percent is the key to reach for. At the 95 that now ships, changing
length_weight is worth two reads; at the old 90 it was worth 320. The plasmid
needed both old values together to disappear, and either change on its own brings
most of it back.
length_weight still defaults to 1 because the two keys protect different size
classes. Reads per length band, same sample (the raw set holds 2,954 reads of
1–3 kb and 3,418 of 3–6 kb):
| 1–3 kb | 3–6 kb | |
|---|---|---|
keep_percent: 90, lw: 10 |
0 | 318 |
keep_percent: 95, lw: 10 |
0 | 2,666 |
keep_percent: 90, lw: 1 |
123 | 2,331 |
keep_percent: 95, lw: 1 |
605 | 2,935 |
length_weight: 10 empties the 1–3 kb band whatever keep_percent says. So
keep_percent protects plasmids of a few kb and length_weight protects
plasmids under about 3 kb; this 5.6 kb one happened to sit in keep_percent's
range.
Recovering the reads is necessary, not sufficient
No setting tried here assembled that plasmid — raw unfiltered reads included. ONT ligation prep under-represents small circular plasmids roughly four-fold, and no filter setting undoes that half of the problem. Read this table as "these settings stop the reads being thrown away", not as "and then it assembled".
There is no spelling for "off". The number goes straight to Filtlong's
--keep_percent, so a blank value arrives as the literal None and Filtlong
refuses to start; set 100 to keep everything above min_length, which is the
safest setting for plasmids. Raising them further rarely gains anything — total
bases barely moved in the test (208 → 227 Mb), so what changes is which reads
survive, not how many. If you raise length_weight you are trading small
replicons for chromosome contiguity; say so in your methods. Full treatment:
nanopore mode.
Decontamination [all modes]¶
decontamination:
mode: auto # auto | include | exclude | off
discard_no_hit: true
include_genera:
include_genera_by_sample:
exclude_genera:
exclude_genera_file:
sample_overrides:
After BlobTools assigns every contig a genus, the selector keeps or discards contigs by this policy and writes an audit row for every decision. One policy covers all four modes, and it runs on the draft assembly, before annotation, so a contaminant never reaches CheckM or Bakta.
mode here is the filtering policy, not the pipeline mode
parameters.decontamination.mode and the top-level mode share a name and
nothing else.
| Key | What it does | Default | Notes |
|---|---|---|---|
mode |
auto keeps the most abundant genus BlobTools inferred; include keeps only the genera you list; exclude removes the ones you list; off keeps everything |
auto |
|
discard_no_hit |
Removes contigs BLAST could not place | true in the shipped template |
Omitting the key entirely resolves to false, so keep the line. Applied before the mode logic. In auto it also drops "no-hit" from the vote that picks the dominant genus. |
include_genera |
Genera to keep, for every sample | unset | An inline list: "GenusA;GenusB" or [GenusA, GenusB]. Commas and semicolons both separate. |
include_genera_by_sample |
A file path to a two-column TSV (sample, genus) |
unset | The compact form when many samples each need their own include list. A sample may appear on several rows. |
exclude_genera |
Genera to discard, for every sample | unset | Same shapes as include_genera. |
exclude_genera_file |
A file path to a genus list, one per line | unset | Blank lines and # comments are skipped. |
sample_overrides |
A file path to a per-sample override TSV | unset | Columns sample and mode are required; include_genera, exclude_genera and discard_no_hit may follow. A row replaces the run-wide setting for that one sample. |
The last three are paths to files, not mappings written inline. A YAML block
written there would be stringified and the selector would try to open a file
literally named {}.
An empty cell in the override TSV means keep the global value
Not "match nothing". A row must still carry the full set of tabs where fields are blank, or the columns shift and the wrong value is read.
auto can delete a genuine plasmid, and in hybrid mode it takes the reads with it
auto follows BlobTools' bestsum, which sums bitscores per taxon across
every hit — and that follows database composition rather than biology. On
K. pneumoniae ATCC BAA-2146 it discarded a real 2,014 bp plasmid
(pMYS, NZ_CP006660.1) as Escherichia, because E. coli entries outnumbered
Klebsiella ones 58,709 to 16,253. Broad-host-range plasmids are dropped
precisely because they are mobile and their nearest database relatives sit
in another genus.
In hybrid mode the loss compounds: only the Illumina reads mapping to the
selected contigs become the short-read reference Filtlong scores ONT reads
against, so sequence absent from that reference is discarded before Flye ever
sees it — the plasmid disappears from the assembly, not merely from the
taxonomy table.
Check 02.assembly/{sample}/contaminants/contig_taxonomy_decisions.tsv for
any discarded contig of plausible plasmid size whose assigned genus differs
from the sample's. The fixes are mode: include naming both genera,
mode: off, discard_no_hit: false, or a sample_overrides row for the one
awkward isolate. This is independent of long_read_qc above; both can delete
a plasmid, for different reasons, and both should be ruled out.
The selector's alias handling, the audit file's reason codes and the two routes by which a real plasmid is lost are on decontamination.
A config written before 2.0.0
A single top-level parameters.genus is still translated into
mode: include with that genus, so older configs keep working. It is not
part of the current schema and is not documented beyond this note.
Long-read assembly and polishing [nanopore|hybrid]¶
nanopore: # read only when mode == nanopore
flye_input_mode: auto # auto | nano-raw | nano-hq
medaka_model: auto
medaka_model_fallback_auto: false
hybrid: # read only when mode == hybrid
flye_input_mode: auto
medaka_model: auto
medaka_model_fallback_auto: false
The block is namespaced per mode: a run reads only the block named after itself.
| Key | What it does | Default | Notes |
|---|---|---|---|
flye_input_mode |
Which read model Flye assembles under | auto |
auto gives --nano-hq unless an explicit fast Medaka model is named. |
medaka_model |
Which Medaka consensus model polishes the assembly | auto |
auto/true/empty infer it from the FASTQ headers; false skips Medaka entirely (optional step, switched off here); anything else is taken as a model name. |
medaka_model_fallback_auto |
What to do when an explicit model turns out to be invalid | false |
false stops with a table of flowcell/device/accuracy suggestions. true falls back to inference — an opt-in, because an explicit choice should be honoured or reported, never silently swapped for a guess. |
The two keys are coupled, and it changes the assembly
With flye_input_mode: auto, an explicit medaka_model whose name contains
"fast" also switches the assembler to --nano-raw — fast basecalling
means noisier reads. So naming a fast model changes how the genome is
assembled, not only how it is polished. To pin the polisher and leave the
assembler alone, set flye_input_mode explicitly instead of leaving it on
auto.
auto needs a basecaller tag in the reads
Inference reads the basecaller name out of the FASTQ headers, so it fails outright on reads that carry none — common in public or re-headered data. Name the model explicitly for such a dataset.
The model is validated right after read filtering, before the assembler runs, so a typo fails in seconds rather than an hour in. Details and the model table: nanopore mode.
Phage caller [all modes]¶
| Key | What it does | Default | Notes |
|---|---|---|---|
caller |
Which tool finds viruses and prophages in stage 07.phages |
virsorter2 |
CheckV then scores whatever that caller found, so this changes CheckV's input, not the QC itself. |
Choosing genomad changes the plasmid stage too. One geNomad run calls viruses
and plasmids together, so opting in also switches on the Platon + geNomad
concordance in 06.plasmids; on the default, that stage is Platon alone and
geNomad never runs. Read licensing before choosing, and
prophages for what the choice does to the output.
Mobilome module [all modes] — optional¶
Everything in this block is inert while run is false, which is the shipped
default. Switching it on adds stage 08.mobilome: for every AMR gene the
pipeline found, the mobile-element context around it and a mobility tier, from
1 (chromosomal, intrinsic candidate) to 6 (inside an ICE or on a conjugative
plasmid — predicted self-transmissible).
The switch, and what it costs¶
| Key | What it does | Default | Notes |
|---|---|---|---|
run |
Master switch for the whole module | false |
Adds 22 rules, two tools (ISEScan and CONJscan/MacSyFinder, both from bioconda), and a model download that needs network access on first use. Those models are non-commercial — see licensing. |
max_composite_span_bp |
Two IS copies further apart than this are not called one composite transposon | 20000 |
A convention, not biology. The measured distance is always reported next to the call, so a reader can disagree without re-running anything. |
coverage_profile |
The fraction of an HMM profile a protein's alignment must cover before the hit is kept | 0.5 |
MacSyFinder's own default, and the value every published result here was measured at. See below. |
Nothing else has to be set. The module runs on the databases BacFlux already has, plus the CONJscan models it fetches itself. See turning it on.
coverage_profile is not identity and not an E-value
A protein can score beyond statistical doubt against a profile and still be dropped by this rule alone, because it aligned to only part of the profile. That happens for two opposite reasons: it really is a fragment or a decayed remnant, or it is a full-length member of a divergent family sharing only the catalytic core.
Lowering it was measured end to end at 0.5 / 0.4 / 0.3 over three benchmark sets — 18 curated ICEs, 12 curated IMEs, and 12 genomes with no curated element at all (32.6 Mb), with the ICEscan layer on:
coverage_profile |
curated ICEs | curated IMEs | calls on the negative set |
|---|---|---|---|
| 0.5 | 15 of 18 | 5 of 12 | 5 (2 without ICEscan) |
| 0.4 | 15 of 18 | 6 of 12 | 7 (4 without ICEscan) |
| 0.3 | 15 of 18 | 6 of 12 | 9 (5 without ICEscan) |
Across 30 curated elements, lowering it recovers exactly one more: a 23 kb IME in
Faecalibacterium duncaniae, found in full and correctly classed. Everything
added on the negative controls at 0.4 is a cime_or_island at passive
mobility, low confidence, claiming nothing and carrying no tier — the tiering
absorbs it, which is what it is for. At 0.3 that stops being true: a 21.6 kb IME
is called in Staphylococcus aureus N315 at medium confidence with a real
mobility claim. Nothing measured here supports 0.3 for single isolates. If your
isolates sit in the space where it might help — of 395 curated IMEs, 73 carry a
relaxase hit that is clean on E-value and fails only this rule, 60 of them
T4SS_MOBT at about 0.32 coverage — 0.4 is worth trying, and the audit TSV
records a reason for every dropped candidate so you can see what changed.
Both machinery searches use this one value, and must: their hit tables are merged, so different stringencies would make an element's class depend on which model set was more permissive.
The four optional layers¶
Each needs a source of its own before it does anything, and none of them switches
on merely because run is true.
| Layer | Switched on by | What it adds | Modes |
|---|---|---|---|
icescan |
icescan.run: true and a url or dir (the shipped url counts) |
A second machinery model set, merged with CONJscan's hits | all |
tncentral |
a url or dir |
Curated transposon and integron names — the only thing that makes ladder tier 4 reachable | all |
iceberg |
a urls entry or dir |
Names the ICE/IME candidates CONJscan already found | all |
isosdb |
fasta_url + family_map_url, or dir |
Read-derived IS copy number: how many copies the assembly lost | illumina, hybrid |
Every layer writes a PROVENANCE.txt beside what it fetched, recording the
source, the fetch date, the observed checksum and how much arrived — sequences
for the three databases, HMM profiles for the ICEscan models. None of these URLs
pins a release, so without that file there is no way to say later which one a
result came from.
icescan — a second machinery model set¶
icescan:
run: false
url: "https://ftp.ebi.ac.uk/pub/databases/metagenomics/pipelines/tool-dbs/icefinder2lite/icf2_dbs.tar.gz"
sha256: ""
dir: ""
| Key | What it does | Default | Notes |
|---|---|---|---|
run |
Runs MacSyFinder a second time with the ICEscan models and merges the two hit tables | false |
Switched on with no url and no dir stops the run — silently producing today's results would hide the fact that the layer never existed. |
url |
Where to fetch the model package from | ICEfinder2's database bundle (~61 MB) | Only the MacSyFinder models are extracted; the rest is discarded. Use the https:// form — the ftp:// spelling of the same path times out from behind many firewalls. |
sha256 |
Pins which release you analysed with | unset | Optional but recommended: the URL carries no version. Empty skips the check; the observed checksum is recorded either way. |
dir |
A MacSyFinder models directory you already hold, containing an ICEscan/ folder |
unset | Takes precedence over url; nothing is downloaded. |
It is a fork of CONJscan by the same authors, one minor version behind the release BacFlux installs, which is why it runs alongside rather than instead: swapping would lose the MOB relaxase models, the decayed-machinery models and the whole plasmid set. What it adds is the AICE class, extra integrase profiles, and many more IME rows — 10 without it against 21 with it, over the 28 benchmark genomes run both ways. What it does not add is boundaries: MacSyFinder reports gene ordinals, never base pairs, so every coordinate still comes from BacFlux's own annotation join, clustering and att-site search. The measurements are on validation.
tncentral — curated transposon and integron names¶
| Key | What it does | Default | Notes |
|---|---|---|---|
url |
A ZIP holding tncentral.fa — about 512 curated transposons, integrons and IS |
unset (layer off) | The server rejects curl's default user-agent, so the download sends a browser one; if that ever stops working the rule fails loudly rather than building an empty database. |
sha256 |
Pins the release | unset | The endpoint is unversioned. Recorded either way in tncentral_db/PROVENANCE.txt. |
dir |
A directory already holding tncentral.fa |
unset | Beats url; nothing is fetched. |
min_identity |
Percent identity a BLAST hit needs before it may confer a curated name | 90.0 |
Below this the sequence may well be a relative of the element, but the name would claim more than the data shows. |
min_reference_coverage |
Fraction of the reference element that must be present | 0.8 |
Measured against the reference, not your contig: a 7 kb element inside a 300 kb contig covers 2% of the contig and 100% of itself. A fragment of a transposon is not that transposon. |
Tier 3 is an inference — two IS copies of one family, the right distance apart, a gene between them. It has known blind spots: IS26 forms translocatable units with its copies in direct orientation, breaking the same-orientation rule the pattern depends on. A curated hit is not an inference, so it gets IS26 right where the pattern does not.
Tier 4 is strict, and rarer than it looks
Reaching it needs a URL and at least min_reference_coverage of the reference
element present and the gene to be somewhere no higher tier claims first. It has
been produced once, on a closed genome: blaCTX-M-15 inside TnEcp1.1 at 87%
coverage (Worked example). In that same genome six
other curated elements were named and none reached tier 4, because they sat on
plasmids. On fragmented clinical assemblies the same transposon was refused outright
at 12% and 49% coverage. Switching this on will name elements; it will not
necessarily produce a tier 4 row.
iceberg — which ICE is it?¶
| Key | What it does | Default | Notes |
|---|---|---|---|
urls |
One or more FASTA URLs, concatenated into one BLAST database | [] (layer off) |
A single URL written without a leading dash is accepted too. Note the host: the older bioinfo-mml.sjtu.edu.cn path serves the previous ICEberg release, not the current one. The larger file is ~100 MB and the server is slow. |
dir |
A directory of .fas files you already hold |
unset | Beats urls. |
min_identity |
Identity floor for a name | 80.0 |
Below this the elements are related but not the same, and the name would mislead. |
min_overlap_fraction |
How much of our candidate the curated element must cover before the two are treated as the same thing | 0.5 |
Deliberately lenient next to the transposon cascade: ICEs are mosaic and their cargo varies between strains, so demanding near-completeness would refuse to name exactly the divergent elements a name helps with most. |
There is no sha256 key here, unlike the other two layers: this is two files
rather than one archive, and the big one has to be resumable over a slow link, so
a single pinned digest is the wrong instrument. The observed checksum of the
concatenated FASTA and the fetch date land in iceberg_db/PROVENANCE.txt.
This layer only labels; it cannot change any gene's tier. Its real value is showing how far our boundaries fall short: on K. pneumoniae ATCC BAA-2146 the ICE call spans 54,943 bp against ICEberg's 58,048 bp for the same element — 0.946 of it, stopping 3,138 bp inside its far end. Any AMR gene in that last 3 kb is scored as though it were outside the ICE. That is the good case, a closed genome where the att search found a tRNA-anchored repeat.
isosdb — how many IS copies did the assembly lose?¶
[illumina|hybrid] only — it needs reads.
| Key | What it does | Default | Notes |
|---|---|---|---|
fasta_url |
22,713 IS nucleotide sequences, already dereplicated at 95% identity | unset (layer off) | About 9.9 MB zipped. |
family_map_url |
Entry → IS family (IS3, IS5, IS110 …) | unset | Required whenever fasta_url is set. Set one without the other and the run stops at parse time, rather than the download failing an hour in. |
dir |
A directory already holding both files | unset | Beats the URLs. |
min_covered_percent |
A database entry must be covered this far end to end before its depth is believed | 90.0 |
A partially covered entry is usually a conserved domain shared with another family; averaging it in would inflate every estimate. |
min_copy_number |
Below this multiple of the genome baseline the element is treated as absent | 0.5 |
Deliberately under 1.0: a real single-copy IS sits near 1×, and sampling noise plus mapping loss routinely push it to 0.6–0.8×. |
Elsewhere the module says the located IS count is a floor, not a count. True, but unquantified — you cannot tell whether the floor is 2 short or 40. Reads are immune to assembly collapse, so depth over an IS divided by depth over the genome gives the copy number. It changes no AMR gene's tier, and must not: it says nothing about where the extra copies are, only that they exist. Results are reported per family as well as per entry, because the family total is robust to which near-identical entry an ambiguous read happened to land on.
Confidence and boundary flags¶
| Key | What it does | Default | Notes |
|---|---|---|---|
require_trna_boundary_for_high |
Also demands a tRNA-anchored att pair before an ICE/IME candidate may be labelled high confidence |
false |
Sensible on closed long-read assemblies, where an unresolved boundary really is a warning sign. |
contig_boundary_bp |
How close to a contig end counts as "at the boundary" when flagging an IS call | 100 |
Feeds the at_contig_boundary and fraction_at_contig_boundary columns of the IS summary. A high fraction means the assembly broke exactly where the IS elements are. |
Left false, confidence answers "how sure are we this is an ICE?" — from the
number of anchor classes, whether the machinery is intact, and whether it all
sits on one contig. Where the element ends is reported separately, in
boundary_method. Set true on a fragmented assembly and nearly every call caps
at medium however good the machinery evidence is, because the flanking sequence
simply is not in the contig; that conflates two different questions. Either way,
cargo is never assigned from an unresolved boundary: a de novo direct repeat is
reported but never widens an element (about 16% of arbitrary spans on a real
chromosome produce one by chance), so the interval stays the machinery span — a
floor, never an invention.
Three contig-end windows, one of them configurable
They answer different questions about different objects, which is why they are separate:
| Window | Question | Set by |
|---|---|---|
| 100 bp | Is an IS call sitting on the edge of its contig? | contig_boundary_bp |
| 1000 bp | Was there enough flanking sequence to have seen a neighbouring element at all? Caps confidence in the mobility table. | fixed in colocalise.py |
| 1000 bp | The same question for an ICE or IME candidate rather than an AMR gene. | fixed in conjscan_to_ice.py |
An IS 500 bp from a contig end is not "at the edge", but you still could not
have seen its partner 2 kb away — absence of evidence is not evidence of
absence. The first and third confusingly share the flag name --boundary-bp
while meaning different things; only the first is wired to a config key.
What each column of the output means is on reading the output; what to conclude on a fragmented assembly is on draft assemblies.
Numbers that are not config keys¶
Some thresholds you might reasonably expect to find in the config are set in the code instead. This is where each one lives.
The ICE/IME geometry¶
conjscan_ice passes none of these, so the script's own defaults are what every
measured result quoted on this site was produced at. Changing one means editing
the rule's shell block in workflow/rules/shared/80_mobilome.smk — and re-running
the benchmark afterwards.
| Default | What it controls |
|---|---|
| 15,000 bp | How far apart two machinery genes may sit and still be chained into one candidate element |
| 8,000 bp | Floor on a candidate's length; below it the cluster is dropped with a reason |
| 2,000 bp | The same floor for the IME/AICE architecture, which is genuinely smaller. The sharpest knob in the module |
| 500,000 bp | Ceiling, so a runaway chain of anchors cannot cover a whole replicon |
| 50,000 bp | How far from the machinery an integrase may sit and still be taken as the element's own |
| 50,000 bp | How much sequence either side of the machinery the att-site search reads |
One asymmetry, which is deliberate
The att search is always given the 8,000 bp floor, never the 2,000 bp IME
one, and it refuses any repeat pair implying an element outside its range. So
an IME admitted at 3 kb can never have its boundaries resolved, however clean
the repeat pair — it keeps boundary_method: none and reports the machinery
span. Four of the five curated IMEs this module detects have machinery spans
under that floor (3,840 / 4,959 / 5,942 / 6,252 bp). The deliberate reading is
that an att pair implying a 3 kb element is mostly noise; the honest reading
is that IME boundaries are therefore largely unresolved. Making the floors
agree per class would change results, so it has not been done quietly.
Elsewhere in the workflow¶
| Value | Where | What it is |
|---|---|---|
minid=0.76, ≥70% of reference length covered |
CARD_MIN_IDENTITY / CARD_MIN_COVERED, 00_common.smk |
The CARD read-mapping AMR leg. Read identity is BBMap's default; the specificity comes from the length-coverage gate. See below. |
| 80% identity, 70% coverage | rule amr_contigs, 50_amr.smk |
ABRicate's EFSA reporting thresholds. Kept on that leg only — a blanket cutoff would fight AMRFinderPlus's curated per-gene ones. |
| eight databases | DATABASES, 00_common.smk |
Which ABRicate databases run: argannot, card, ecoh, ecoli_vf, megares, ncbi, resfinder, vfdb. |
--target_bases 500000000 |
rule filter_long_reads, nanopore/10_reads.smk |
The nanopore mode's total-output cap. The hybrid rule has no equivalent — the selected Illumina reads decide what is worth keeping there, and it passes --trim, --split 1000 and --length_weight instead. |
| coverage ≥ 2×, length ≥ 500 bp | FASTA_SEL_CMD, 00_common.smk |
The contig filter, read out of the SPAdes header. Always applied in illumina and hybrid; in contigs mode only when the first header looks like SPAdes' own (it carries length_… or cov_…), since inventing a filter for an assembly from anywhere else would silently delete real sequence. |
| 80% of the reference | EXACT_NAME_REFERENCE_COVERAGE, name_ice_elements.py |
Below it, an ICEberg name is suffixed -like. Distinct from iceberg.min_overlap_fraction, which is measured against our candidate. |
The CARD leg is a sensitive screen, not a high-identity one
Until 2026-08-15 this rule passed BBMap idfilter=0.99, and both the code and
the documentation claimed results were screened at 99% identity. They were
not. idfilter does not filter the primary alignment of a properly-paired
read (BBMap 39.33, align2/AbstractMapThread.java: the guard reads
if(!r.paired() && …)), so every result the leg ever produced was screened at
BBMap's default of 0.76. Measured on the same reads: paired, idfilter=0.99
kept 1240 alignments, 1238 of them below 99% identity; the same reads treated
as single-end kept 1.
The rule now passes minid=0.76 — which changes no output, and stops the code
claiming a stringency it never applied. It is deliberately not raised: at a
real 0.99 the same reads keep 2 alignments instead of 1240, which would
disable the one AMR leg immune to assembly collapse. Specificity comes from requiring
≥70% of the reference gene's length to be covered, which is why the loose
identity floor is tolerable. There is no second pass and no divergent tier.
See antimicrobial resistance.
Where to go next¶
| Page | Read it for |
|---|---|
| Reference databases | the databases these keys point at, and how to get them |
| Quick start | a fresh clone to a running job |
| Running BacFlux | what --cores does, and how to restart a run |
| Output files | what each numbered stage writes |
| Turning it on · Optional layers · Tuning | the mobilome block in full, layer by layer |
| Coming from v1 | which keys changed name or moved |
| Troubleshooting | what to do when a run stops |