Concept
Params, bindings, and resolution
How Linkar resolves values, what bindings are for, and how warnings and placeholders fit into the model.
Parameter resolution is where Linkar turns a reusable template contract into one concrete run.
Resolution order
Linkar resolves parameters in this order:
- explicit CLI or API values
- pack binding rules
- latest matching project outputs
- template defaults
- missing required parameter error
That means bindings are not a sidecar convenience. They are part of the runtime contract.
Pack bindings
The canonical pack binding file is linkar_pack.yaml. Linkar still accepts binding.yaml for
backward compatibility, but new packs should use linkar_pack.yaml.
Example:
templates:
nfcore_3mrnaseq:
params:
samplesheet:
function: generate_nfcore_rnaseq_samplesheet_forward
genome:
function: get_agendo_genome
Bindings can resolve values through:
function- another template’s
output - literal
value
Binding functions
A binding function is a small Python file in functions/ with:
def resolve(ctx):
...
The context exposes:
ctx.templatectx.projectctx.resolved_paramsctx.latest_output(...)ctx.warn(...)
Binding functions should stay narrow. They are best for:
- resolving a file path from another recorded run
- loading facility metadata
- deriving small defaults
- warning and returning a placeholder when render should continue
They should not turn into a second workflow engine.
They are also not the right place for broad site inventory logic such as:
- listing all projects on a server
- scanning all FASTQ roots
- discovering all raw sequencing runs
That kind of environment discovery is usually better as a separate pack-side discovery layer.
Resolution is order-sensitive
Bindings only see parameters already resolved earlier in the template.
That means parameter order in linkar_template.yaml matters when one binding depends on another
resolved value.
Example:
agendo_idmust appear beforegenomeif thegenomebinding readsctx.resolved_params["agendo_id"]
This is runtime behavior, not an abstract principle.
Bindings versus discovery
It helps to keep these two ideas separate:
- bindings answer: “how should this template param be resolved?”
- discovery answers: “what local projects, datasets, or references exist here?”
For example:
- “use the latest demultiplex output as
fastq_dir” belongs in a binding - “list likely FASTQ run directories on this server” belongs in a discovery helper
That boundary keeps bindings focused on workflow semantics instead of turning them into a general storage index.
Structured warnings
Bindings can emit warnings without aborting resolution:
ctx.warn(
"Could not derive genome from Agendo organism 'other'.",
action="Edit run.sh and replace __EDIT_ME_GENOME__ before execution.",
fallback="__EDIT_ME_GENOME__",
)
return "__EDIT_ME_GENOME__"
Linkar then:
- shows the warning in CLI output
- records it in the run metadata JSON
- records it in the run runtime JSON
This is the preferred way to handle “render now, fix later” cases.
Placeholders
When render should succeed but the final execution still needs human review, use a visible placeholder such as:
__EDIT_ME_GENOME__
That placeholder should also be enforced in the rendered script so execution fails clearly until it is replaced.
File localization during render
When a bound path or list[path] value points to an external file, linkar render ... copies it
into the rendered directory and rewrites the resolved value to the local copy.
If a staged template file already has the same basename, the localized bound file overwrites it.
That is why a rendered samplesheet.csv can replace a bundled fallback file cleanly.