Pipeline v2: Retry, Parallelism, and Conditionals for APS Automation
RAPS Pipeline v2 adds retry policies, per-step timeouts, conditional execution, parallel steps, and for-each loops to YAML pipeline files
The Problem With Linear Pipelines
If youβve ever had a 45-minute pipelineβοΈPipelineAutomated sequence of build/test/deploy steps.View in glossary fail at stepπ§STEPISO standard for 3D CAD data exchange.View in glossary 9 of 10 because of a transient 429 error, you know the pain. Pipeline v1 in RAPSπΌRAPSRust CLI for Autodesk Platform Services.View in glossary gave teams a clean way to define multi-step APSβοΈAPSAutodesk Platform Services - cloud APIs for CAD/BIM automation.View in glossary workflows in YAMLπYAMLHuman-readable configuration format.View in glossary: upload a model, translate it, download derivatives, notify stakeholders. But every step ran sequentially, there was no retry logic, no timeout, and no way to branch. One flaky APIπAPIInterface for software components to communicate.View in glossary call killed the whole run, and the only recovery was to start over from the top.
That was fine for simple three-step scripts. It was not fine for production workflows that orchestrate dozens of models across ACCποΈACCAutodesk's construction management platform.View in glossary projects at scale. Teams were wrapping raps pipeline run in shellπ₯οΈTerminalApplication for running command-line programs.View in glossary loops with manual sleep calls, bolting on ad-hoc error handling that nobody wanted to maintain.
Pipeline v2 changes all of that.
Whatβs New in v2
Five capabilities ship in this release, all expressed as optional YAML fields on pipeline steps:
- Retry policies β automatic re-execution of failed steps with configurable backoff
- Per-step timeouts β hard limits that prevent any single step from blocking a run indefinitely
- Conditional execution β
ifandunlessguards that reference the outcomes of earlier steps - Parallel steps β concurrent execution of independent steps within a single stage
- For-each loops β dynamic fan-out over a list of items with concurrency control
Every feature is opt-in. Your existing v1 pipelines continue to work without modification.
Retry Policies
Large Revitπ RevitAutodesk's BIM software for architecture and construction.View in glossary model uploads to OSSπ¦OSSAPS cloud storage for files and models.View in glossary can fail intermittently. Autodesk enforces rate limits, cloud storage has transient errors, and network conditions fluctuate. Rather than failing the entire pipeline, you can now declare a retry policy directly on the step:
steps:
- name: Upload building model
command: object upload my-bucket building.rvt
retry:
max_attempts: 3
delay: 10s
When the object upload commandβΆοΈCommandInstruction executed by a CLI tool.View in glossary exits with a non-zero code, RAPS waits the specified delay (10 seconds here) and retries, up to max_attempts total attempts. The delay is applied between each attempt, giving the upstream service time to recover. If all attempts fail, the step is marked as failed and the pipeline proceeds according to whatever conditional logic you have defined.
You can also use delay: exponential for exponential backoff, which doubles the wait on each subsequent retry β useful when you are hitting a rate limiter that penalizes rapid retries.
Step Timeouts
Model TranslationβοΈTranslation JobBackground process converting CAD files to viewable formats.View in glossary via the Model Derivative APIπModel DerivativeAPS service for translating and extracting CAD data.View in glossary can take anywhere from seconds to hours depending on model complexity. Without a timeout, a stuck translation blocks your entire pipeline run forever:
- name: Translate to SVF2
command: translate start $URN --format svf2 --wait
timeout: 30m
The timeout field accepts durations like 30s, 5m, 2h, or combinations thereof. If the step has not completed when the timeout expires, RAPS terminates the process and marks the step as failed. Combined with a retry policy, this lets you express βtry the translation up to 3 times, but never spend more than 30 minutes on a single attempt.β
Conditional Execution
Not every step should run on every execution. Sometimes you only want to download derivatives if the translation succeeded, or send a Slack notification if it failed. Pipeline v2 introduces if and unless guards that reference step outcomes using expression syntax:
- name: Download derivatives
command: translate download $URN --output ./output
if: "${{ steps.translate.exit_code == 0 }}"
- name: Notify failure
command: api post https://hooks.slack.com/... --body '{"text":"Translation failed"}'
unless: "${{ steps.translate.exit_code == 0 }}"
The expression inside ${{ }} is evaluated at runtime. You can reference any earlier step by its name (normalized to snake_case). The if guard runs the step only when the expression is truthy; unless runs it only when the expression is falsy. This replaces the shell-script gymnastics teams were using to handle conditional logic outside of the pipeline file.
Available variables inside expressions include steps.<name>.exit_code, steps.<name>.duration, and env.<VAR> for environment variables.
Parallel Steps
Uploading models one at a time is slow when you have an architectural model, a structural model, and an MEP model that are completely independent of each other. The parallel block runs its children concurrently:
- name: Upload all models
parallel:
- name: Upload architectural
command: object upload project-bucket architectural.rvt
- name: Upload structural
command: object upload project-bucket structural.rvt
- name: Upload MEP
command: object upload project-bucket mep.rvt
All three uploads start simultaneously. The parent step completes when every child has finished. If any child fails, the parent step is marked as failed (but the other children are allowed to complete). Each child inside a parallel block supports the same fields as a top-level step: retry, timeout, and conditionals all work.
For a three-model upload where each file takes roughly 2 minutes, parallel execution cuts wall-clock time from 6 minutes to about 2 minutes.
For-Each Loops
When the list of items is dynamic or long, hard-coding parallel children becomes unwieldy. The for_each construct iterates over a list and executes a template step for each item, with optional concurrency control:
- name: Translate models
for_each:
items: ["architectural.rvt", "structural.rvt", "mep.rvt"]
variable: MODEL
max_concurrency: 2
template:
name: "Translate ${MODEL}"
command: "translate start $(raps object urn project-bucket ${MODEL}) --wait"
This creates three translation steps from the template, but only runs two at a time (max_concurrency: 2). This is useful when the upstream API imposes per-client concurrency limits and you want to maximize throughput without triggering rate-limit errors.
The items field can also reference an environment variableπEnvironment VariableConfiguration value passed to applications.View in glossary or the output of a previous step, enabling truly dynamic pipelines where the set of models to process is determined at runtime.
Putting It All Together
Here is a complete pipeline that combines every v2 feature into a realistic production workflowπWorkflowAutomated process triggered by events.View in glossary: upload models in parallel, translate each with retry and timeout, download derivatives conditionally, and clean up on failure.
name: Full Model Processing Pipeline
version: 2
env:
BUCKET: project-models-prod
OUTPUT_DIR: ./derivatives
steps:
- name: Upload all models
parallel:
- name: Upload architectural
command: object upload $BUCKET architectural.rvt
retry:
max_attempts: 3
delay: 5s
- name: Upload structural
command: object upload $BUCKET structural.rvt
retry:
max_attempts: 3
delay: 5s
- name: Upload MEP
command: object upload $BUCKET mep.rvt
retry:
max_attempts: 3
delay: 5s
- name: Translate models
for_each:
items: ["architectural.rvt", "structural.rvt", "mep.rvt"]
variable: MODEL
max_concurrency: 2
template:
name: "Translate ${MODEL}"
command: "translate start $(raps object urn $BUCKET ${MODEL}) --format svf2 --wait"
timeout: 30m
retry:
max_attempts: 2
delay: 30s
- name: Download derivatives
command: translate download --all --output $OUTPUT_DIR
if: "${{ steps.translate_models.exit_code == 0 }}"
timeout: 15m
- name: Notify success
command: api post https://hooks.slack.com/T00/B00/xxx --body '{"text":"All models processed"}'
if: "${{ steps.translate_models.exit_code == 0 }}"
- name: Notify failure
command: api post https://hooks.slack.com/T00/B00/xxx --body '{"text":"Pipeline failed β check logs"}'
unless: "${{ steps.translate_models.exit_code == 0 }}"
- name: Cleanup temp files
command: object delete $BUCKET --prefix tmp/
if: "${{ env.CLEANUP == 'true' }}"
This pipeline handles transient failures gracefully, respects API concurrency limits, branches based on outcomes, and finishes in a fraction of the time its v1 equivalent would take.
Backward Compatibility
Pipeline v2 is a strict superset of v1. Every v1 pipeline file is a valid v2 pipeline. The version: 2 declaration at the top is recommended but not required β if omitted, RAPS auto-detects based on whether v2-only fields are present. No migration is necessary. You can adopt v2 features incrementally, one step at a time.
Getting Started
Update RAPS to the latest release, then validate your pipeline file before running it:
raps pipeline validate my-pipeline.yaml
You can also dry-run the pipeline to see the execution plan without making any API calls:
raps pipeline run my-pipeline.yaml --dry-run
The dry-run output shows the step execution order, which steps will run in parallel, how retry and timeout are configured, and which conditionals are present.
Full documentation for every v2 field is available in the Pipeline Reference section. If you run into issues or have feature requests, open an issue on GitHub.