Skip to content

feat(heimdall): user-drawn pipelines on a canvas, run by the Heimdall engine - #113

Open
zpyoung wants to merge 33 commits into
mainfrom
zpyoung/heimdall-pipelines
Open

zpyoung wants to merge 33 commits into
mainfrom
zpyoung/heimdall-pipelines

Conversation

@zpyoung

@zpyoung zpyoung commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner
Files Added Deleted Net
Test 135 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​29833 $\color{#cf222e}{\Huge{\mathbf{−}}}$​436 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​29397
Prod 339 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​39773 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1715 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​38058

ELI5

Heimdall can now run pipelines you draw yourself. You drop nodes onto a canvas: an agent, a check, a human approval, a fan-out "swarm" of agents, or a "land" step that opens a PR. You wire them together, save, and press Run. Heimdall walks the graph, asks you before anything risky, and ends with a pull request. The existing Objective and PR-sitter watchers show up as one-node pipelines and behave exactly as before.

What Changed

Before: Heimdall had two fixed watcher kinds, Objective and PR sitter. Changing how work flowed meant changing code.

After:

  • Pipeline files. A pipeline is .orca/pipelines/<id>.yaml (logic) plus <id>.layout.json (positions), or a personal pipeline in the profile. Node types are Agent, Check, Script, Decision, Loop, Swarm, Merge, Human gate and Land, plus Objective and PR sitter as composite nodes.
  • Engine. A graph interpreter runs on the existing Heimdall runner as a new pipeline watcher kind. Pipeline rows live in side tables (pipeline.db), so heimdall.db stays at schema 4 and older Orca builds keep reading the fleet.
  • UI. There's a canvas editor that keeps the YAML byte-stable outside the ranges you edit, and a run form showing requested vs granted capabilities. A read-only run graph survives terminal compaction, with approval dialogs reachable from its nodes.
  • CLI. orca heimdall create --pipeline, and approve --choice for engine choices such as retry, skip or abort.
  • Wire. Pipelines are capability-negotiated, so clients without heimdall.pipeline.v1 never see pipeline rows.
  • Sitter. There's one deliberate behavior change: when the sitter's own fix fails again, it now gets 3 attempts by default (configurable) before it escalates.

Engine fixes found by live acceptance and a final adversarial review:

  • Send-back after a loop. A gate or onFail send-back to a node inside a loop was silently dropped once the loop had run two or more rounds. The operator's comment was lost and only downstream nodes re-ran. Loop rounds advance only the loop body's epochs, so "already repaired" now compares ledger dispatch order instead of epoch numbers. Loop acceptance and send-back comments are scoped to the epoch they were made in, and an approve verdict is no longer shown to the next fixer as objections.
  • Merge re-runs. A Merge re-run after a send-back or loop round reused the previous round's "applied" rows and finished without merging the new work. Merge progress is now read for the Merge's current epoch only. A Merge whose retry budget is spent now offers the retries-exhausted choice instead of retrying forever.
  • Land completion. Land never completed after its PR opened. It is now done and exposes prUrl, prNumber, branch, provider and headSha for the PR-sitter composite.
  • Swarm expansion. A Swarm could expand from an empty task list when the tick read its snapshot before the planner's outputs settled. The tick now re-reads after new worker completions for kinds without judgment.
  • Validator gaps. A Decision on a json, file or taskList output, and references to a Swarm's outputs, now fail validation instead of hanging or rendering empty at runtime.
  • Remote run view. The desktop app can now open the run graph of a pipeline owned by a paired remote host.
  • Run graph refresh. The run graph stays mounted when the fleet refreshes, so an open gate dialog and a half-typed send-back comment survive.
  • Unmatched Decision. A Decision whose value matched none of its branches skipped them all and reported the run complete. It now parks the run as a configuration error naming the node and value.
  • Undeclared run inputs. A prompt referencing an undeclared $run.inputs.<name> validated cleanly and rendered empty. It now fails validation.
  • Read-only database. A heimdall.db bumped by a newer build that never created the pipeline tables now opens read-only with built-in watchers, instead of failing boot.
  • Root-anchored ignore rules. /.orca/ and /.orca are now recognized when re-including pipeline files.

Why

The logic spec chose composite nodes for the built-ins, because their logic doesn't decompose into typed nodes without changing behavior. Side tables keep downgrade safe. One validator, one content hash and one choice-options function are shared by the canvas, CLI, host and control plane, so the surfaces can't disagree about what a pipeline means or what choices a stuck node offers.

Linked Issue

N/A (fork feature, maintainer)

Visual Proof

[ASK: the live-run screenshots were in a temp folder that has since been cleared; attach fresh captures of the canvas, a completed Bugfix run graph, and a completed Swarm run graph]

Testing

Live acceptance ran in an isolated, hidden dev profile (ORCA_BACKGROUND_LAUNCH=1, driven over CDP only):

  • Bugfix with a loop and a gate send-back. The personal Bugfix pipeline (repro → decision → fix/review loop → verify → human gate → Land) ran on a seeded pagination bug (watcher b5775ad9-d785-496c-8a60-61db4c707ac9).
    • The decision took the "reproduced" branch.
    • The loop ran two rounds (review: revise, then approve). The round-2 Fix prompt carried the reviewer's objections.
    • At the final gate I sent the run back with a comment. Fix re-ran at epoch 2, and its prompt ended with ## Send-back comment and the comment text, with no stale reviewer objections. Review and Verify re-ran, the gate was offered again, and after approval Land opened https://github.com/zpyoung/orca-pipeline-smoke-20261001-6fmmmu/pull/2. The run ended pipeline-complete.
  • Bugfix (fast) on an empty canvas. The pipeline was built from scratch on the canvas and ran to an opened PR (https://github.com/zpyoung/orca-pipeline-smoke-20261001-6fmmmu/pull/1). Land's commit, push and open-review were each approved from the run graph.
  • Swarm of 5. A planner emitted 5 tasks into a Swarm with worktree: own. All 5 children ran in their own worktrees about 8 s apart, and the run completed.

Automated:

  • New interpreter regression tests cover:
    • send-back after loop rounds (gate and onFail);
    • shared-target dedupe;
    • accepted-loop reopening;
    • send-back comment lifetime;
    • Merge at epoch ≥ 1, Merge inside a loop, and Merge retries exhausted;
    • the two validator gaps, remote run-view access, and run-graph refresh.
  • typecheck:node, typecheck:web and typecheck:cli pass. typecheck:e2e has pre-existing errors in files this branch doesn't touch.
  • The full pnpm lint chain passes, including the changed-code gate and the localization verifiers.
  • The fork ownership guard passes against main (upstream v1.4.218), and so do the root-entry and .d.ts guards.
  • Full 16-shard unit suite on the remote sandbox: 15 of 16 shards pass. The one failure is palette-match-performance.test.ts (ledger bug-288), which fails the same way on the base branch on that host.

Tested on macOS only. SSH-hosted runs, Linux and Windows were not exercised.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

AI Disclosure

Implemented with Claude Code (Opus) and omp/Codex workers. Reviewed with a multi-agent Claude review whose findings were each verified independently before fixing.

Review

Start with src/shared/fork-heimdall-pipeline/interpreter/ (decide, run-state, state-history), then src/main/fork-heimdall-pipeline/ (executors and action dispatch). The shared-runner change that every watcher kind runs is in src/main/fork-heimdall/judgment/mailbox-refresh.ts.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Platforms. Tested on macOS only. SSH-hosted pipeline runs (the CLI create path is deferred for SSH), Linux and Windows were not exercised.
  • Land sweeps untracked files. Land commits every untracked, non-ignored file except .git and .orca. In the live run that swept a memory plugin's files into the PR (ledger proposal-289).
  • Canvas class allowlist. Upstream's new no-unknown-classes rule flags the canvas's BEM classes, so pipeline-* is allowlisted through a declared seam in config/oxlint-dead-classes.json. Moving the canvas to Tailwind is deferred (ledger deferred-295).
  • Lockfile ownership. pnpm-lock.yaml is now a permanent fork exception, because this branch adds @xyflow/react and package.json is already one. Main's passthrough seam for the pnpm 12.5.1 lines was dropped, since the exception covers the whole file and the guard forbids both.
  • Known red on the sandbox host only. palette-match-performance.test.ts (ledger bug-288) fails the same way on the base branch there. Upstream's matcher is left untouched.
  • Known low-severity issue. Agent prompts carry two overlapping "Report instructions" sections (ledger bug-294).

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Copilot AI balanced review requested due to automatic review settings October 6, 2026 14:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Root-anchored .orca rules break pipeline re-inclusion, and future-schema read-only databases can fail when pipeline side tables are absent.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Adds user-authored Heimdall pipelines spanning the shared graph model, execution engine, persistence, remote protocol, CLI, and canvas UI.

Changes:

  • Introduces pipeline schemas, validation, interpretation, persistence, execution, and capability negotiation.
  • Adds pipeline authoring/run UI, CLI commands, localization, and remote-host support.
  • Integrates built-in Objective/PR-sitter flows and configurable repeated-fix handling.
File Description
src/​shared/​rpc-contract/​rpc-params-catalog.generated.ts Registers pipeline RPC schemas.
src/​shared/​protocol-version.ts Advertises pipeline capabilities.
src/​shared/​fork-hosted-review-sitter/​types.ts Adds repeat-fix limits.
src/​shared/​fork-hosted-review-sitter/​enrollment-candidate.ts Validates repeat limits.
src/​shared/​fork-hosted-review-sitter/​agent-prompt.ts Includes previous fix evidence.
src/​shared/​fork-hosted-review-sitter/​agent-prompt.test.ts Tests repeated-failure prompts.
src/​shared/​fork-heimdall/​owner/​deviation.ts Adds pipeline-node deviations.
src/​shared/​fork-heimdall-pipeline/​yaml-writer/​pipeline-edit-values.ts Handles YAML edit values.
src/​shared/​fork-heimdall-pipeline/​yaml-writer/​pipeline-edit-types.ts Defines editing contracts.
src/​shared/​fork-heimdall-pipeline/​yaml-writer/​pipeline-authoring-document.ts Defines authoring documents.
src/​shared/​fork-heimdall-pipeline/​yaml-writer/​layout-writer.ts Serializes canvas layouts.
src/​shared/​fork-heimdall-pipeline/​yaml-writer/​index.ts Exports YAML writer APIs.
src/​shared/​fork-heimdall-pipeline/​task-list.test.ts Tests task-list validation.
src/​shared/​fork-heimdall-pipeline/​run-view-types.test.ts Tests forward-compatible run views.
src/​shared/​fork-heimdall-pipeline/​pipeline-source.ts Bounds pipeline source size.
src/​shared/​fork-heimdall-pipeline/​pipeline-source.test.ts Tests source limits.
src/​shared/​fork-heimdall-pipeline/​pipeline-ref.ts Parses pipeline references.
src/​shared/​fork-heimdall-pipeline/​pipeline-ref.test.ts Tests reference parsing.
src/​shared/​fork-heimdall-pipeline/​pipeline-pin.ts Defines immutable run pins.
src/​shared/​fork-heimdall-pipeline/​pipeline-canonical-hash.ts Hashes canonical documents.
src/​shared/​fork-heimdall-pipeline/​node-id.ts Defines node identifiers.
src/​shared/​fork-heimdall-pipeline/​layout-schema.ts Defines layout schemas.
src/​shared/​fork-heimdall-pipeline/​layout-schema.test.ts Tests layout compatibility.
src/​shared/​fork-heimdall-pipeline/​interpreter/​verdict-output.ts Decodes verdict outputs.
src/​shared/​fork-heimdall-pipeline/​interpreter/​swarm-rules.ts Schedules swarm children.
src/​shared/​fork-heimdall-pipeline/​interpreter/​stop-verdict.ts Determines terminal verdicts.
src/​shared/​fork-heimdall-pipeline/​interpreter/​node-history.ts Projects node attempt history.
src/​shared/​fork-heimdall-pipeline/​interpreter/​merge-order.ts Orders merge operations.
src/​shared/​fork-heimdall-pipeline/​enrollment-payload.ts Defines run enrollment payloads.
src/​shared/​fork-heimdall-pipeline/​enrollment-payload.test.ts Tests enrollment validation.
src/​shared/​fork-heimdall-pipeline/​capability.ts Defines wire capabilities.
src/​shared/​fork-heimdall-pipeline/​capability.test.ts Tests capability mapping.
src/​shared/​fork-heimdall-pipeline/​capability-grants.ts Computes requested grants.
src/​shared/​fork-heimdall-pipeline/​builtin-pipelines.ts Defines built-in pipelines.
src/​shared/​fork-heimdall-pipeline/​builtin-pipelines.test.ts Tests built-in documents.
src/​renderer/​src/​store/​slices/​tabs-hydration.ts Drops transient pipeline tabs.
src/​renderer/​src/​store/​slices/​fork-heimdall/​fleet.ts Uses remote-reader fleet types.
src/​renderer/​src/​store/​slices/​editor/​types/​open-file.ts Adds pipeline editor mode.
src/​renderer/​src/​i18n/​fork-localization-catalogs.test.ts Removes brittle catalog-count test.
src/​renderer/​src/​fork-hosted-review-sitter/​HostedReviewSitterStatusContent.tsx Supports reader-safe entries.
src/​renderer/​src/​fork-hosted-review-sitter/​hosted-review-sitter-panel-state.ts Updates remote entry types.
src/​renderer/​src/​fork-hosted-review-sitter/​active-sitter-registry.ts Narrows active sitter entries.
src/​renderer/​src/​fork-heimdall/​watcher-status-copy.test.ts Tests watcher-kind labels.
src/​renderer/​src/​fork-heimdall/​locales/​zh.json Localizes new fleet UI.
src/​renderer/​src/​fork-heimdall/​locales/​ko.json Localizes new fleet UI.
src/​renderer/​src/​fork-heimdall/​locales/​ja.json Localizes new fleet UI.
src/​renderer/​src/​fork-heimdall/​locales/​fr.json Localizes new fleet UI.
src/​renderer/​src/​fork-heimdall/​locales/​es.json Localizes new fleet UI.
src/​renderer/​src/​fork-heimdall/​locales/​en.json Adds new fleet copy.
src/​renderer/​src/​fork-heimdall/​HeimdallStatusPill.tsx Supports remote-reader rows.
src/​renderer/​src/​fork-heimdall/​HeimdallConcurrencyControl.tsx Supports reader enrollments.
src/​renderer/​src/​fork-heimdall/​heimdall-tone-pill.tsx Adds destructive status tone.
src/​renderer/​src/​fork-heimdall/​heimdall-tone-pill.test.tsx Tests destructive styling.
src/​renderer/​src/​fork-heimdall/​fleet-row-presentation.ts Projects reader-safe fleet data.
src/​renderer/​src/​fork-heimdall/​fleet-action-history.ts Supports reader-safe details.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelineYamlPreview.tsx Adds read-only YAML preview.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelineYamlPreview.test.tsx Tests preview read-only state.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelineValidationList.tsx Displays validation errors.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelineTrackingNotice.tsx Shows Git tracking guidance.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelinePalette.tsx Adds draggable node palette.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelineExternalChangeBanner.tsx Handles external edits.
src/​renderer/​src/​fork-heimdall-pipeline/​PipelineCanvasTab.tsx Lazily mounts the canvas.
src/​renderer/​src/​fork-heimdall-pipeline/​pipeline-virtual-tab-id.ts Recognizes pipeline tab IDs.
src/​renderer/​src/​fork-heimdall-pipeline/​pipeline-tab-lifecycle.ts Disposes canvas drafts.
src/​renderer/​src/​fork-heimdall-pipeline/​pipeline-tab-lifecycle.test.ts Tests draft disposal.
src/​renderer/​src/​fork-heimdall-pipeline/​pipeline-node-views/​UnknownNodeView.tsx Renders future node types.
src/​renderer/​src/​fork-heimdall-pipeline/​pipeline-node-views/​index.ts Exports node views.
src/​renderer/​src/​fork-heimdall-pipeline/​localization-catalog.ts Registers pipeline locales.
src/​renderer/​src/​fork-heimdall-objective/​ObjectivePlanQuality.tsx Renames gates to checks.
src/​renderer/​src/​fork-heimdall-objective/​ObjectivePlan.test.tsx Updates objective tests.
src/​renderer/​src/​fork-heimdall-objective/​ObjectiveEnrollmentGateFields.tsx Updates check terminology.
src/​renderer/​src/​fork-heimdall-objective/​ObjectiveEnrollmentFieldHelp.tsx Updates check help text.
src/​renderer/​src/​fork-heimdall-objective/​ObjectiveDetailContent.tsx Handles reader-safe sitter rows.
src/​renderer/​src/​fork-heimdall-objective/​objective-detail-format.ts Updates check labels.
src/​renderer/​src/​fork-heimdall-objective/​locales/​zh.json Adds objective translations.
src/​renderer/​src/​fork-heimdall-objective/​locales/​ko.json Adds objective translations.
src/​renderer/​src/​fork-heimdall-objective/​locales/​ja.json Adds objective translations.
src/​renderer/​src/​fork-heimdall-objective/​locales/​fr.json Adds objective translations.
src/​renderer/​src/​fork-heimdall-objective/​locales/​es.json Adds objective translations.
src/​renderer/​src/​components/​editor/​EditorPanelHeaderPath.tsx Treats pipelines as virtual tabs.
src/​renderer/​src/​components/​editor/​EditorPanel.tsx Avoids folder actions for pipelines.
src/​renderer/​src/​components/​editor/​EditorContent.tsx Renders pipeline canvases.
src/​renderer/​src/​components/​editor/​editor-save-queue.ts Routes pipeline saves.
src/​renderer/​src/​components/​editor/​editor-labels.ts Labels pipeline tabs.
src/​renderer/​src/​components/​editor/​editor-header.ts Hides file header for pipelines.
src/​renderer/​src/​components/​editor/​editor-autosave-controller.ts Saves pipelines before close.
src/​renderer/​src/​components/​editor/​closed-editor-tab-disposal.ts Disposes pipeline caches.
src/​relay/​fs-handler.ts Exposes relay readlink.
src/​relay/​fork-heimdall-pipeline/​link-target-request.ts Implements relay readlink.
src/​relay/​fork-heimdall-pipeline/​link-target-request.test.ts Tests relay symlink reads.
src/​preload/​preload-runtime-support.ts Preserves internal drag events.
src/​main/​runtime/​runtime-repository-issue-command.ts Reuses .orca ignore detection.
src/​main/​runtime/​rpc/​methods/​fork-heimdall/​pipeline-kind-wire.ts Negotiates pipeline rows.
src/​main/​runtime/​rpc/​methods/​fork-heimdall/​legacy-wire.ts Filters legacy pipeline lists.
src/​main/​runtime/​rpc/​methods/​fork-heimdall/​dispatch-result-wire.ts Uses reader-safe details.
src/​main/​runtime/​rpc/​methods/​fork-artifact-passwords/​artifact-password-local-caller.ts Makes caller kind optional.
src/​main/​providers/​ssh-filesystem-provider.ts Adds SSH link reading.
src/​main/​providers/​filesystem-provider-contract.ts Extends filesystem contract.
src/​main/​issue-command-file.ts Reuses .orca ignore rules.
src/​main/​ipc/​hooks/​register-worktree-hook-file-handlers.ts Reuses ignore detection remotely.
src/​main/​ipc/​desktop-renderer-runtime-capabilities.ts Advertises desktop pipeline support.
src/​main/​fork-hosted-review-sitter/​service-action-executor.ts Supplies failure history.
src/​main/​fork-hosted-review-sitter/​owner-state-brief.ts Applies repeat-fix defaults.
src/​main/​fork-hosted-review-sitter/​definition.ts Persists repeat limits.
src/​main/​fork-hosted-review-sitter/​definition.test.ts Tests repeat-limit definitions.
src/​main/​fork-hosted-review-sitter/​definition-store.ts Validates stored limits.
src/​main/​fork-hosted-review-sitter/​definition-store.test.ts Tests limit persistence.
src/​main/​fork-hosted-review-sitter/​agent-preparation.ts Adds prior attempts to prompts.
src/​main/​fork-heimdall/​runner-worker-lifecycle.ts Resolves accepted completions.
src/​main/​fork-heimdall/​runner-stop-lifecycle.ts Persists terminal projections.
src/​main/​fork-heimdall/​runner-gating.ts Supports kind-specific advisories.
src/​main/​fork-heimdall/​runner-actions.ts Persists dispatch evidence.
src/​main/​fork-heimdall/​runner-accepted-completion.ts Verifies worker completion effects.
src/​main/​fork-heimdall/​retention.ts Reads pipeline enrollment tables.
src/​main/​fork-heimdall/​registration.ts Registers pipeline-aware kinds.
src/​main/​fork-heimdall/​owner/​owner-intervention.ts Generalizes rejection gates.
src/​main/​fork-heimdall/​owner/​deviation-scope.ts Supports kind-owned escalation.
src/​main/​fork-heimdall/​owner/​deviation-routing.test.ts Tests escalation routing.
src/​main/​fork-heimdall/​orchestration/​coordinator-runtime-facade.ts Defaults terminals to background.
src/​main/​fork-heimdall/​notification.ts Adds pipeline approval copy.
src/​main/​fork-heimdall/​local-fleet-projection.ts Projects pipeline fleet state.
src/​main/​fork-heimdall/​local-fleet-projection.test.ts Tests pipeline projections.
src/​main/​fork-heimdall/​kernel-worker-release.test.ts Updates mailbox read expectations.
src/​main/​fork-heimdall/​kernel-terminal-transition.ts Recognizes pipeline uniqueness errors.
src/​main/​fork-heimdall/​kernel-service.ts Adds pipeline enrollment hooks.
src/​main/​fork-heimdall/​kernel-service-test-harness.ts Exposes runtime test fixtures.
src/​main/​fork-heimdall/​judgment/​mailbox-refresh.ts Refreshes non-judgment kinds.
src/​main/​fork-heimdall/​fleet-remote-operations.ts Negotiates remote pipeline commands.
src/​main/​fork-heimdall/​fleet-remote-command.ts Refuses unsupported pipeline commands.
src/​main/​fork-heimdall/​fleet-ipc-forward.test.ts Uses reader-safe snapshot types.
src/​main/​fork-heimdall/​enrollment-insert-rollback.ts Rolls back failed enrollment inserts.
src/​main/​fork-heimdall/​database.ts Creates pipeline side tables.
src/​main/​fork-heimdall/​database.test.ts Tests side-table initialization.
src/​main/​fork-heimdall/​control-plane.test.ts Updates enrollment test doubles.
src/​main/​fork-heimdall-pipeline/​ssh-link-reader.ts Reads remote symlink targets.
src/​main/​fork-heimdall-pipeline/​precheck-extra-env.test.ts Tests precheck environment merging.
src/​main/​fork-heimdall-pipeline/​pipeline-store-test-fixtures.ts Adds in-memory store fixture.
src/​main/​fork-heimdall-pipeline/​pipeline-report-path.ts Defines report locations.
src/​main/​fork-heimdall-pipeline/​pipeline-report-path.test.ts Tests report paths.
src/​main/​fork-heimdall-pipeline/​pipeline-enrollment-table.ts Defines pipeline side tables.
src/​main/​fork-heimdall-pipeline/​pipeline-binding.ts Binds runtime pipeline services.
src/​main/​fork-heimdall-pipeline/​pipeline-action-dispatch-contracts.ts Defines dispatcher contracts.
src/​main/​fork-heimdall-pipeline/​orca-gitignore-rules.ts Manages pipeline re-inclusion.
src/​main/​fork-heimdall-pipeline/​orca-gitignore-rules.test.ts Tests ignore rewriting.
src/​main/​fork-heimdall-pipeline/​issue-command-orca-gitignore.test.ts Tests issue-command fallback.
src/​main/​fork-heimdall-pipeline/​check-node-executor.ts Executes check nodes.
src/​main/​fork-heimdall-pipeline/​builtin-one-node-kind.ts Wraps built-in watcher kinds.
src/​main/​fork-heimdall-pipeline/​approval-notification-copy.ts Formats approval notifications.
src/​main/​fork-heimdall-pipeline/​answer-attribution.ts Attributes pipeline answers.
src/​main/​fork-heimdall-objective/​definition.ts Shares worktree enrollment logic.
src/​main/​fork-heimdall-objective/​check-runner.ts Supports environment and command preservation.
src/​main/​automations/​precheck-runner.ts Merges local extra environment.
src/​cli/​root-help-text-secondary.ts Documents pipeline creation.
src/​cli/​root-help-text-primary.ts Adds pipeline help entry.
src/​cli/​handler-group-manifest.ts Registers pipeline create handler.
src/​cli/​fork-heimdall/​specs-manage.ts Documents pipeline choices.
src/​cli/​fork-heimdall/​specs-create.ts Defines pipeline create CLI.
src/​cli/​fork-heimdall/​create-input.ts Broadens create candidate kinds.
skills/​orca-heimdall/​SKILL.md Documents pipeline operations.
resources/​skills/​snapshot-registry.json Records updated skill snapshot.
resources/​skills/​current-manifest.json Advances skill revision.
package.json Adds React Flow dependency.
config/​scripts/​run-sandboxed-test-shards.test.mjs Tests wire-lane selection.
config/​oxlint-dead-classes.json Allows pipeline CSS classes.
config/​docker/​test-sandbox/​README.md Documents wire and shard lanes.
config/​docker/​test-sandbox/​entrypoint.sh Imports baseline refs.
config/​docker/​test-sandbox/​Dockerfile Adds newer Git and lsof.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 123 to +128
connection(): Database.Database {
return this.scoped.connection()
const connection = this.scoped.connection()
if (!this.pipelineSideTablesEnsured && !this.scoped.isReadOnly()) {
ensurePipelineSideTables(connection)
this.pipelineSideTablesEnsured = true
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 6d2fda6. Upstream has no Heimdall, so the realistic case is a newer fork build that bumped heimdall.db without ever creating the pipeline side tables. PipelineAwareEnrollmentStore now checks for the tables when the database is read-only and falls back to built-in-only results for get, list, findLiveByWorkspace, pendingKindPurges and pipelineRowsClaimedByBuiltin. A regression test opens such a database read-only.

Comment thread src/main/fork-heimdall-pipeline/orca-gitignore-rules.ts Outdated
@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[High risk] Adds pipeline canvas editor and execution engine to Heimdall.

The PR is not ready to merge because unmatched decisions can complete runs without their intended work and missing input references can silently alter agent prompts; the repository’s child-process requirement also needs to be satisfied.

Findings

  1. P1 Unmatched branches complete the run ▶
  2. P1 Missing inputs render empty ▶
  3. P2 Concurrent edits can be overwritten ▶
  4. P2 Direct child processes violate policy ▶

Summary

The PR adds YAML-defined Heimdall pipelines, a graph interpreter and host executors, canvas authoring and run views, CLI controls, and capability-aware remote integration. It also changes the PR sitter’s repeated-fix limit and adds a sandbox cross-version wire lane.

  • Decision routing can finish a run when no branch matches.
  • Undeclared run-input references silently render empty.
  • Repository saves have a concurrent-write window, and the new sandbox script conflicts with the repository’s child-process requirement.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  YAML[Pipeline YAML] --> Validate[Parse and validate]
  Validate --> Enroll[Pin run and grants]
  Enroll --> Decide[Heimdall graph interpreter]
  Decide --> Execute[Host node executors]
  Execute --> Facts[Ledger and pipeline side tables]
  Facts --> Decide
  Facts --> View[Canvas run graph]
Loading

Reviews (1) · Last reviewed commit: "fix(heimdall): allowlist the pipeline ca..."

if (!allResolved) {
continue
}
const nextStatus: PipelineNodeRunState['status'] = anySatisfied ? 'ready' : 'skipped'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unmatched branches complete the run When a Decision uses a text or number output and none of its when values matches, this code skips every downstream branch. Skipped sink nodes count as complete, so the pipeline can report success without running the intended work, including Land. Require a matching branch or fallback instead of completing the run.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 64f8d06. Skipped sinks still count as complete, because a branch that legitimately isn't taken must not stall the run. A done Decision whose value matches none of its when edges now stops the run as pipeline-configuration-error (park), with the node and value in the detail, e.g. kindPayload.document.nodes.route.on: value "Bug" matches no branch. Covered by a new stop-verdict.test.ts for both the unmatched and matched cases.

Comment on lines +389 to +393
): OutputRefParts | null {
const parts = outputRefParts(reference)
if (parts === null) {
return null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing inputs render empty A prompt can reference $run.inputs.taks even if taks is not declared in document.inputs: validation ignores input references, and rendering replaces the missing value with an empty string. An Agent or Swarm can therefore run with a silently incomplete task prompt. Validate these references against the declared inputs.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 47f98df. $run.inputs.<name> references in prompts, Land text, script inputs and decision/loop/swarm expressions are now checked against document.inputs and fail validation when undeclared. A declared optional input with no default still renders empty by design. This also caught a skill-guide example that used task alongside other declared inputs; task is only implicit when inputs is omitted, so the example now declares it.

Comment on lines +392 to +396
await ensurePipelineDirectory(runtime, paths)
recordSelfWrite(paths.yamlPath, yamlText, runtimeEnvironmentId, SELF_WRITE_REMOTE_TTL_MS)
await writeRuntimeFile(runtime, paths.yamlPath, yamlText)
recordSelfWrite(paths.layoutPath, layoutText, runtimeEnvironmentId, SELF_WRITE_REMOTE_TTL_MS)
await writeRuntimeFile(runtime, paths.layoutPath, layoutText)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Concurrent edits can be overwritten A repository pipeline save checks the disk signature before reaching this unconditional YAML and layout write. If an external editor changes either file between the check and the write, its changes can be overwritten without a conflict banner. Protect the write against that race.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Right that it isn't atomic: the disk-signature check and the YAML/layout writes are separate calls, and the shared fs:writeFile / files.write path has no expected-signature option. The window is a few round trips. The main editor save is looser still, since it has no save-time disk check and relies on the file watcher (editor-save-queue.ts, editor-autosave.ts). Closing it fully means adding compare-and-write to the shared file RPC, which is upstream-owned and part of the remote wire contract, so I'm keeping this consistent with the editor and tracking it separately rather than building a fork-only primitive here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That trade-off is reasonable. The race is real, but closing it correctly requires changing the shared file RPC and its remote wire contract; adding a fork-only compare-and-write path here would make pipeline saves behave differently from the editor and create a larger compatibility surface. I’m treating this as a known, separately tracked limitation rather than an actionable issue for this PR.

@@ -0,0 +1,119 @@
import { spawnSync } from 'node:child_process'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Direct child processes violate policy This new sandbox baseline script imports spawnSync from node:child_process and uses it to start Git and tar. The repository requires Windows child processes to go through runProcess or spawnProcess in src/shared/child-process/, never directly through child_process. The same pattern appears elsewhere in this script and its test; satisfy this requirement before merging.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The runProcess/spawnProcess rule covers shipped app code, and its ratchet (child-process-import-boundary.test.ts) only scans src/**/*.ts(x). This is a dev-side script under config/scripts. Its only caller, run-sandboxed-test-shards.mjs, imports spawnSync from node:child_process itself, as do about 30 other upstream scripts there. It just builds the tar sent to the Linux Docker test host with fixed-argv git/tar calls, so none of the Windows .cmd or quoting problems the wrapper exists for apply. Leaving it as is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That’s a fair distinction. The runProcess/spawnProcess boundary is enforced for shipped src/**/*.ts(x) code, and this helper is a developer-side config/scripts/**/*.mjs utility alongside existing scripts that use spawnSync. Given its fixed-argv local git/tar calls and the absence of any matching ratchet for config scripts, I’m withdrawing the direct-child-process finding; no change is needed here.

… rounds

A gate or onFail send-back to a loop body node was dropped once the loop had run
two or more rounds, because repair dedupe compared per-node epochs that loop
rounds advance independently. Dedupe now compares ledger dispatch order.

Also scopes loop acceptance and send-back comments to the epoch they were made
in, and stops rendering an approve verdict as reviewer objections.
A re-run Merge (after a send-back or a loop round) read prior-epoch 'applied'
rows and went done without merging the new children. Merge progress lookups now
use only the Merge's current epoch, merge decisions read the swarm epoch from
the run state, and a Merge whose retry budget is spent stays failed so the
retries-exhausted choice is offered.
A Decision on a json, file or taskList output, and any reference to a Swarm's
outputs, validated but hung or rendered empty at runtime. Both are now
invalid-output-ref errors.
…runs

pipelineRunView rejected every runtime-kind caller with a connection id, which
includes the local desktop renderer, so remotely owned runs never rendered.
Only paired runtime forwarders are rejected now.
…shes

Every fleet snapshot refetched the run view and swapped the graph for a loading
placeholder, closing an open gate dialog and discarding a typed send-back
comment.
The next upstream sync would reset these files and silently drop the coverage.
The tests now live in fork-owned files and the upstream files match main.
…raction

i18next-cli resolved translate(copy.key, ...) in the gate dialog and run form to
the local `key` array in makePipelineNodeEvidenceKey and extracted a phantom
'node' key, failing verify:localization-extraction.
…lasses

Upstream's new shadcn(no-unknown-classes) rule flags the canvas's BEM classes
from fork-heimdall-pipeline/pipeline-canvas.css. Allow pipeline-* through a
declared registration seam in oxlint-dead-classes.json.
A .gitignore with /.orca/ kept pipelines ignored: the re-include appended
!.orca/pipelines/ under a parent exclusion git never lifts. Accept the
optional leading slash in all three exact-rule patterns.
… pipeline tables

A heimdall.db bumped past this build's schema by a build that never created
the pipeline side tables opens read-only, and boot then failed with
'no such table: heimdall_pipeline_enrollment'. Fall back to the built-in
store when the database is read-only and the tables are absent.
A typo like $run.inputs.taks validated cleanly and rendered as an empty
string. Validate input references in prompts, Land text, script inputs and
decision/loop/swarm expressions against document.inputs. The Heimdall skill
guide's example relied on an implicit task input alongside declared inputs,
which the schema only provides when inputs is omitted; declare it.
A Decision on a text or number output whose value matched none of its when
edges skipped every branch, and skipped sinks count as complete, so the run
reported pipeline-complete without doing the work (Land included). Park it
as a pipeline-configuration-error naming the node and the value instead.
Declare the workspace permission every RPC method now requires, and drop
the preload drop-boundary test: upstream removed the preload file-drop
listener it exercised, so pipeline node drags no longer pass through it.
Upstream replaced the SSH relay with orcad, so pipeline file, check,
tracking and protected-digest reads now run local-only and refuse SSH
hosts, matching the objective kind. Repoints renderer imports at the
split runtime file clients, routes renderer host lookups by the
workspace owner instead of the focused server, drops the pipeline
readlink seams, and renames symbols the new anti-slop rule rejects.
Main's WSL mover now requires every SQLite schema file and table to carry a
disposition, and its Vitest boundary requires real-SQLite tests to run in the
Node runtime project. List the pipeline database, the pipeline enrollment side
tables, and the two pipeline tests that open SQLite. The orchestration adapter
test spied on a runtime method main removed; mock the trust module instead.
@zpyoung
zpyoung force-pushed the zpyoung/heimdall-pipelines branch from dff2317 to 32080ba Compare October 20, 2026 13:13
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.

2 participants