Back to docs

Concept

Template runtime contract

The `linkar_template.yaml` contract, `run.command` versus `script.sh`, and what render and run do.

Linkar templates are small runtime contracts, not mini workflow languages.

The canonical template file is linkar_template.yaml. Linkar still accepts template.yaml for backward compatibility, but new templates should use the canonical name.

Template shape

The smallest useful template is usually:

id: simple_echo
version: 0.1.0
description: Write one greeting file.
params:
  name:
    type: str
    required: true
outputs:
  greeting_file:
    path: greeting.txt
run:
  mode: direct
  command: >-
    printf 'hello %s\n' "${param:name}" > "${LINKAR_RESULTS_DIR}/greeting.txt"

Top-level fields:

  • id
  • version
  • description
  • params
  • outputs
  • cleanup
  • tools
  • run

Parameter types

Linkar supports:

  • str
  • int
  • float
  • bool
  • path
  • list[path]

Use explicit defaults in the schema whenever possible. That keeps wrapper logic small and makes the CLI help clearer.

Declared outputs

Outputs are how Linkar records what a template produced.

Common patterns:

outputs:
  results_dir: {}
  report_html:
    path: reports/report.html
  fastq_files:
    glob: output/**/*.fastq.gz

Rules:

  • results_dir: {} means the whole results/ directory
  • path is resolved relative to results/
  • glob is also resolved relative to results/
  • only existing outputs are recorded

Cleanup rules

Templates can also declare disposable runtime artifacts:

cleanup:
  - path: .pixi
    type: dir
  - path: work
    type: dir
  - path: .nextflow
    type: dir
  - glob: ".nextflow.log*"
    type: file

These rules are consumed by:

linkar clean .

clean can be run from a project root or from a rendered template directory. When run from a project root, Linkar reads project.yaml, finds the recorded template directories, and applies each template’s rules to its own directory. When run from a rendered template directory, only that directory is cleaned. When the same template id is available from a current configured pack, Linkar uses the latest cleanup rules from that pack instead of keeping cleanup pinned to the version that originally rendered the workspace. The default terminal flow previews the matching folders and files before asking for confirmation.

Rules:

  • path and glob are resolved relative to the rendered template directory
  • each rule must declare exactly one of path or glob
  • type can be dir, file, or any
  • absolute paths, .., and rules that target the template root are rejected
  • declared outputs and scientific results should not be cleanup targets

Tool requirements

Templates can declare external commands that must exist before execution:

tools:
  required:
    - pixi
  required_any:
    - [bcl-convert, bcl_convert]

This is a preflight check. It is not an environment manager. When Linkar itself is running inside a Pixi environment, it also honors Pixi’s PIXI_EXE variable and exposes that executable to template subprocesses. Nested template-local pixi run commands therefore remain available even when Pixi did not add its own binary directory to PATH.

run.command versus script.sh

Use run.command for a thin wrapper around one real command.

Use script.sh when the template needs real local shell logic:

  • branching
  • temp files
  • generated configs
  • multi-step local orchestration
  • traps and cleanup

run.py is also a good option once shell stops being clearer.

Explicit parameter placeholders

The preferred form in run.command is:

"${param:bcl_dir}"
"${param:samplesheet}"
${param:run_name:+--run-name "${param:run_name}"}

This is clearer than relying on the older implicit shell convention where bcl_dir became BCL_DIR.

Linkar still supports the older form because existing templates use it, but new templates should prefer ${param:...}.

What linkar render does

linkar render ... stages a standalone runnable artifact and stops there. If the chosen output directory already contains files, render stops with a clear error; choose a different --outdir or remove the existing directory before rendering.

Bare template names such as export or nfcore_methylseq resolve through configured packs first. If you want to execute a template from the current filesystem path explicitly, pass ./template_dir or an absolute path instead of a bare name.

Render behavior:

  • writes one final run.sh
  • for run.entry: script.sh, wraps the source script in the rendered run.sh artifact
  • resolves parameters into the rendered script
  • localizes bound file parameters into the rendered directory when needed
  • writes metadata under .linkar/
  • does not execute the template
  • when inside a project, records the rendered bundle in project.yaml with state: rendered

In a project, render defaults to the visible project path such as ./demultiplex, not to .linkar/runs/....

What linkar run does

linkar run ... always executes.

Run behavior:

  • for run.mode: direct, stages the run under .linkar/runs/<instance_id>/
  • for run.mode: render inside a project, executes directly in the visible project directory such as ./export
  • executes the template
  • collects declared outputs
  • writes project-central .linkar/meta/<instance_id>.json and .linkar/runtime/<instance_id>.json records, or portable .linkar/meta.json and .linkar/runtime.json records outside a project
  • appends a run record to project.yaml with state: completed or state: failed
  • updates the stable project alias such as ./fastqc when a separate history run directory exists

For render-mode templates in a project:

  • linkar render TEMPLATE creates the visible bundle when its target is empty or absent
  • a second linkar render TEMPLATE currently stops rather than overwriting a non-empty bundle
  • linkar run TEMPLATE runs the current visible bundle if it already exists
  • linkar run TEMPLATE --refresh rerenders first, then runs
  • if no visible bundle exists yet, linkar run TEMPLATE materializes it once and then executes it

That split is intentional:

  • render creates a handoff artifact and records that current draft as rendered
  • run creates immutable executed history for direct-run templates and records execution state for both direct and render-mode templates

Manual execution after render

If you manually execute a rendered run.sh, Linkar is not involved in the execution itself.

You can collect outputs afterward with:

linkar collect /path/to/rendered_dir

That updates the run’s registered metadata JSON and, when the rendered artifact belongs to a project, also updates its recorded outputs in project.yaml.