Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Writing a Node Manifest

A node manifest (dora-node.yml) is the typed, versioned description of a reusable dora node: its entry point, build command, typed input/output contracts, and configuration surface. It is what turns a folder of code into a node that other people can discover and wire up with one line of YAML:

nodes:
  - id: detector
    hub: dora-yolo@^0.5      # ← resolved from a manifest, no PATH knowledge
    inputs:
      image: camera/image
    outputs:
      - bbox

The manifest holds only what a language’s native manifest cannot: dora contracts and dora wiring. Name, version, and dependencies stay in pyproject.toml / Cargo.toml; the manifest never duplicates them.

hub: is an unstable feature — its behaviour may change in a future release. The manifest format itself (apiVersion: 1) is stable and useful on its own: a dora-node.yml next to a plain path: node is picked up for contract validation today, with no index involved (see Local development).

A complete manifest

# dora-node.yml — lives next to pyproject.toml / Cargo.toml
apiVersion: 1                 # manifest schema version (currently 1)
name: dora-yolo               # defaults to [project].name / [package].name
namespace: dora-rs            # index namespace (== a GitHub org/user)
description: YOLO object detection on camera frames
categories: [ml-inference]    # from the fixed category list
keywords: [vision, detection, yolo]

runtime: python               # python | rust | c | cpp
entrypoint: dora-yolo         # what to run, relative to the node's working dir
build: pip install .          # how to build it (run in the working dir)

platforms: []                 # optional allowlist, e.g. [linux-x86_64,
                              # linux-aarch64]; empty = all
dora: ">=0.4"                 # optional supported dora version range

inputs:
  image:
    type: std/media/v1/Image  # type URN (see Type Annotations)
    required: true
    description: BGR frame to run detection on
outputs:
  bbox:
    type: std/vision/v1/BoundingBox
    description: detected bounding boxes
  # a node may have zero inputs (a source) or zero outputs (a sink)

env:                          # configuration surface, documented + typed
  MODEL:
    default: yolov8n.pt
    description: model weights file
  CONFIDENCE:
    type: float
    default: 0.4

types: {}                     # optional custom type definitions, keyed by
                              # type URN (see below); omit when there are none

example: |                    # snippet shown by `dora hub info`
  - id: detector
    hub: dora-yolo@^0.5
    inputs:
      image: camera/image
    outputs:
      - bbox

Field reference

FieldRequired备注
apiVersionyesmanifest schema version; currently 1
namedefaultablefalls back to [project].name / [package].name; PEP 503-normalized
namespaceyesindex key is namespace/name; equals a GitHub org/user
runtimeyespython | rust | c | cpp
entrypointyesa relative path within the working dir — no absolute, no .. (see below)
buildrecommendedthe build command, run in the working dir (pip install ., cargo build --release, a cmake invocation)
inputs, outputsyes, may be emptysinks have no outputs; sources no inputs
inputs.*.type etc.recommendedtype URNs; omit to leave a port untyped (validation skips it)
platforms, dorarecommendedplatform allowlist (validated for <os>-<arch> shape; drives dora hub search --platform filtering) + dora compat range
description, categories, keywordsrecommendeddrive dora hub search
env, types, requirements, exampleoptionalconfiguration, shipped types, declared needs, doc snippet

The manifest schema ships as JSON Schema at libraries/core/dora-node-schema.json for editor autocompletion.

The entrypoint

entrypoint is validated as a relative path within the node’s working directory — absolute paths and .. components are rejected:

  • Python: a console script on the managed-env PATH (the [project.scripts] name), e.g. dora-yolo.
  • Rust / C / C++: the build output, e.g. target/release/dora-yolo or build/lidar.

When resolved from a hub package, the entrypoint is looked up only inside the node’s own working dir or managed environment — the ambient $PATH is not searched, so a typo fails loudly instead of silently running a host binary.

Typed contracts

Input and output types use dora’s existing type URN system: std/<category>/v1/<TypeName>, nested struct schemas, parameterized types, and a widening-compatibility graph. When a hub: (or adjacent path:) node is composed, the manifest’s inputs/outputs types surface as the node’s input_types / output_types, and the normal validation pipeline applies:

dora validate dataflow.yml   # producer/consumer URN compatibility, field diffs
dora build dataflow.yml      # same checks, before anything runs

A mismatch — wiring a std/vision/v1/BoundingBox output into an input expecting std/media/v1/Image — fails at compose time with the producer/consumer URNs, before any process starts. The dataflow author’s own annotations always win over the manifest’s.

Shipping custom types

A node may ship its own type definitions (same YAML shape as types/std/*/v1.yml). They must live under the package’s own namespacestd/ and other namespaces are rejected:

namespace: acme
types:
  acme/lidar/v1/PointCloud:
    arrow: Struct
    fields:
      - name: x
        type: Float32
      - name: y
        type: Float32
outputs:
  cloud:
    type: acme/lidar/v1/PointCloud

Shipped types are loaded into the dataflow’s type registry at resolution time, so a consumer can reference them in its own port checks.

Scaffolding with dora hub init

dora hub init writes a starter dora-node.yml next to your pyproject.toml or Cargo.toml, pre-filling what it can read from there (name, entrypoint from [project.scripts] / the binary target, description):

cd my-node
dora hub init               # writes ./dora-node.yml
dora hub init path/to/node  # or target a specific directory

Fill in your inputs/outputs types and env, then validate the manifest on its own:

dora validate --node-manifest dora-node.yml

Local development (no index)

You do not need to publish anything to get contract validation. A dora-node.yml sitting next to a plain path: node is picked up automatically:

nodes:
  - id: detector
    path: target/release/detector   # dora-node.yml lives at the node's root
    inputs:
      image: camera/image

dora build / dora validate read the adjacent manifest, inject its typed ports, and check your wiring — the same validation a hub consumer gets. This is the recommended inner loop while authoring a node’s contracts.

Build-location guidance

A node built from source means something compiles on the machine that runs it. dora’s multi-daemon flow has each daemon build its own nodes — fine on a laptop, but slow-to-impossible on a constrained device (a Jetson compiling a heavy node can thermal-throttle or run out of memory).

  • Compile-on-target (the default): acceptable for capable machines and light nodes.
  • Build-on-a-host-and-ship (recommended for embedded): build for the target architecture on a capable machine and deploy the result via the offline cache flow, so the robot never compiles.

As a rule of thumb: don’t run heavy builds on constrained daemons.