Quickstart
Goal: a tiny step where you transform rows and framework writes them. Three files are enough — manifest, thin hooks, processor. Full mental model: Core concepts.
Ship-with-framework reference: examples/hello-step/ in the Framework repo (pytest examples/hello-step). Validate any manifest with drtoller validate path/to/step.drtml --compile. Bootstrap: drtoller init my-pipeline — see AI quickstart.
Minimal package
Section titled “Minimal package”acme_steps/uppercase/ uppercase.drtml hooks.py processor.pyManifest
Section titled “Manifest”The important ideas in the YAML below:
run.type: partition— feed → processor loop;processor.batch— your function;params.runtime.*— batching / metrics knobs (framework reads these, notdomain.*).
drtml: 4step: uppercaserun: type: partition processor: batch: acme_steps.uppercase.processor:process_batch derive: document_registry: from: documents.document_id operation: rowsorchestration: artifacts_root: /tmp/drtoller-dataparams: runtime: document_batch_size: 32 metrics_enabled: true resume_enabled: false max_docs: 0 progress_log_every_n_docs: 100 finalize_flush_every_n_docs: 0 corpus_batch_max_chars: 0 pipeline: read_q_max: 4 write_q_max: 4 lane_q_max: 4 mapping: upstream_runs: source-run-idstorage: default: artifacts stores: artifacts: {backend: local, root: /tmp/drtoller-data}inputs: documents: schema: {document_id: string, text: string} run: mapping.upstream_runs feed: {batch: 256}outputs: uppercase_documents: schema: {run_id: string, document_id: string, text: string} from: documents document_registry: kind: registry key: document_id schema: {run_id: string, document_id: string}Processor
Section titled “Processor”from typing import Any, Mappingfrom drtoller.framework.processing.process_result import ProcessResultfrom drtoller.framework.storage.feed import ProcessorBatch
def process_batch( raws: list[Any], *, ctx, params: Mapping[str, Any], processor_ctx: Mapping[str, Any], partition_id: str,) -> list[ProcessResult]: del params, processor_ctx, partition_id results = [] for batch in raws: if not isinstance(batch, ProcessorBatch): raise TypeError("expected ProcessorBatch") rows = [ { "run_id": ctx.run_id, "document_id": str(row["document_id"]), "text": str(row.get("text") or "").upper(), } for row in batch.primary_rows ] results.append(ProcessResult(ok=True, rows={"documents": rows}, shard_id=batch.shard_id)) return resultsdocuments is a logical output key. DRTML maps it to physical dataset uppercase_documents via from:.
from drtoller.framework.drtml.resolve import manifest_path_adjacentfrom drtoller.framework.processing.partition_loop import run_partition_loop
def run(ctx, *, manifest_path=None): path = manifest_path or manifest_path_adjacent(__file__, step_id="uppercase") return run_partition_loop(ctx, manifest_path=path)What the framework does
Section titled “What the framework does”- loads and validates the v4 manifest;
- opens storage from
storage.stores; - feeds primary input rows according to
inputs.*.feed; - calls the processor and writes logical outputs;
- derives registry rows when
run.deriveis present; - emits metrics/UI/orchestration metadata from the same contract.