Snakemake — 18 Operations for AI Agents
Snakemake pipelines orchestrate the world's bioinformatics and data workflows — rules, wildcards, DAGs of jobs. act101 reads each rule's inputs and outputs as structure, so agents trace a pipeline without executing it in their head.
This page is the canonical reference an AI coding agent uses to refactor, query, and analyze Snakemake code through the act MCP server. 18 operations available: 0 refactor, 18 query, 0 analysis. Each operation is callable from Claude Code, Cursor, Codex, OpenCode, or any MCP-compatible agent host. Click any operation for a stable anchor link suitable for citation.
Worked Snakemake examples
act101 reads a Snakemake workflow's rule blocks as function declarations named for the rule, alongside the ordinary Python constructs a .smk file also allows: a def surfaces as function, a module-level assignment surfaces as variable, and a bare import statement surfaces as import, named for its entire statement text. symbols covers the rules, the Python function, and the module-level assignments identically to the skeleton, but adds each function parameter as its own entry — aligner_for's sample argument comes back kind: unknown — and drops the bare import os line entirely, since its rule only matches an aliased import. query complexity scores a target's body for branching, adding one point per if_statement, independent of the rule DAG itself. Each example below is the verbatim output of the command shown, run against the file shown. Query outputs are pretty-printed with the timing block omitted.
Read the rules, helper function, and variables as a skeleton
align.smk runs a genome alignment pipeline: an aligner_for helper picks a strategy per sample, then align, sort, and index rules turn FASTQ reads into an indexed BAM file.
$ act query skeleton align.smk
Before
import os
REFERENCE = "ref/genome.fa"
SAMPLES = ["patient01", "patient02"]
def aligner_for(sample):
if sample.endswith("_wgs"):
return "bwa mem"
else:
return "bwa aln"
rule align:
input:
ref=REFERENCE,
reads="reads/{sample}.fastq"
output:
"aligned/{sample}.bam"
shell:
"bwa mem {input.ref} {input.reads} | samtools view -bS - > {output}"
rule sort:
input:
"aligned/{sample}.bam"
output:
"sorted/{sample}.sorted.bam"
shell:
"samtools sort {input} -o {output}"
rule index:
input:
"sorted/{sample}.sorted.bam"
output:
"sorted/{sample}.sorted.bam.bai"
shell:
"samtools index {input}"
Output
{
"type": "Skeleton",
"declarations": [
{
"kind": "import",
"name": "import os",
"range": {
"start": {
"file": "align.smk",
"line": 1,
"column": 1,
"byte_offset": 0
},
"end": {
"file": "align.smk",
"line": 1,
"column": 10,
"byte_offset": 9
}
}
},
{
"kind": "variable",
"name": "REFERENCE",
"range": {
"start": {
"file": "align.smk",
"line": 1,
"column": 1,
"byte_offset": 0
},
"end": {
"file": "align.smk",
"line": 36,
"column": 1,
"byte_offset": 725
}
},
"name_range": {
"start": {
"file": "align.smk",
"line": 3,
"column": 1,
"byte_offset": 11
},
"end": {
"file": "align.smk",
"line": 3,
"column": 10,
"byte_offset": 20
}
}
},
{
"kind": "variable",
"name": "SAMPLES",
"range": {
"start": {
"file": "align.smk",
"line": 1,
"column": 1,
"byte_offset": 0
},
"end": {
"file": "align.smk",
"line": 36,
"column": 1,
"byte_offset": 725
}
},
"name_range": {
"start": {
"file": "align.smk",
"line": 4,
"column": 1,
"byte_offset": 39
},
"end": {
"file": "align.smk",
"line": 4,
"column": 8,
"byte_offset": 46
}
}
},
{
"kind": "function",
"name": "aligner_for",
"range": {
"start": {
"file": "align.smk",
"line": 6,
"column": 1,
"byte_offset": 77
},
"end": {
"file": "align.smk",
"line": 10,
"column": 25,
"byte_offset": 193
}
},
"name_range": {
"start": {
"file": "align.smk",
"line": 6,
"column": 5,
"byte_offset": 81
},
"end": {
"file": "align.smk",
"line": 6,
"column": 16,
"byte_offset": 92
}
}
},
{
"kind": "function",
"name": "align",
"range": {
"start": {
"file": "align.smk",
"line": 12,
"column": 1,
"byte_offset": 195
},
"end": {
"file": "align.smk",
"line": 19,
"column": 77,
"byte_offset": 408
}
},
"name_range": {
"start": {
"file": "align.smk",
"line": 12,
"column": 6,
"byte_offset": 200
},
"end": {
"file": "align.smk",
"line": 12,
"column": 11,
"byte_offset": 205
}
}
},
{
"kind": "function",
"name": "sort",
"range": {
"start": {
"file": "align.smk",
"line": 21,
"column": 1,
"byte_offset": 410
},
"end": {
"file": "align.smk",
"line": 27,
"column": 44,
"byte_offset": 566
}
},
"name_range": {
"start": {
"file": "align.smk",
"line": 21,
"column": 6,
"byte_offset": 415
},
"end": {
"file": "align.smk",
"line": 21,
"column": 10,
"byte_offset": 419
}
}
},
{
"kind": "function",
"name": "index",
"range": {
"start": {
"file": "align.smk",
"line": 29,
"column": 1,
"byte_offset": 568
},
"end": {
"file": "align.smk",
"line": 35,
"column": 33,
"byte_offset": 724
}
},
"name_range": {
"start": {
"file": "align.smk",
"line": 29,
"column": 6,
"byte_offset": 573
},
"end": {
"file": "align.smk",
"line": 29,
"column": 11,
"byte_offset": 578
}
}
}
]
}
The skeleton reports import os as an import, REFERENCE and SAMPLES as variable declarations, and aligner_for, align, sort, and index as function declarations — the three rules alongside the one Python helper.
List the rules, variables, and function parameter as symbols
aligner_for takes one parameter, sample, and branches on whether its name ends in _wgs.
$ act query symbols align.smk
Before
import os
REFERENCE = "ref/genome.fa"
SAMPLES = ["patient01", "patient02"]
def aligner_for(sample):
if sample.endswith("_wgs"):
return "bwa mem"
else:
return "bwa aln"
rule align:
input:
ref=REFERENCE,
reads="reads/{sample}.fastq"
output:
"aligned/{sample}.bam"
shell:
"bwa mem {input.ref} {input.reads} | samtools view -bS - > {output}"
rule sort:
input:
"aligned/{sample}.bam"
output:
"sorted/{sample}.sorted.bam"
shell:
"samtools sort {input} -o {output}"
rule index:
input:
"sorted/{sample}.sorted.bam"
output:
"sorted/{sample}.sorted.bam.bai"
shell:
"samtools index {input}"
Output
{
"type": "Symbols",
"symbols": [
{
"name": "REFERENCE",
"kind": "variable",
"range": {
"start": {
"file": "align.smk",
"line": 3,
"column": 1,
"byte_offset": 11
},
"end": {
"file": "align.smk",
"line": 3,
"column": 10,
"byte_offset": 20
}
},
"visibility": "unknown"
},
{
"name": "SAMPLES",
"kind": "variable",
"range": {
"start": {
"file": "align.smk",
"line": 4,
"column": 1,
"byte_offset": 39
},
"end": {
"file": "align.smk",
"line": 4,
"column": 8,
"byte_offset": 46
}
},
"visibility": "unknown"
},
{
"name": "aligner_for",
"kind": "function",
"range": {
"start": {
"file": "align.smk",
"line": 6,
"column": 5,
"byte_offset": 81
},
"end": {
"file": "align.smk",
"line": 6,
"column": 16,
"byte_offset": 92
}
},
"visibility": "unknown"
},
{
"name": "sample",
"kind": "unknown",
"range": {
"start": {
"file": "align.smk",
"line": 6,
"column": 17,
"byte_offset": 93
},
"end": {
"file": "align.smk",
"line": 6,
"column": 23,
"byte_offset": 99
}
},
"visibility": "unknown"
},
{
"name": "align",
"kind": "function",
"range": {
"start": {
"file": "align.smk",
"line": 12,
"column": 6,
"byte_offset": 200
},
"end": {
"file": "align.smk",
"line": 12,
"column": 11,
"byte_offset": 205
}
},
"visibility": "unknown"
},
{
"name": "sort",
"kind": "function",
"range": {
"start": {
"file": "align.smk",
"line": 21,
"column": 6,
"byte_offset": 415
},
"end": {
"file": "align.smk",
"line": 21,
"column": 10,
"byte_offset": 419
}
},
"visibility": "unknown"
},
{
"name": "index",
"kind": "function",
"range": {
"start": {
"file": "align.smk",
"line": 29,
"column": 6,
"byte_offset": 573
},
"end": {
"file": "align.smk",
"line": 29,
"column": 11,
"byte_offset": 578
}
},
"visibility": "unknown"
}
]
}
symbols reports the same variables and four functions as skeleton, plus sample as a kind: unknown entry for aligner_for's parameter; import os has no matching rule in this query and is absent.
Score the aligner-selection branch for complexity
aligner_for is the only construct in the file with a conditional: whole-genome samples get bwa mem, everything else gets bwa aln.
$ act query complexity align.smk
Before
import os
REFERENCE = "ref/genome.fa"
SAMPLES = ["patient01", "patient02"]
def aligner_for(sample):
if sample.endswith("_wgs"):
return "bwa mem"
else:
return "bwa aln"
rule align:
input:
ref=REFERENCE,
reads="reads/{sample}.fastq"
output:
"aligned/{sample}.bam"
shell:
"bwa mem {input.ref} {input.reads} | samtools view -bS - > {output}"
rule sort:
input:
"aligned/{sample}.bam"
output:
"sorted/{sample}.sorted.bam"
shell:
"samtools sort {input} -o {output}"
rule index:
input:
"sorted/{sample}.sorted.bam"
output:
"sorted/{sample}.sorted.bam.bai"
shell:
"samtools index {input}"
Output
{
"type": "Complexity",
"score": 2,
"details": "Base complexity: 1, +1 if_statement"
}
The score comes back 2: a base complexity of 1, plus one point for the single if_statement inside aligner_for — the three rule blocks contribute nothing, since none of them branches.
Query
18 query tools, the same on every supported language. Descriptions live in the shared reference: /docs/query-tools.
callers control_flow data_flow definition diagnostics effect_closure effect_summary fix_auto get_type graph import_organize interface mutations references repo_outline skeleton symbols symbols_batch