Orchestrating molecular design workflows
Protocols let you describe a multi-step molecular design job — chained model calls, gather/aggregation steps, filters, and structured outputs — as a single declarative YAML file. The platform runs the whole graph for you, handling concurrency, dependency ordering, and result collection, so you submit inputs and collect a results table instead of wiring API calls together by hand.
This guide walks the full lifecycle: author → validate → submit → wait → results. Authoring and validation happen locally against your YAML; submission and monitoring happen on the platform through the Python SDK.
What a protocol is
A protocol is a YAML document with a handful of top-level sections:
inputs— named parameters the caller supplies at submission time.tasks— the steps of the graph (model calls,gatheraggregations, transforms), wired together withdepends_onand${{ ... }}template expressions.outputs— rules that shape the final merged results table (what to log, how to filter, order, and limit rows).
For the full field reference, see Protocol Schema Reference.
Once authored, a protocol is registered on the platform and referenced by its
slug (for example my-protocol-slug). You never submit the local YAML
file to run it — you validate the YAML locally, then submit inputs against the
registered slug.
Validate
Validation is fully local and catches the errors that would otherwise fail a run: YAML syntax, schema compliance, unknown task references, circular dependencies, and malformed template expressions.
From the CLI:
biolm protocol validate my_protocol.yaml
# Machine-readable output for CI
biolm protocol validate my_protocol.yaml --json
The command exits non-zero when validation fails, so it drops straight into a pre-commit hook or CI step.
From Python, biolm.Protocol.validate() returns a result object you can
inspect programmatically:
from biolm import Protocol
result = Protocol.validate("my_protocol.yaml")
if result.is_valid:
print("OK:", result.statistics) # task_count, input_count, ...
else:
for err in result.errors:
# error_type is one of: syntax, schema, semantic
print(f"[{err.error_type}] {err.path}: {err.message}")
for warning in result.warnings:
print("warning:", warning)
Each error carries a message, a JSONPath-like path, and an
error_type, so you can surface exactly where a protocol is malformed.
Submit, wait, and collect results
Once the platform has a registered protocol, run it from Python. Submission
uses the platform slug, not the local YAML path, and inputs is a plain
dict whose keys match the inputs section of the protocol.
The one-liner: biolm.run_protocol()
run_protocol() submits a run, blocks until it finishes, and
returns the results dict:
from biolm import run_protocol
results = run_protocol(
"my-protocol-slug", # platform slug
inputs={ # keys match the protocol's `inputs`
"sequences": ["MKTAYIAKQRQGHQAMAEIKQ"],
},
run_name="protocol-demo", # optional label
timeout=3600.0, # seconds to wait
show_progress=True,
)
print(results)
Authentication comes from the BIOLM_TOKEN environment variable (or an
explicit api_key= argument). See Authentication for token setup.
Full control: ProtocolClient and ProtocolRun
When you need progress polling, cancellation, downloads, or a DataFrame, submit
with ProtocolClient and drive the returned
ProtocolRun:
from biolm import ProtocolClient
client = ProtocolClient() # reads BIOLM_TOKEN
run = client.submit(
"my-protocol-slug",
inputs={"sequences": ["MKTAYIAKQRQGHQAMAEIKQ"]},
run_name="protocol-demo",
)
print("submitted:", run.run_id, run.status)
# Stream status until terminal, or poll a snapshot yourself
run.wait(timeout=3600.0, show_progress=True)
snapshot = run.progress() # dict: status, progress_pct, ...
# Collect results
detail = run.results() # full run detail dict
df = run.to_dataframe() # download CSV zip -> pandas DataFrame
path = run.download(output_dir="results", file_type="csv")
# Cancel a running job if needed
# run.cancel()
To reconnect to a run started elsewhere, use its ID:
run = client.get_run("run-abc123")
run.wait()
Note
run.to_dataframe() requires pandas, and run.wait() uses
websockets for live telemetry. Install them if they are not already in
your environment.
ProtocolClient vs. run_protocol()
Reach for run_protocol() when you want a single blocking call and
only care about the final results dict — scripts, notebooks, and quick jobs.
Reach for ProtocolClient / ProtocolRun when you
need any of: submitting without blocking, polling progress() for a
dashboard, cancelling a run, downloading artifacts, converting results to a
DataFrame, or reconnecting to an existing run_id. Under the hood
run_protocol is exactly client.submit(...).wait(...) followed by
run.results().
Note
biolm protocol run and biolm protocol list are placeholders today and
print a “coming soon” message — they do not submit or enumerate runs.
Regardless of what older README snippets suggest, use the Python API above
(run_protocol() or ProtocolClient) to submit,
wait, and retrieve results. The working CLI subcommands are init,
show, validate, and log.
Logging results to MLflow
The biolm protocol log command pushes a run’s results into MLflow using the
protocol’s outputs configuration (params, metrics, tags, aggregates, and
artifacts):
biolm protocol log results.jsonl --outputs my_protocol.yaml \
--account acme --workspace lab --protocol my-protocol-slug
MLflow support is optional and ships behind an extra. If it is not installed the command reports MLflow Not Available; install it with:
pip install biolm-sdk[mlflow]
See biolm protocol for the complete command reference and options.
Troubleshooting
Validation errors. Protocol.validate() (and biolm protocol validate)
report an error_type per error:
syntax— the YAML itself failed to parse. Fix indentation/quoting.schema— a field is missing, misnamed, or the wrong type. Check it against Protocol Schema Reference.semantic— adepends_on/from/foreachreference points at an unknown task or input, a circular dependency exists between tasks, or a${{ ... }}template expression is empty or has unbalanced braces.
Authentication. A missing token raises ValueError on the first API call.
Set BIOLM_TOKEN (or run biolm login), or pass api_key= to
ProtocolClient. See Authentication.
Run failures. A run that fails or is cancelled raises
ProtocolRunError from wait(). An unknown slug or version
raises ProtocolNotFoundError (a subclass). Catch them to react:
from biolm import run_protocol, ProtocolRunError, ProtocolNotFoundError
try:
results = run_protocol("my-protocol-slug", inputs={...})
except ProtocolNotFoundError:
print("Check the protocol slug/version.")
except ProtocolRunError as exc:
print("Run failed:", exc)
Timeouts. If a run exceeds timeout seconds, wait() raises
TimeoutError after refreshing status — the run keeps going on the platform.
Reconnect later with client.get_run(run_id) and call wait() again, or
raise the timeout for long jobs.
See also
Orchestrating molecular design workflows — how protocols fit alongside the local pipeline framework
Managing datasets — upload and download platform datasets for protocol inputs
Protocol Schema Reference — the full protocol YAML field reference
biolm.protocols — Python API reference for protocol classes
biolm protocol — CLI command reference, including
logand MLflow