Skip to content

Tool Authoring

A tool plugin should provide a narrow integration layer around scientific workflow logic.

Distribution dependencies

Declare caddforge-core>=0.21.0 in plugin project.dependencies; do not depend on caddforge, which is the complete toolbox bundle. Keep Python imports and entry-point groups under caddforge. Adding a public plugin to the bundle requires an explicit, exact dependency pin and a newly tested bundle release. Only reviewed public components belong in the public bundle.

Optional CLI commands

An installed package can add a top-level CLI command through the caddforge.cli_commands entry-point group. Core 0.21.0 introduces this interface. The entry-point name becomes the command name; its target is a callable with a Typer-compatible signature and help docstring:

[project.entry-points."caddforge.cli_commands"]
docs = "caddforge_docs.cli:docs"

Core registers these commands after its built-in commands and groups. Conflicts, duplicate command names, and extensions that fail to load or have unsupported signatures are logged and skipped, leaving built-in commands available. Keep extension imports lightweight and load expensive runtime dependencies only when the command executes. Removing the providing package removes its command from subsequent CLI invocations. Plugins that only expose scientific tools should continue to use the caddforge.tools group.

Required pieces

  • Config model with validation and YAML/JSON loading support.
  • Workflow class that prepares and runs jobs.
  • Tool metadata class registered through caddforge.tools entry points.
  • Canonical Result output with metrics and paths.
  • Tests for config validation, workflow setup, and result generation.

Pipeline contracts

Expose pipeline ports when a tool should participate in pipelines:

InputPort(name="ligands", kind=ArtifactKind.MOLECULE_COLLECTION, config_field="ligand_paths")
OutputPort(name="scores", kind=ArtifactKind.TABULAR_SCORES, result_path_key="best_scores_csv")
OutputPort(
    name="pose_files",
    kind=ArtifactKind.POSE_COLLECTION,
    result_path_key="pose_manifest_csv",
    required=False,
    path_collection_column="pose_file",
)

Use declared ports or explicit outputs mappings. Do not force downstream stages to infer paths from tool-specific output directories.

Runtime input and compression contracts

Set InputPort.config_field to the configuration path that receives a pipeline artifact. Core also uses declared inputs to materialize transparent gzip wrappers immediately before execution. For a nested collection, set runtime_selector, for example input_files[].path. Mark a selector with InputCompressionPolicy.NATIVE when the scientific tool consumes the compressed stream itself.

Outputs requested with a .gz suffix require an OutputPort.config_field so core can distinguish the requested destination from an existing compressed input and finalize it atomically.

Structured collection fields can declare a SplitContract that identifies the path member to split, preserves sibling metadata in every shard, and selects a deterministic reducer for shard outputs.

Scratch publication helpers

Use LogSync for mutable stdout, stderr, or progress snapshots written in scratch. Set atomic=True for structured snapshots and inspect its LogSyncReport when the final publication state belongs in the result. Synchronization failures are retried and must not change scientific job status.

Use ArtifactSyncQueue for completed immutable artifacts. It publishes by atomic replacement, retries transient failures, and reports unresolved files after the final pass. Do not place tool-specific behavior in these core helpers.

Backends

Workflow code should not contain Slurm-specific or local-process orchestration. Submit and track jobs through the core backend interfaces.