Skip to content

feat(local-runner): add recovery policies and reusable pipelines - #436

Merged
jamesbhobbs merged 1 commit into
feat/local-orchestrationfrom
feat/local-orchestration-policies
Aug 5, 2026
Merged

jamesbhobbs merged 1 commit into
feat/local-orchestrationfrom
feat/local-orchestration-policies

Conversation

@jamesbhobbs

@jamesbhobbs jamesbhobbs commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #435. Review the incremental diff only; this PR adds the next two orchestration capabilities:

  • retry/recovery policies with selective retry, exponential backoff, explicit idempotency, and fallback notebook steps
  • reusable and nested sub-pipelines with scoped child IDs and parent graph metadata
  • persistence-aware retries and sub-pipeline input fingerprints
  • policy and pipeline nodes in the static pipeline gallery
  • an end-to-end regional fan-out demo using one reusable sub-pipeline and policy

Validation

  • pnpm test — 2,699 passed, 1 skipped
  • pnpm typecheck
  • pnpm build
  • pnpm biome:check — passes with 7 existing warnings
  • pnpm prettier:check
  • pnpm spell-check
  • pnpm example:orchestration — completed against Deepnote Cloud, including both regional notebook runs and the concluding agent run

Summary by CodeRabbit

  • New Features

    • Added reusable orchestration pipelines for composing regional and nested workflows.
    • Added configurable retry policies with backoff, fallback steps, and controlled failure handling.
    • Improved execution visibility by showing pipeline lifecycle events and individual retry attempts.
    • Added safeguards for resuming workflows with changed pipeline inputs.
  • Bug Fixes

    • Improved pipeline ordering and concluding-step selection for retry scenarios.
  • Documentation

    • Added usage guidance and examples for pipelines, retry policies, and regional orchestration.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The orchestration runtime adds validated retry and fallback policies, attempt metadata, policy graph nodes, reusable nested pipelines, scoped hierarchical IDs, persistence fingerprints, and pipeline lifecycle events. Public types and helpers are re-exported. Tests cover retries, fallbacks, idempotency, resumption, nested pipeline scoping, and validation. The regional example now uses a reusable preparation pipeline. Gallery normalization now handles concluding snapshot nodes and traverses graph edges for layout.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OrchestrationContext
  participant invokePipelineNode
  participant prepareRegions
  participant runWithPolicyNode
  participant NotebookRunner
  OrchestrationContext->>invokePipelineNode: invoke regional-preparation
  invokePipelineNode->>prepareRegions: execute scoped pipeline
  prepareRegions->>runWithPolicyNode: run regional step with policy
  runWithPolicyNode->>NotebookRunner: execute retryable attempt
  NotebookRunner-->>runWithPolicyNode: prepared regional text
  runWithPolicyNode-->>prepareRegions: resolved step result
  prepareRegions-->>invokePipelineNode: notes and preparation state
  invokePipelineNode-->>OrchestrationContext: pipeline output
Loading
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Updates Docs ✅ Passed Visible docs were updated in the package and example READMEs; I couldn't verify the OSS/internal repos, so please mirror the changes there too.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: recovery policies and reusable pipelines for the local runner.

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.46%. Comparing base (499f7fb) to head (a77f8ba).
⚠️ Report is 1 commits behind head on feat/local-orchestration.

Files with missing lines Patch % Lines
packages/local-runner/src/orchestrate.ts 85.71% 23 Missing ⚠️
Additional details and impacted files
@@                     Coverage Diff                      @@
##           feat/local-orchestration     #436      +/-   ##
============================================================
- Coverage                     87.48%   87.46%   -0.03%     
============================================================
  Files                           184      184              
  Lines                          9926    10082     +156     
  Branches                       2825     2882      +57     
============================================================
+ Hits                           8684     8818     +134     
- Misses                         1241     1263      +22     
  Partials                          1        1              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
examples/local-runner/gallery/pipeline-data.js (1)

58-77: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use an indexed queue instead of shift().

Array.prototype.shift() can be O(n), so wide graphs can still make this traversal quadratic.

Proposed fix
   const ready = nodes.filter(node => incoming.get(node.id) === 0).map(node => node.id)
+  let readyIndex = 0
   let visited = 0
-  while (ready.length > 0) {
-    const id = ready.shift()
+  while (readyIndex < ready.length) {
+    const id = ready[readyIndex++]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/local-runner/gallery/pipeline-data.js` around lines 58 - 77, Replace
the shift-based queue consumption in the graph traversal while loop with an
indexed queue position, advancing the index as each ready node is processed.
Keep appending newly ready child IDs to ready and preserve the existing
traversal, column calculation, and cycle detection behavior.
examples/local-runner/orchestration/run.mjs (1)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nit: target shadows the module-level target.

Same value today, but renaming the destructured field (e.g. runTarget) keeps the example unambiguous.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/local-runner/orchestration/run.mjs` at line 25, Rename the
destructured target parameter in the run method to runTarget to avoid shadowing
the module-level target, and update all references within run to use the new
name.
packages/local-runner/src/orchestrate.ts (1)

796-798: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Rethrown OrchestrationStepError carries the attempt ID, not the policy ID.

Infrastructure errors from the last attempt propagate as-is, so error.stepId is "<id>-attempt-N" while docs tell consumers to key off the stable policy node ID. Consider wrapping with the policy id and cause preserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/local-runner/src/orchestrate.ts` around lines 796 - 798, Update the
last-error rethrow in the orchestration flow to wrap an OrchestrationStepError
with the stable policy id rather than rethrowing the attempt-scoped error
directly. Preserve the original error as the cause and retain the existing
behavior for non-Error values.
packages/local-runner/src/orchestrate.test.ts (1)

318-321: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider covering backoff delays.

Nothing exercises initialDelayMs/backoffMultiplier/maxDelayMs; fake timers would pin the computed delay sequence and the cap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/local-runner/src/orchestrate.test.ts` around lines 318 - 321, Add a
test around defineRunPolicy that configures initialDelayMs, backoffMultiplier,
and maxDelayMs, uses fake timers to control retry scheduling, and asserts the
computed delay sequence includes the configured cap. Keep the existing
idempotent and maxAttempts behavior covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/local-runner/src/orchestrate.ts`:
- Around line 1009-1022: The qualifyDependencies function currently forces every
unprefixed dependency ID into the current scope, preventing references to
outer-scope nodes. Add and consistently apply an explicit absolute-ID escape
hatch for dependency strings and object IDs, while retaining current scoping for
ordinary IDs and preserving already-qualified IDs.

---

Nitpick comments:
In `@examples/local-runner/gallery/pipeline-data.js`:
- Around line 58-77: Replace the shift-based queue consumption in the graph
traversal while loop with an indexed queue position, advancing the index as each
ready node is processed. Keep appending newly ready child IDs to ready and
preserve the existing traversal, column calculation, and cycle detection
behavior.

In `@examples/local-runner/orchestration/run.mjs`:
- Line 25: Rename the destructured target parameter in the run method to
runTarget to avoid shadowing the module-level target, and update all references
within run to use the new name.

In `@packages/local-runner/src/orchestrate.test.ts`:
- Around line 318-321: Add a test around defineRunPolicy that configures
initialDelayMs, backoffMultiplier, and maxDelayMs, uses fake timers to control
retry scheduling, and asserts the computed delay sequence includes the
configured cap. Keep the existing idempotent and maxAttempts behavior covered.

In `@packages/local-runner/src/orchestrate.ts`:
- Around line 796-798: Update the last-error rethrow in the orchestration flow
to wrap an OrchestrationStepError with the stable policy id rather than
rethrowing the attempt-scoped error directly. Preserve the original error as the
cause and retain the existing behavior for non-Error values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34eb9ccd-e66a-4065-86a9-abecf28fa207

📥 Commits

Reviewing files that changed from the base of the PR and between 499f7fb and a77f8ba.

📒 Files selected for processing (8)
  • examples/local-runner/gallery/pipeline-data.js
  • examples/local-runner/gallery/pipeline.test.ts
  • examples/local-runner/orchestration/README.md
  • examples/local-runner/orchestration/run.mjs
  • packages/local-runner/README.md
  • packages/local-runner/src/index.ts
  • packages/local-runner/src/orchestrate.test.ts
  • packages/local-runner/src/orchestrate.ts

Comment on lines +1009 to +1022
function qualifyDependencies(
scopeId: string,
dependencies: OrchestrationDependencyInput[] | undefined
): OrchestrationDependencyInput[] | undefined {
const qualify = (id: string): string => (id.startsWith(`${scopeId}/`) ? id : `${scopeId}/${id}`)
return dependencies?.map(dependency =>
typeof dependency === 'string'
? qualify(dependency)
: {
...dependency,
id: qualify(dependency.id),
}
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No way to depend on an outer-scope node from inside a pipeline.

Every dependency ID that isn't already prefixed gets scoped, so a child step depending on a parent-level node (e.g. 'inputs') fails with "depends on unknown or not-yet-started node". Fine if intentional isolation, but consider documenting it or allowing an absolute-ID escape hatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/local-runner/src/orchestrate.ts` around lines 1009 - 1022, The
qualifyDependencies function currently forces every unprefixed dependency ID
into the current scope, preventing references to outer-scope nodes. Add and
consistently apply an explicit absolute-ID escape hatch for dependency strings
and object IDs, while retaining current scoping for ordinary IDs and preserving
already-qualified IDs.

@jamesbhobbs
jamesbhobbs marked this pull request as ready for review August 5, 2026 17:32
@jamesbhobbs
jamesbhobbs requested a review from a team as a code owner August 5, 2026 17:32
@jamesbhobbs
jamesbhobbs merged commit b146a53 into feat/local-orchestration Aug 5, 2026
21 checks passed
@jamesbhobbs
jamesbhobbs deleted the feat/local-orchestration-policies branch August 5, 2026 17:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant