460 lines
20 KiB
Markdown
460 lines
20 KiB
Markdown
# SciMesh Workload SDK v1
|
|
|
|
SciMesh now ships a public Python SDK under `scimesh.sdk`. The implemented
|
|
authoring profile is **`core-batch-v1`**: installed and digest-pinned workload
|
|
definitions, strict JSON manifests, typed artifact ports and collections, a
|
|
static map/reduce workflow, CPU/memory/scratch eligibility, atomic local
|
|
resource reservation, exact/canonical/numeric verifier primitives, and a
|
|
compatibility adapter for the existing `DistributedWorkload` protocol. Its
|
|
local executor is deliberately a trusted, in-process conformance harness; the
|
|
production subprocess/lease sandbox remains a coordinator/Worker milestone.
|
|
|
|
The full target contract remains in
|
|
[`scimesh-sdk-contract.md`](scimesh-sdk-contract.md). Dynamic expansion,
|
|
streaming, accelerators, gang execution, and side effects have typed bounded
|
|
declarations, but the current coordinator/Worker runtime does not advertise
|
|
their features. Compatibility negotiation therefore rejects those workflows
|
|
before planner code runs.
|
|
|
|
## SDK versus workloads
|
|
|
|
`scimesh.sdk` is the framework only: strict manifests, plans, artifacts,
|
|
registry, verifiers, and the local conformance executor. It contains no
|
|
scientific workload code. Workloads are user Python scripts and packages that
|
|
import the SDK and live outside it. The built-in SciMesh workloads are under
|
|
`scimesh/workloads/`:
|
|
|
|
- `scimesh/workloads/search/` — SDK-built `similarity-search@1.0.0`;
|
|
- `scimesh/workloads/graph/` — SDK-built `similarity-graph@1.0.0`;
|
|
- `scimesh/workloads/descriptors/` — SDK-built `descriptor-batch@1.0.0`;
|
|
- `scimesh/workloads/library.py` — the built-in library wiring: a default
|
|
registry containing all three definitions and a runtime advertising their
|
|
capabilities;
|
|
- the plain `scimesh/workloads/*.py` modules remain the local CLI scientific
|
|
cores and their `Workload` registry.
|
|
|
|
Each SDK-built workload is a small package with `core.py` (scientific code)
|
|
and `definition.py` (manifest plus planner/runner/reducer handlers). A future
|
|
external workload library can follow the same shape: its own distribution, one
|
|
`scimesh.workloads` entry point per workload version, and an administrator
|
|
allowlist.
|
|
|
|
## What authors import
|
|
|
|
The stable authoring surface is exported from `scimesh.sdk`:
|
|
|
|
- `WorkloadManifest`, `WorkloadId`, `VersionRange`, `PackageSpec`, and
|
|
`EnvironmentSpec` pin identity and compatibility;
|
|
- `ArtifactSchema`, `PortSpec`, `ArtifactRef`, and `ArtifactCollection` define
|
|
immutable data boundaries without transport URLs or local paths;
|
|
- `WorkflowSpec`, `StageSpec`, `ArtifactEdge`, `TaskSpec`, and `WorkflowPlan`
|
|
define a typed acyclic plan and pin package/manifest digests plus trust mode;
|
|
- `ResourceRequirements` and `ExecutionProfile` separate per-task resources
|
|
from Agent `max_concurrency`;
|
|
- `Planner`, `Runner`, `Reducer`, and `Verifier` are the package handler
|
|
protocols;
|
|
- `OutputManifest` and `Provenance` describe sealed durable results;
|
|
- `WorkloadRegistry` resolves an exact name, version, package digest, runtime,
|
|
environment, and feature set. It never selects an implicit latest version;
|
|
- `MapReduceWorkload` is the primary authoring scaffold for `core-batch-v1`:
|
|
a subclass declares its identity, parameter schema, artifact ports, and
|
|
three scientific hooks (partition, compute, merge), and the SDK assembles
|
|
the manifest, map/reduce stages, workflow, digest-pinned handlers, and the
|
|
exact-artifact verifier. See "Authoring a workload" below.
|
|
|
|
Persisted manifests, requests, plans, tasks, expansions, outputs, candidates,
|
|
decisions, and failures are frozen, recursively immutable, JSON-safe,
|
|
canonically serialized, and strict about unknown fields; their enclosing wire
|
|
contracts carry schema versions.
|
|
Artifact identities contain a coordinator-owned UUID, schema, checksum, media
|
|
type, and bounds; a scientific handler never persists a filesystem path.
|
|
|
|
## Try the built-in SDK workloads
|
|
|
|
This example runs the SDK-built `similarity-search` without starting
|
|
PostgreSQL or the coordinator:
|
|
|
|
```python
|
|
from pathlib import Path
|
|
|
|
from scimesh.sdk import (
|
|
ArtifactCollection,
|
|
JobRequest,
|
|
LocalArtifactStore,
|
|
LocalCoreBatchExecutor,
|
|
)
|
|
from scimesh.workloads.library import (
|
|
default_sdk_registry,
|
|
default_sdk_runtime,
|
|
similarity_search_sdk_definition,
|
|
)
|
|
|
|
root = Path("sdk-run")
|
|
store = LocalArtifactStore(root / "artifacts")
|
|
workload = similarity_search_sdk_definition(shard_rows=1_000)
|
|
|
|
dataset = store.import_file(
|
|
Path("chembl_37_chemreps.txt"),
|
|
declaration=workload.manifest.inputs["input"].schema,
|
|
)
|
|
request = JobRequest(
|
|
workload=workload.manifest.workload,
|
|
parameters={"query_smiles": "CCO", "top_k": 20},
|
|
inputs={"input": ArtifactCollection.single(dataset)},
|
|
)
|
|
|
|
result = LocalCoreBatchExecutor(
|
|
default_sdk_registry(shard_rows=1_000),
|
|
default_sdk_runtime(),
|
|
store,
|
|
root / "attempts",
|
|
).execute(request, workload.manifest.package.digest)
|
|
|
|
result_ref = result.outputs["result"].items[0].artifact
|
|
print(store.materialize(result_ref))
|
|
```
|
|
|
|
`LocalCoreBatchExecutor` is a correctness/conformance runtime, not a substitute
|
|
for coordinator leases or multi-machine scheduling. It accepts only
|
|
`TrustMode.TRUSTED`, `NetworkPolicy.TRUSTED`, single-process/single-threaded CPU
|
|
map/reduce stages without secrets, checkpoints, retries, gangs, or
|
|
accelerators. It does not claim network, timeout, process, or credential
|
|
isolation. Unsupported declarations are rejected before a handler runs. The
|
|
harness runs the SDK-built workload handlers themselves, and their parity
|
|
against the single-process references is covered by automated tests.
|
|
|
|
## The descriptor-batch reference workload
|
|
|
|
`descriptor-batch@1.0.0` is the first SDK-native reference workload: it is
|
|
built directly on the manifest/planner/runner/reducer contracts, and it is
|
|
the intended first `untrusted_quorum` candidate
|
|
(`byte_exact` plus `exact-artifact@1`). Its scientific contract is pinned:
|
|
|
|
- one output CSV row per valid input molecule, in input order, with RDKit
|
|
canonical SMILES recomputed by RDKit;
|
|
- an explicit 81-name pinned RDKit 2D descriptor set (see
|
|
`scimesh/workloads/descriptors/core.py`), validated against the installed RDKit at
|
|
definition build time;
|
|
- `%.6f` float formatting, `utf-8` CSV with one header, and row-bounded
|
|
deterministic shards;
|
|
- `skip_invalid` is the only parameter (default `true`): invalid SMILES rows
|
|
are counted and skipped, or fail the run when `false`;
|
|
- the reducer concatenates shard partials by shard index with exactly one
|
|
header, so the distributed output is byte-identical to the single-process
|
|
reference for the same input rows.
|
|
|
|
```python
|
|
from pathlib import Path
|
|
|
|
from scimesh.sdk import (
|
|
ArtifactCollection,
|
|
JobRequest,
|
|
LocalArtifactStore,
|
|
LocalCoreBatchExecutor,
|
|
WorkloadRegistry,
|
|
)
|
|
from scimesh.workloads.descriptors import descriptor_batch_sdk_definition
|
|
from scimesh.workloads.library import default_sdk_runtime
|
|
|
|
root = Path("descriptor-run")
|
|
store = LocalArtifactStore(root / "artifacts")
|
|
workload = descriptor_batch_sdk_definition(shard_rows=1_000)
|
|
|
|
dataset = store.import_file(
|
|
Path("chembl_37_chemreps.txt"),
|
|
declaration=workload.manifest.inputs["input"].schema,
|
|
)
|
|
request = JobRequest(
|
|
workload=workload.manifest.workload,
|
|
parameters={"skip_invalid": True},
|
|
inputs={"input": ArtifactCollection.single(dataset)},
|
|
)
|
|
registry = WorkloadRegistry()
|
|
registry.register(workload.definition(), enabled=True)
|
|
|
|
result = LocalCoreBatchExecutor(
|
|
registry,
|
|
default_sdk_runtime(),
|
|
store,
|
|
root / "attempts",
|
|
).execute(request, workload.manifest.package.digest)
|
|
|
|
result_ref = result.outputs["result"].items[0].artifact
|
|
print(store.materialize(result_ref))
|
|
```
|
|
|
|
The descriptor-batch entry point `descriptor-batch@1.0.0` is declared in
|
|
`pyproject.toml`; discovery loads it only when an administrator supplies a
|
|
matching `AllowedPackage` allowlist entry. Its manifest declares both
|
|
`trusted` and `untrusted_quorum` trust modes and the exact-artifact verifier,
|
|
so the same definition can later run under coordinator quorum once protocol-v2
|
|
leases exist.
|
|
|
|
## The SDK-built similarity workloads
|
|
|
|
`similarity-search@1.0.0` and `similarity-graph@1.0.0` are SDK-built workloads
|
|
under `scimesh/workloads/search/` and `scimesh/workloads/graph/`; both reuse
|
|
the local scientific cores from `scimesh/workloads/similarity_search.py` and
|
|
`similarity_graph.py` and declare `byte_exact` + `exact-artifact@1`:
|
|
|
|
- the search workload resolves `query_id` exactly once at plan time, shards
|
|
the input deterministically, computes a local top-k per shard with the
|
|
reference heap, and merges the sorted partials with the same tie-breakers,
|
|
so the final CSV is byte-identical to the single-process CLI output;
|
|
- the graph workload parses molecules once into deterministic row-ordered
|
|
blocks, plans one map task per block pair `(i, j)` with `i <= j`, and its
|
|
reducer enforces the pair-coverage invariant (every unordered molecule pair
|
|
compared exactly once, no duplicates) before emitting the same
|
|
deterministically sorted edge list as the local brute-force reference, for
|
|
either threshold direction and any block size;
|
|
- the v1 worker executes SDK-built workloads directly:
|
|
`scimesh/worker/runners.py` is a workload-generic wire bridge that builds a
|
|
`TaskSpec` with the workload's own pins, negotiates against a runtime
|
|
derived from the loaded definitions, reserves resources, seals the partial
|
|
through a content-addressed store, and uploads the resulting CSV over the
|
|
unchanged coordinator contract. The worker loads workloads from
|
|
`SCIMESH_WORKLOAD_ALLOWLIST` (a JSON array of
|
|
`{distribution, name, version, digest}` entries matched against installed
|
|
`scimesh.workloads` entry points) or falls back to the built-in
|
|
`similarity-search`; advertised capabilities come from
|
|
`SCIMESH_CAPABILITIES`. Workloads whose map stage needs more than one input
|
|
port are rejected with a clear message until the coordinator contract
|
|
supports them.
|
|
|
|
## Authoring a workload
|
|
|
|
A workload is a user script that imports the SDK. For the standard
|
|
`core-batch-v1` shape (one input dataset, shards, partials, one merged result)
|
|
subclass `MapReduceWorkload` and implement the three scientific hooks; the
|
|
framework provides everything else:
|
|
|
|
```python
|
|
from pathlib import Path
|
|
from typing import Any, Mapping, Sequence
|
|
|
|
from scimesh.sdk import (
|
|
ArtifactSchema,
|
|
ComponentRef,
|
|
MapReduceWorkload,
|
|
PortSpec,
|
|
SchemaRef,
|
|
WorkloadId,
|
|
)
|
|
|
|
class CountRowsWorkload(MapReduceWorkload):
|
|
workload_id = WorkloadId("count-rows", "1.0.0")
|
|
description = "Count TSV data rows per shard and concatenate the counts."
|
|
parameters_schema = {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {"prefix": {"type": "string", "minLength": 1, "maxLength": 50}},
|
|
}
|
|
input_port = PortSpec(ArtifactSchema(
|
|
SchemaRef("molecule-table", 1), "text/tab-separated-values", "utf-8",
|
|
max_bytes=10**9, validator=ComponentRef("delimited-table", 1),
|
|
validator_configuration={"required_columns": ["canonical_smiles", "chembl_id"]},
|
|
))
|
|
partial_port = output_port = PortSpec(ArtifactSchema(
|
|
SchemaRef("count-table", 1), "text/csv", "utf-8",
|
|
max_bytes=10**9, validator=ComponentRef("delimited-table", 1),
|
|
validator_configuration={"columns": ["id", "rows"]},
|
|
))
|
|
map_parameter_names = ("prefix",)
|
|
|
|
def partition_input(self, input_path, parameters, workspace): # -> list[Path]
|
|
... # deterministic shard files, one per map task
|
|
|
|
def compute_shard(self, inputs, parameters, output_path): # -> Mapping[str, int|float]
|
|
... # one map task; inputs maps each map port to a materialized file
|
|
|
|
def reduce_partials(self, partial_paths, parameters, output_path): # -> Mapping[str, int|float]
|
|
... # deterministic merge of the accepted partials
|
|
```
|
|
|
|
The base class then provides `validate`, `plan`, `run`, `reduce`, and
|
|
`definition()`; the registry, negotiation, resource reservation, verification,
|
|
and the local conformance executor treat the result like any other workload:
|
|
|
|
```python
|
|
from scimesh.sdk import (
|
|
ArtifactCollection,
|
|
JobRequest,
|
|
LocalArtifactStore,
|
|
LocalCoreBatchExecutor,
|
|
WorkloadRegistry,
|
|
)
|
|
from scimesh.workloads.library import default_sdk_runtime
|
|
|
|
workload = CountRowsWorkload(package_digest=..., environment_digest=...)
|
|
registry = WorkloadRegistry()
|
|
registry.register(workload.definition(), enabled=True)
|
|
|
|
store = LocalArtifactStore(Path("artifacts"))
|
|
artifact = store.import_file(Path("tiny.tsv"), declaration=workload.manifest.inputs["input"].schema)
|
|
request = JobRequest(workload=workload.manifest.workload, parameters={"prefix": "x"},
|
|
inputs={"input": ArtifactCollection.single(artifact)})
|
|
result = LocalCoreBatchExecutor(registry, default_sdk_runtime(), store, Path("work")) \
|
|
.execute(request, workload.manifest.package.digest)
|
|
```
|
|
|
|
Hooks you can override beyond the three scientific ones:
|
|
|
|
- `domain_validate(parameters)` — extra job-parameter validation (the JSON
|
|
schema already ran);
|
|
- `resolved_parameters(request)` / `resolved_parameters_for_plan(job, input_path, resolved)`
|
|
— values persisted into the plan (for example one-time query resolution);
|
|
- `plan_tasks(...)` — custom task construction (the graph workload uses this
|
|
to plan one task per block pair with two block inputs);
|
|
- `parse_partial_key(key)` / `validate_partial_keys(parsed)` — partial-key
|
|
policy (default: `map.<eight-digit-index>`, contiguous; the graph workload
|
|
parses `map.<i>x<j>` and enforces the pair-coverage invariant);
|
|
- `map_stage_inputs` — a map stage with more than one input port (each extra
|
|
port must share the external input schema).
|
|
|
|
Anything outside this model uses the lower-level SDK value objects directly.
|
|
Authoring rules: keep the scientific core callable without a coordinator,
|
|
inline a strict JSON parameter schema, declare artifact schemas with bounds,
|
|
return only sink-sealed artifacts, and select a verifier compatible with
|
|
determinism and trust.
|
|
|
|
To run a workload from the command line without writing any program code:
|
|
|
|
```bash
|
|
scimesh workload list
|
|
scimesh workload run count-rows --input tiny.tsv --params '{"prefix": "x"}' -o result.csv
|
|
```
|
|
|
|
`scimesh workload` is a generic SDK tool; it contains no workload-specific
|
|
logic, so new workloads do not require changes to the CLI or any other part of
|
|
the program.
|
|
|
|
## Package shape and registration
|
|
|
|
A workload distribution provides one explicit entry point per workload
|
|
version. The built-in workloads are part of the `scimesh` distribution:
|
|
|
|
```toml
|
|
[project.entry-points."scimesh.workloads"]
|
|
"similarity-search@1.0.0" = "scimesh.workloads.search:workload_definition"
|
|
"similarity-graph@1.0.0" = "scimesh.workloads.graph:workload_definition"
|
|
"descriptor-batch@1.0.0" = "scimesh.workloads.descriptors:workload_definition"
|
|
```
|
|
|
|
The factory returns a `WorkloadDefinition` containing its manifest and handler
|
|
objects. An administrator supplies an `AllowedPackage` with the same
|
|
distribution, exact `WorkloadId`, and `sha256:` package digest. Discovery
|
|
filters installed metadata before importing an entry point and fails
|
|
transactionally if an allowlisted definition is missing or mismatched. Job
|
|
parameters cannot name a module, entry point, package path, or executable.
|
|
The measured digest covers package payload files and installed entry-point
|
|
declarations and is checked before and after loading. It is a content pin, not
|
|
a signature or image attestation; production discovery should run in a fresh
|
|
trusted control-plane process so a pre-populated Python module cache is not an
|
|
integrity boundary.
|
|
|
|
Direct registration is useful for tests and embedded deployments:
|
|
|
|
```python
|
|
registry = WorkloadRegistry()
|
|
registry.register(definition, enabled=False)
|
|
registry.enable(
|
|
definition.manifest.workload.name,
|
|
definition.manifest.workload.version,
|
|
definition.manifest.package.digest,
|
|
)
|
|
```
|
|
|
|
Both version and digest are required when resolving or planning. Upgrading an
|
|
installed definition does not change the identity of an existing Job.
|
|
|
|
## Authoring rules
|
|
|
|
1. Keep the scientific core callable without a coordinator.
|
|
2. Inline a strict JSON parameter schema with `type: object` and
|
|
`additionalProperties: false`; the planner still performs domain validation.
|
|
3. Give every external and stage port an `ArtifactSchema` with a media type,
|
|
schema version, and byte/record/dimension bounds.
|
|
4. Connect stage ports with `ArtifactEdge` values. `WorkflowSpec` checks source
|
|
and target schemas, complete input bindings, declared dependencies, and
|
|
acyclicity.
|
|
5. Declare one `ResourceRequirements` and `ExecutionProfile` per stage. A task
|
|
cannot run until its entire request is eligible and atomically reserved.
|
|
6. Return only sink-sealed artifacts in `OutputManifest`; the local harness
|
|
binds task key/provenance itself and rejects fabricated references,
|
|
unexpected/missing ports, wrong schema/media type, and cumulative output or
|
|
artifact-limit violations.
|
|
7. Select a verifier compatible with determinism and trust. SDK v1 permits
|
|
`untrusted_quorum` only for `byte_exact` plus `exact-artifact@1`.
|
|
8. Add golden fixtures, local/distributed parity, retry/completion-order, and
|
|
verifier failure tests before enabling a package.
|
|
|
|
`ArtifactSink` and `ArtifactCatalog` are bridge-owned protocols. They let
|
|
scientific handlers materialize verified inputs and seal outputs without bearer
|
|
tokens, database credentials, upload URLs, or durable local paths.
|
|
|
|
## Verification
|
|
|
|
The SDK includes:
|
|
|
|
- `ExactArtifactVerifier`: compares logical port/collection/schema/content
|
|
digests while ignoring coordinator UUIDs, timestamps, metrics, and worker
|
|
identity. Quorum inputs use coordinator-created `CandidateOutput` envelopes,
|
|
count at most one vote per owner, and require a `VerificationBinding` for the
|
|
exact task, inputs, parameters, package, manifest, and environment;
|
|
- `CanonicalRecordVerifier`: applies a package-owned bounded canonicalizer and
|
|
compares length-framed canonical records;
|
|
- `NumericToleranceVerifier`: recursively checks structure plus explicit
|
|
absolute, relative, ULP, and NaN policy, returning bounded sanitized evidence.
|
|
|
|
Canonical and numeric objects expose direct bounded comparison methods. To use
|
|
them as manifest `Verifier` handlers, the package supplies an artifact-to-record
|
|
or artifact-to-structured-value loader; without one, verification returns
|
|
`inconclusive` rather than accepting bytes it did not parse.
|
|
|
|
A decision is `accepted`, `rejected`, or `inconclusive`; only `accepted`
|
|
satisfies a stage. Evidence is limited to 16 KiB and cannot contain local paths
|
|
or transport URLs.
|
|
|
|
## Resources and current runtime boundary
|
|
|
|
`ResourcePool` provides a lock-protected all-or-nothing local reservation for
|
|
CPU cores, memory, scratch, and accelerator device/partition IDs, including
|
|
whole-device versus partition conflict fencing. It enforces aggregate capacity
|
|
and execution-slot count. `ExecutionProfile` produces only
|
|
allocation-derived OpenMP/BLAS and device-visibility values; credentials never
|
|
belong to scientific parameters.
|
|
|
|
The current protocol-v1 coordinator stores one input/result per flat task and
|
|
does not persist resource requirements, device allocations, stage edges, or
|
|
package versions. The production Worker also remains serial. Consequently:
|
|
|
|
- SDK `core-batch-v1` can be authored, validated, tested, discovered, and run
|
|
through the trusted local conformance harness now;
|
|
- existing production `similarity-search` remains on its compatible v1 wire
|
|
path and is not renamed;
|
|
- real concurrent claims, GPU scheduling, multi-output DAG execution, dynamic
|
|
loops, streaming, and gang leases require the versioned coordinator/Worker
|
|
changes listed in [`scimesh-sdk-roadmap.md`](scimesh-sdk-roadmap.md);
|
|
- merely declaring a GPU or gang request never enables it. Missing runtime
|
|
features or inventory fail before the planner executes.
|
|
|
|
## Conformance commands
|
|
|
|
Install development tools and run the SDK suite:
|
|
|
|
```bash
|
|
pip install -e '.[dev]'
|
|
pytest tests/test_sdk_models.py \
|
|
tests/test_sdk_resources.py \
|
|
tests/test_sdk_verification.py \
|
|
tests/test_sdk_compatibility.py \
|
|
tests/test_sdk_registry.py \
|
|
tests/test_sdk_descriptors.py \
|
|
tests/test_sdk_search.py \
|
|
tests/test_sdk_graph.py
|
|
```
|
|
|
|
Run `pytest` for the full Worker, local-science, and SDK regression suite. Package authors can reuse `LocalArtifactStore`,
|
|
`LocalCoreBatchExecutor`, and `assert_manifest_round_trip` in their own golden
|
|
tests.
|