Algorithm Manifests (v1)
Complete guide to defining v1 algorithm manifests
Algorithm Manifests (v1)
An algorithm manifest is a JSON document that describes your algorithm's requirements, capabilities, and behavior. The platform validates this manifest against the manifest schema when you register an algorithm.
This page documents manifest schema version 1.0.0. Manifests declaring manifest_version: 0.1.0 remain valid and are documented in Algorithm Manifests; see Migrating from 0.1.0 below for the mapping between the two.
Complete Example
Here's a complete manifest for a ship detection algorithm running as a container:
{
"manifest_version": "1.0.0",
"metadata": {
"description": "Wide-area ship detection on high-resolution Planet SkySat imagery, producing rotated bounding box detections and classifying ships into 8 commercial, passenger, and military classes.",
"tags": ["skysat_ship"]
},
"runtime": {
"type": "container",
"image": "orbitalinsight/skysat_ship_d2:64361851",
"command": ["python", "/orbital/base/algorithms/skysat_ship_d2/src/py/skysat_ship_d2/simple_inference.py"],
"resource_request": {
"gpu": 1,
"memory_gb": 16,
"cpu_millicore": 1000
}
},
"io": {
"interface": "paths",
"inputs": [
{
"data_type_name": "planet_SkySatScene",
"parameters": {
"image_processing_spec": "PL-SkySatScene",
"bands": [91]
}
}
],
"outputs": [
{
"data_type_name": "multiclass_object_detection"
}
]
},
"capability": {
"classes": ["aircraft_carrier", "large_cargo", "military_warship", "cruise_ship",
"submarine", "tugboat", "small_vessel", "other_ship"],
"metrics": {
"f1_score": 0.6746,
"precision": 0.7223,
"recall": 0.6329,
"per_class": [
{"class": "aircraft_carrier", "precision": 0.7931, "recall": 0.76, "f1_score": 0.7762},
{"class": "large_cargo", "precision": 0.7950, "recall": 0.75, "f1_score": 0.7719},
{"class": "military_warship", "precision": 0.7602, "recall": 0.68, "f1_score": 0.7179},
{"class": "cruise_ship", "precision": 0.8266, "recall": 0.82, "f1_score": 0.8333},
{"class": "submarine", "precision": 0.9115, "recall": 0.80, "f1_score": 0.8521},
{"class": "tugboat", "precision": 0.7360, "recall": 0.62, "f1_score": 0.6731},
{"class": "small_vessel", "precision": 0.6618, "recall": 0.67, "f1_score": 0.6659},
{"class": "other_ship", "precision": 0.5106, "recall": 0.49, "f1_score": 0.5001}
]
},
"coverage": "Trained on 2,843 Planet SkySat scenes over open water and coastal areas across 6 continents (April 2020 - April 2021), with roughly 10% adverse-weather imagery.",
"location": "Open and coastal waters worldwide; performs best in calm water where ships are not packed closely together.",
"known_issues": "Reduced accuracy in dense anchorages and dark areas, and under snow, haze, or cloud. High appearance variance within a class (e.g. military warships) can degrade classification."
},
"parameters": [
{
"name": "sts_enabled",
"type": "boolean",
"description": "Enable ship-to-ship transfer detection on detected vessels.",
"default": false
},
{
"name": "ignore_aoi",
"type": "boolean",
"description": "Turn off AOI filtering on detections.",
"default": false
},
{
"name": "get_small_vessel",
"type": "boolean",
"description": "Turn on small vessel detections.",
"default": false
}
],
"dimensions": [
{"name": "AOI"},
{"name": "SCENE_ID"}
]
}Different algorithms require different runtime engines. This is specified using the runtime block. For example, an algorithm that is run using a Triton inference service would have the following runtime block:
{
"runtime": {
"type": "triton",
"image": "orbitalinsight/skysat_ship_prepost:a1b2c3d4",
"command": ["python", "-u", "/app/prepost.py"],
"model_name": "f0cd3037-8a2e-4b6d-9c15-7e3f5a1b8d42",
"model_architecture": "yolox",
"tensor_input": {"channels": 3, "height": 640, "width": 640},
"resource_request": {
"gpu": 0,
"memory_gb": 8,
"cpu_millicore": 1000
}
}
}And an algorithm that runs as a Flink stream-processing job ships a jar rather than a container:
{
"runtime": {
"type": "flink",
"jar": "s3://oi-artifacts/track-enrichment-1.4.2.jar",
"entry_class": "com.orbitalinsight.tracks.EnrichmentJob",
"parallelism": 4
}
}Manifest Structure
The following four fields are required: manifest_version, metadata, runtime, and io. The rest (capability, lineage, parameters, and dimensions) are optional.
v1 manifests are validated strictly: fields that are not part of the schema are rejected.
manifest_version (required)
Specifies which version of the manifest schema to use, in semantic-version form.
{
"manifest_version": "1.0.0"
}Current version: "1.0.0"
The platform validates your manifest against the schema for this version.
metadata (required)
Provides high-level information about your algorithm.
{
"metadata": {
"description": "Detect and count railcars in optical imagery.",
"tags": ["railcar", "rail", "object_detection"]
}
}Fields:
description(string, required) - Clear description of the algorithm's purposetags(array of strings, optional) - Keywords for categorization and search
What the algorithm can do, how well, and where it works belongs in capability, not here.
Note: there is no
namefield. The algorithm's human-readable name belongs to the Algorithm entity and is supplied at registration time — renaming an algorithm does not create a new Algorithm Version. There is also noversionfield: Algorithm Versions are identified by the SHA-256 hash of the manifest — see Manifest Versioning.
runtime (required)
Specifies the execution environment for the algorithm: what runs, where the model executes, and what resources it needs.
{
"runtime": {
"type": "container",
"image": "myorg/my-algorithm:v1.0.0",
"command": ["python", "-u", "/app/main.py"],
"resource_request": {
"gpu": 0,
"memory_gb": 8,
"cpu_millicore": 1000,
"max_input_gb": 1
}
}
}Fields:
type(string, required) - one of:container- the algorithm runs entirely in its own container (the standard runtime)triton- the algorithm container performs pre/post processing and calls the shared Triton inference service for model inferenceflink- the algorithm runs as a job on the Flink stream-processing engine, shipped as a jar - there is no container, so noimageorcommand
image(string, required forcontainerandtriton) - Full container image reference (registry/name:tag)command(array of strings, required forcontainerandtriton) - Container command in exec form (e.g.["python", "main.py"], not"python main.py")resource_request(object, optional) - Resource requirements:gpu(number) - Number of GPUs (0for CPU-only)memory_gb(number) - Memory in gigabytescpu_millicore(number) - CPU in millicores (1000= 1 core)max_input_gb(number) - Maximum size in GB for the algorithm input
Additional fields for type: triton:
model_name(string, required for triton) - The model to invoke on the inference server. The server endpoint is resolved by the platform at run time and injected intoalgo_input.json; it is never part of the manifest.model_architecture(string, optional) - The architecture of the served model (e.g."yolox")tensor_input(object, optional) - The served model's input tensor shape:channels,height,width
For type: triton, the GPU lives with the shared Triton service, so the algorithm container is typically CPU-only ("gpu": 0).
Additional fields for type: flink:
jar(string, required for flink) - Reference to the job jar artifactentry_class(string, optional) - Fully qualified entry class of the job; required if the jar's manifest does not declare oneparallelism(number, optional) - Flink job parallelism;resource_requestdoes not apply — the job's resources are governed by the Flink engine
Runtime types name what the algorithm author ships — a container, a Triton-served model with its pre/post container, a Flink jar — never the platform service that schedules the run. New types arrive in a schema revision; validation rejects values the schema doesn't know.
Best practices:
- Use specific image tags, not
latest; include the image digest for reproducibility. - Request resources conservatively but adequately; monitor actual usage during test runs.
- Use the
-uflag with Python for unbuffered output.
io (required)
Specifies how data gets into and out of the algorithm: the I/O interface it implements, the Data Types it consumes and produces, and — for imagery algorithms that need scenes grouped in a particular way — an optional scene_selection block.
{
"io": {
"interface": "urls",
"inputs": [
{
"data_type_name": "pings",
"min_count": 1,
"max_count": 1
}
],
"outputs": [
{
"data_type_name": "device_visits"
}
]
}
}io.interface (required)
Declares which I/O contract the algorithm's code implements. This is an author declaration, not a hint: each value requires different code inside the algorithm container, and the platform provides a different environment for each.
interface | Documents | The platform | The algorithm |
|---|---|---|---|
paths | v1 | downloads all input data to local disk before the run | reads local filesystem paths from algo_input.json |
urls | v2 | downloads nothing; composes each input's data-source query as a ready-to-fetch URL and injects it | fetches the URLs itself — any URL-capable reader (fsspec, GDAL, pandas) works |
api | v2 | downloads nothing; injects data-source endpoints and scoped credentials | composes its own queries, e.g. through the data source's client library |
Notes on each value:
pathsis the backwards-compatible contract: existing algorithms adopt this manifest schema without container changes by declaring it.urlsentries are ready to fetch exactly as given — the query is already written, and any required authorization is already embedded in the URL. Anurlsalgorithm can only be paired with data sources whose queries are expressible as URL parameters; registration validates this.apiis for algorithms that drive a data source themselves — parameterized queries (time windows, geohashes, filters) through the source's API or client library.
urls and api are the same thing underneath — data-source API access — differing only in who writes the query. Both run behind the platform trust boundary: pod egress is allowlisted to the injected endpoints, and credentials are scoped and short-lived.
Every value exchanges the same pair of documents: input via an algo_input.json file (path in the ALGORITHM_INPUT_PATH environment variable), output written to the supplied output_path. paths uses the v1 document schemas; urls and api use v2. See Algorithm Input/Output.
Future interface values name drivable contracts — a documented protocol the platform (or a bridging sidecar) can be implemented against, such as a vendor ICD (e.g. airbus_icd) — never bare transports like http.
io.inputs (required)
Declares the Data Types your algorithm consumes. This is an array — one entry per input Data Type.
Fields per input:
data_type_name(string, required) - Name of the Data Type (must exist in the platform; retrieve withDataTypesGetRequest)min_count(integer, optional, default1) - Minimum number of data sources requiredmax_count(integer, optional, default1) - Maximum number of data sources allowedparameters(object, optional) - Data-Type query parameters conforming to the Data Type's query schema (e.g.image_processing_spec,bands,lookback)
Notes:
- Most algorithms have a single input Data Type.
- Use multiple inputs if your algorithm combines different data types.
- Set
min_count: 0for optional inputs. - Inputs declare data types, not data sources. Which data source supplies a type is chosen in the Algorithm/Analysis Configuration, not the manifest.
io.outputs (required)
Declares the Data Types your algorithm produces. This is an array — one entry per output Data Type (each entry's own observation_value_columns lists that type's summary metrics).
Fields per output:
data_type_name(string, required) - The output Data Type
Outputs are objects even though they carry a single field today — consistent with inputs, and open to per-output fields later. The classes an algorithm detects are declared in capability, not per output.
A summary rollup (a detection count, a unique-device count) is not declared on an output — it is its own output with a dedicated data type (e.g. detection_count), written by the algorithm alongside its detail output. The columns an output contains are described by its Data Type. See Algorithm Output Schema for the full output structure.
io.scene_selection (optional)
A declarative block consumed by the platform's scene-selection step. Use it for algorithms that need scenes grouped or paired in a particular way (e.g. stereo pairs, before/after change detection). It refines io.inputs: inputs declare which Data Types the algorithm consumes, scene_selection declares which scenes qualify and how they combine into a run.
{
"io": {
"scene_selection": {
"grouping_strategy": "pair",
"temporal_strategy": "window",
"spatial_strategy": "independent",
"min_count": 2,
"lookback_windows": 0,
"spacing": {
"min_separation": "P10D",
"max_separation": "P40D"
},
"data_source_filters": ["cloud_cover <= 20"]
}
}
}Fields:
grouping_strategy(required) - how scenes are grouped:single,independent,pair,crosstemporal_strategy(required) - how time is sliced:all,window,eventspatial_strategy(required) - how AOIs combine with grouping:independent,cross_aoimin_count(integer, optional, default1) - minimum scenes per groupspacing(object, optional) - temporal spacing between scenes within a group (min_separation/max_separation, ISO 8601 durations); only valid forpairorcrossdata_source_filters(array, optional) - CQL2 filter expressions that replace the data source's default constraints for this algorithm (usercql2_filtersAND on top)grouping_filters(array, optional) - CQL2 filter expressions for grouping, usingreference.*/target.*prefixesbefore_search/after_search(ISO 8601 durations) - required whentemporal_strategyiseventlookback_windows(integer) - required whentemporal_strategyiswindow;0means current window only (e.g. stereo pairs)
capability (optional)
What the algorithm can do, how well, and where it works — the block a user (or an agent) reads to decide whether the algorithm fits their use case.
{
"capability": {
"classes": ["fighter", "bomber"],
"metrics": {
"f1_score": 0.87,
"precision": 0.91,
"recall": 0.83,
"per_class": [
{"class": "fighter", "precision": 0.92, "recall": 0.85, "f1_score": 0.88},
{"class": "bomber", "precision": 0.89, "recall": 0.81, "f1_score": 0.85}
]
},
"coverage": "Trained on ~100,000 labeled aircraft across 20,000 scenes worldwide.",
"location": "Works best at airfields and military bases.",
"known_issues": "Reduced accuracy on heavily shadowed or hazy imagery."
}
}Fields:
classes(array of strings, optional) - The classes the algorithm detects or classifies. Algorithm-level: one vocabulary regardless of how many outputs the algorithm produces. For Model Studio models it is filled from the model's categories at registration.metrics(object, optional) - Reported accuracy:f1_score,precision, andrecallacross all classes, plusper_class(array ofclass,precision,recall,f1_score).per_classentries reference names inclasses.coverage(string, optional) - What data the algorithm was trained and evaluated onlocation(string, optional) - Where/when the algorithm works bestknown_issues(string, optional) - Known limitations
lineage (optional)
Records where the algorithm's model came from. For models trained in Model Studio, these fields are filled from the export automatically at registration; agents and users read them to judge whether a model fits their use case.
{
"lineage": {
"source": "model-studio",
"project_id": "7c2a91e4-31f8-4b0a-9d55-8f2e6a7c4d13",
"experiment_id": "2f8b03d7-6a91-4c25-b7e8-1d4f9c0a5e66",
"run_id": "91e5c6a2-4d78-4f31-a2b9-6c8e0d3f7a24",
"action_id": "f0cd3037-8a2e-4b6d-9c15-7e3f5a1b8d42",
"dataset": {
"dataset_id": "5d417f9b-2c86-4e03-9a71-3b8d6f2c9e48",
"dataset_commit_id": "8a30c1e6-7f24-4d95-b1c8-2e6a9d4f0b37",
"num_samples": 18450
}
}
}Fields:
source(string, optional) - The system that produced the model (e.g."model-studio")project_id/experiment_id/run_id(strings, optional) - The Model Studio training lineageaction_id(string, optional) - The Model Studio export action id — the stable key joining this Algorithm Version to the exported modeldataset(object, optional) - The dataset the model was trained on:dataset_id,dataset_commit_id,num_samples
parameters (optional)
Defines configurable parameters users can set when creating an Algorithm Config.
{
"parameters": [
{
"name": "confidence_threshold",
"type": "double",
"description": "Minimum confidence score for detections",
"units": "probability",
"min": 0.0,
"max": 1.0,
"default": 0.7
},
{
"name": "enable_filtering",
"type": "boolean",
"description": "Apply post-processing filters",
"default": true
},
{
"name": "model_variant",
"type": "string",
"description": "Which model variant to use",
"allowed_values": ["fast", "standard", "accurate"],
"default": "standard"
}
]
}Fields per parameter:
name(string, required) - Parameter identifiertype(string, required) - One ofinteger,double,number,string,boolean,arraydescription(string, required) - Clear explanation of what the parameter doesunits(string, optional) - Units for the parameter (e.g. "seconds", "meters")min(number, optional) - Minimum allowed value (numeric types)max(number, optional) - Maximum allowed value (numeric types)allowed_values(array, optional) - Restricted set of allowed valuesdefault(optional) - Default value used when a config doesn't override the parameter
The manifest declares parameters and their defaults; the chosen values for a deployment live in an Algorithm Config, never in the manifest. Changing a parameter value is a config change, not a new Algorithm Version.
Best practices:
- Provide sensible defaults and use
min/maxto prevent invalid values. - Include units for clarity and write descriptions that explain each parameter's impact.
dimensions (optional)
Lists the Dimensions the algorithm can be parallelized by, and how each may be subdivided.
{
"dimensions": [
{
"name": "AOI",
"subdividable": true,
"units": "geohash",
"size": 2
},
{
"name": "TIME_RANGE_GROUP"
}
]
}Fields per entry:
name(string, required) - the Dimension name (e.g.AOI,TIME_RANGE_GROUP)subdividable(boolean, optional) - whether the Dimension can be subdividedunits(string, optional) - unit of the subdividable component (e.g.geohash)size(number, optional) - quantity of units of the subdividable component
Note: the manifest's
dimensionsdeclares which Dimensions apply to the algorithm; thedimensionsobjects inalgo_input.jsonandalgorithm_output.jsoncarry the values of those Dimensions for a specific run. Same concept, declaration vs. instance.
Validation
When you register an algorithm, the platform validates:
- Schema compliance - Manifest matches the schema for
manifest_version; unknown fields are rejected - Data Type existence - All referenced Data Types exist
- Data Source compatibility - Data sources support the specified Data Types (validated against DEM)
- Parameter validity - Parameters have valid types and constraints
- Resource constraints - Resources are within platform limits
Common validation errors:
Invalid manifest: 'io.outputs' must be an array
→ Fix: outputs is a list of {data_type_name, ...} objects, not an object
Data Type 'custom_type' not found
→ Fix: Create the Data Type first or use an existing one
Parameter 'threshold' missing required field 'description'
→ Fix: name, type, and description are required on every parameter
'runtime.model_name' is required when runtime.type is 'triton'
→ Fix: name the served model; the Triton endpoint itself is injected at run time
Manifest Versioning
Algorithm versions are identified by the SHA-256 hash of the manifest — there is no version number in the manifest itself.
- Registering a manifest computes its hash. If that hash already exists for the algorithm, the existing Algorithm Version is returned unchanged (no duplicate is created).
- Any change to the manifest produces a new hash and therefore a new Algorithm Version.
- Algorithm Versions are immutable once registered, and are never deleted — only deactivated or deprecated.
- The user-facing "version" label is the creation timestamp (ISO 8601) of the Algorithm Version; the latest version is the one with the most recent timestamp.
So to publish an update — a bug fix, a new model, changed parameters, or changed inputs/outputs — you simply register the updated manifest, which becomes a new Algorithm Version. For changed inputs/outputs in particular, consider whether a new Algorithm is more appropriate than a new version of the existing one.
manifest_version selects the schema, not the algorithm version: 0.1.0 manifests remain valid indefinitely and are validated against the legacy schema. Moving an algorithm's manifest to 1.0.0 is a manifest change like any other — it mints a new Algorithm Version.
Migrating from 0.1.0
0.1.0 manifests remain valid during the transition, and all algorithm manifests are being migrated to 1.0.0. Migrating a manifest does not require changing the algorithm itself — declare io.interface: paths and the container runs unchanged. The mapping:
| 0.1.0 | 1.0.0 |
|---|---|
container_parameters.image / .command / .resource_request | runtime.image / .command / .resource_request |
| — | runtime.type (container, triton, or flink; new, required) |
interface.interface_type: FILESYSTEM_TASK_WORKER | io.interface: paths |
inputs / outputs (top level) | io.inputs / io.outputs |
parallelization[].dimension | dimensions[].name |
parallelization[].subdividible | dimensions[].subdividable (the spelling code actually reads) |
performance_metrics.overall_f1_score / .overall_precision / .overall_recall | capability.metrics.f1_score / .precision / .recall |
metrics_per_subclass[] (subclass) | capability.metrics.per_class[] (class) |
evaluation_details | lineage.dataset |
top-level name, display_name, author, description, developer, release_status | not in the manifest — supplied to the registration API; they describe the Algorithm entity, and changing them does not create a new Algorithm Version |
metadata.name, metadata.version, metadata.release_date, metadata.indicator | removed |
restrictions, batching, tiling | removed (never used) |
interface.adapter, NO_OP_WORKER, SYNCHRONOUS_TASK_HTTP_WORKER | removed; vendor contracts return as named io.interface values (e.g. airbus_icd) if needed |
inputs[].data_source_ids / .data_source_id / .data_source_name | removed — data source choice lives in the Algorithm/Analysis Configuration |
outputs[].classes (and the undocumented top-level classes drift) | capability.classes |
metadata.training_location_coverage, metadata.best_input_location, metadata.known_issues | capability.coverage, capability.location, capability.known_issues |
outputs[].observation_value_columns | removed — aggregates are their own outputs with dedicated data types; an output's columns are described by its Data Type |
outputs[].output_geometry | removed — never consumed; output records' geometry (WKB/WKT) is self-describing |
inputs[].parameters.sampling, outputs[].sample_result, outputs[].skip_export, algorithm_parameters, visualizer_config_names | removed |
Next Steps
- Learn about Algorithm Input/Output to understand the data your algorithm will process
- See Container Images for packaging your algorithm
- Follow Registering Algorithms to publish your algorithm
Updated 27 days ago