Skip to content

feat(workflows): add dynamic declarative workflows - #618

Open
pascalandr wants to merge 9 commits into
NeuralNomadsAI:devfrom
pascalandr:feat/workflow-host-spike
Open

feat(workflows): add dynamic declarative workflows#618
pascalandr wants to merge 9 commits into
NeuralNomadsAI:devfrom
pascalandr:feat/workflow-host-spike

Conversation

@pascalandr

@pascalandr pascalandr commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Claude Code-style dynamic workflows through a safe declarative YAML/JSON runtime backed by OpenCode agents. It provides comparable dynamic orchestration without evaluating generated JavaScript or TypeScript.

  • Add a versioned JSON/YAML workflow DSL with sequence, condition, parallel, foreach, repeat, agent, shell, gate, and nested saved-workflow nodes.
  • Add bounded concurrency, retries, node deadlines, structured output validation, observed usage budgets, pause/resume, durable gates, cancellation, crash recovery, immutable definition revisions, and retained managed-worktree execution.
  • Preserve the Simple workflow builder and add localized declarative authoring, revision conflict handling, execution trees, usage/output/session monitoring, and recovery controls in the customizable right panel.
  • Extend the OpenCode plugin with create/update/read/start/cancel access to saved workflows while keeping deletion, gates, approvals, and recovery confirmation on authenticated user APIs.

User experience

Users can build and operate workflows from the Workflows tab in the customizable right panel:

  • Simple mode creates sequential workflows without requiring YAML.
  • Declarative mode authors reusable YAML/JSON definitions with branching, loops, parallelism, nested workflows, agents, shell steps, and human gates.
  • Live run cards expose execution progress, node outputs, errors, token/cost usage, budgets, and generated OpenCode sessions.
  • Human controls include approval, structured input, pause, resume, explicit recovery, and cancellation.
  • Definitions and runs are persisted by the server; interrupted executions can be inspected and recovered after restart.
  • Runs can execute in the current workspace or a retained CodeNomad-managed worktree.

Example:

version: 1
id: review-change
name: Review and implement
root:
  type: sequence
  id: root
  steps:
    - type: agent
      id: review
      instructions: Review the requested change
    - type: gate
      id: approval
      gate: approval
      prompt: Approve implementation?
    - type: agent
      id: implement
      instructions: Implement the approved review

Compared with Claude Code dynamic workflows

Claude Code approach CodeNomad approach
Workflow logic may be generated and executed as TypeScript/JavaScript Workflow logic is a strictly validated YAML/JSON definition
Nearly unrestricted language-level flexibility Controlled primitives for agents, shell, conditions, loops, parallelism, nesting, and gates
Code and terminal logs are the primary debugging surface The right panel provides a visual execution tree, outputs, usage, errors, and session links
Executed code forms much of the security boundary No eval or generated-code execution; commands cross an explicit shell-node boundary
Human controls must be implemented by the workflow Approval, input, pause, resume, recovery, and cancellation are native runtime states
Restart recovery depends on workflow code Runs, node journals, gates, reservations, and recovery state are persisted by CodeNomad
Tied to Claude Code agents and models Uses OpenCode agents and its configured model providers

The user outcome is intentionally similar: workflows are created dynamically and can branch, loop, fan out, invoke agents, pause for people, and compose other workflows. The execution model is deliberately different: CodeNomad favors predictable validation, visual operation, persistence, and recovery over arbitrary generated-code flexibility.

Tradeoffs

  • A declarative workflow cannot freely import libraries, register arbitrary callbacks, or invent new JavaScript primitives.
  • Custom behavior must use an existing DSL node or an explicit shell node.
  • Provider token and cost usage is reported after an action completes, so a single admitted action can exceed an observed total budget; no subsequent work is admitted after equality or overrun.
  • Agent nodes inherit normal OpenCode tool access when tools is omitted; an explicit tools list remains an installed-tool allowlist. Shell nodes remain the deterministic command primitive.
  • The runtime is not byte-for-byte compatible with Claude Code workflow scripts; it targets comparable orchestration behavior through a safer portable representation.

Safety

  • Reuse existing OpenCode permissions and Yolo family inheritance; validate explicit agent tool allowlists against installed tools and keep deterministic commands visible as shell nodes.
  • Use per-workspace callback capabilities, trusted-origin CORS/CSRF checks, atomic latest-revision starts, bounded input/schema/context/history sizes, and safe Markdown URL rendering.
  • Serialize admission, ownership, pause/resume, deletion, and recovery; retain reservations whenever persistence or session termination is ambiguous.
  • Canonically contain managed worktrees and terminate Git/OpenCode process trees on cancellation, timeout, and shutdown.
  • Keep definition deletion, gate answers, approvals, and recovery confirmation unavailable to plugin credentials.

Persistent named sessions and probable follow-ups

Agent nodes may declare an optional sessionKey. The first use creates an OpenCode session; later nodes and repeat iterations with the same key continue that conversation. Bindings are persisted for restart recovery, and prompts sharing a key are serialized so parallel branches cannot mutate one conversation concurrently. Omitting the key preserves one session per action.

Probable follow-ups remain intentionally separate: per-session worktrees for independently isolated workers, provider-specific reasoning controls such as a max-thinking variant, and richer PR authorization primitives if generic human gates prove insufficient.

Validation

  • Server typecheck passed.
  • UI typecheck and production build passed.
  • OpenCode plugin build passed.
  • Full server suite after named-session coverage: 354 passed, 4 platform-skipped, 0 failed.
  • Instance-client merge coverage: loopback routing, authorization, directory overrides, pre-dispatch cancellation, active cancellation, and fallback timeouts.
  • Focused UI workflow tests: 18 passed.
  • Markdown safety tests: 15 passed.
  • Final gatekeeper review: zero actionable findings.

Closes #435

Add durable sequential workflow execution with bounded outputs, human review gates, cancellation, restart recovery, lineage-scoped locking, and SSE updates.

Expose workflow creation and monitoring in the right panel and through the CodeNomad plugin bridge. Keep approval mutations on the authenticated user API so plugin credentials cannot bypass a human gate, and preserve workspace lineage across desktop restore.

Cover runtime transitions, restart rebinding, abort containment, route scoping, client reconciliation, restore identity, and plugin messaging with focused tests.
@pascalandr
pascalandr marked this pull request as ready for review July 21, 2026 06:43
Resolve right-panel customization conflicts by registering Workflows through the modular tab registry while retaining keyboard tab navigation and ARIA panel relationships.

Combine workflow and customization translations, keep generic persisted tab IDs, and preserve all upstream settings, auth, filesystem, permission, and Linux launch fixes.

Validated with the UI typecheck and 78 focused registry, workflow, reconciliation, snapshot, and codec tests.
Add versioned JSON/YAML workflow definitions with branching, bounded parallelism, loops, nested workflows, gates, retries, structured outputs, budgets, pause/resume, crash recovery, and managed-worktree execution.

Keep workflow mutation behind authenticated host APIs, scope plugin callbacks with per-workspace capabilities, restrict agent tools and plugin operations, and harden cancellation, persistence, CORS, process cleanup, ownership leases, and stale revision handling.

Extend the right-panel workflow UI with declarative authoring, localized execution monitoring, durable drafts, recovery controls, safe output rendering, and retained-workspace session navigation while preserving the simple builder.

Validated with server typechecks and the full 335-pass server suite, UI/plugin typechecks and builds, focused workflow/UI/Markdown tests, and a zero-finding gatekeeper review.
@pascalandr pascalandr changed the title feat(workflows): add host-managed workflow runs feat(workflows): add declarative orchestration runtime Jul 28, 2026
@github-actions

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/30383020047

Artifacts expire in 7 days.
Artifacts:

  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-tauri-macos
  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-tauri-windows
  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-tauri-linux
  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-electron-macos
  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-electron-linux
  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-tauri-macos-arm64
  • pr-618-4ccf771a394c42819d1169d19bfc3bb0ef3fc9ab-electron-windows

Merge the background-process client-factory refactor from dev while preserving workflow cancellation semantics.

The shared instance client now supports explicit session/worktree directories and composes Request, caller, and fallback timeout signals. Combined tests cover loopback routing, authorization, directory scoping, pre-dispatch cancellation, active cancellation, and timeouts.

Validated with server and UI typechecks plus the full server suite: 345 passed, 4 platform-skipped, 0 failed.
@github-actions

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/30665339657

Artifacts expire in 7 days.
Artifacts:

  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-tauri-macos
  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-tauri-windows
  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-electron-macos
  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-tauri-macos-arm64
  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-tauri-linux
  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-electron-linux
  • pr-618-871011b09b0bc499e91863aa989c0ec42c074218-electron-windows

@pascalandr pascalandr changed the title feat(workflows): add declarative orchestration runtime feat(workflows): add dynamic declarative workflows Jul 31, 2026
Merge upstream/dev at c16cc00 into the workflow host branch. This adopts the first-party right-panel manifest runtime from NeuralNomadsAI#617 and registers Workflows through that contract while preserving stable tab IDs, keyboard navigation, localization, and existing customization behavior.

The merge also carries the latest invalid OpenCode configuration diagnostics and retains both workspace lifecycle test paths. Resolutions were validated with server and UI typechecks, the UI production build, the full server suite, focused right-panel/workflow/workspace tests, and an independent regression review.
Keep the event loop referenced while the mocked stuck fetch waits for AbortSignal.timeout. Real fetch requests retain a socket handle, but the test double did not, allowing Node's test runner to cancel the suite before the unrefed timeout fired in CI.

The timer is cleared after each test. The focused instance-client test and full server suite pass with no failures or cancellations.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/30809608845

Artifacts expire in 7 days.
Artifacts:

  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-tauri-macos
  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-tauri-windows
  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-electron-macos
  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-tauri-linux
  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-tauri-macos-arm64
  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-electron-linux
  • pr-618-ba9776db2fb19a9552f03e9cb42a5e0fddfaaa32-electron-windows

Expose saved-definition create and update operations to the workspace plugin, and attach plugin-started runs to the calling OpenCode session so existing family permissions and Yolo state apply naturally.

Let omitted agent tool lists inherit normal OpenCode access, retain explicit installed-tool allowlists, and add an opt-in repeat exhaustion failure policy for bounded self-correction loops.

Cover schema parsing, runtime behavior, plugin payloads, and route boundaries. Validated with both TypeScript projects, 62 focused tests, and the full 355-test server suite.
A concurrently starting modern Electron client can publish its legacy running marker just before its cross-host participant. On slower Windows runners, Tauri could observe that transient state and both clients could remain secondary.

Recheck a blocking legacy marker once after a bounded 50 ms grace period while preserving PID, process identity, and executable validation. Add a deterministic participant-publication race test.

Validated with rustfmt, five cross-host startup runs, and the complete 85-test Tauri suite on Windows.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/30886836822

Artifacts expire in 7 days.
Artifacts:

  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-tauri-macos
  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-tauri-windows
  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-electron-macos
  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-tauri-linux
  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-tauri-macos-arm64
  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-electron-linux
  • pr-618-8379098faf8052351e5ebc0c9f4ebb833df54a2f-electron-windows

Allow agent nodes to declare an optional sessionKey. Nodes and repeat iterations sharing a key now continue the same OpenCode conversation, while nodes without a key retain the existing one-session-per-action behavior.

Persist bounded key-to-session bindings in the workflow journal and serialize prompts per key so parallel branches cannot mutate one conversation concurrently. Existing crash recovery and confirmed session abort handling remain the side-effect boundary.

Cover portable schema validation, persisted-state validation, repeat and cross-node reuse, parallel serialization, and restored bindings. Validated with server/UI/plugin typechecks and the complete server suite: 354 passed, 4 skipped.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/30890442848

Artifacts expire in 7 days.
Artifacts:

  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-macos
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-windows
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-electron-macos
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-macos-arm64
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-linux
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-electron-linux
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-electron-windows

1 similar comment
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/30890442848

Artifacts expire in 7 days.
Artifacts:

  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-macos
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-windows
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-electron-macos
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-macos-arm64
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-tauri-linux
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-electron-linux
  • pr-618-087c8b2b0d6fad2d07c9e144de6635da8b343885-electron-windows

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Gatekeeper review

Findings

  1. High: Shared sessionKey cancellation can falsely confirm termination without aborting the active OpenCode generation. packages/server/src/workflows/interpreter.ts:192, packages/server/src/workflows/interpreter.ts:657-670, packages/server/src/workflows/manager.ts:1085-1089, packages/server/src/workflows/manager.ts:1222-1229. The session limiter is released before the prior node calls sessionFinished(). A waiting node can start another prompt on the same session, then the prior node deletes that session ID from the shared Set. Cancellation subsequently treats the active session as already finished and skips the explicit session.abort, allowing remote work or side effects to continue after the run is persisted as cancelled.

  2. High: Caller-controlled ancestry can borrow another session family's Yolo permissions. packages/server/src/server/routes/workflows.ts:71-75, packages/server/src/server/routes/workflows.ts:247-251, packages/server/src/workflows/interpreter.ts:98-103, packages/server/src/permissions/auto-accept-store.ts:29-55. The plugin start route accepts any initiatorSessionId under the workspace bearer and uses it directly as parentID. A callback-token holder can nominate a different Yolo-enabled session, causing workflow descendants and their inherited tools to be auto-approved under that family.

  3. High: A workspace-scoped plugin capability can replace workflows used by every workspace. packages/server/src/server/http-server.ts:247-269, packages/server/src/server/routes/workflows.ts:184-230, packages/server/src/workflows/manager.ts:128-131, packages/server/src/workflows/manager.ts:431-445. Plugin definition routes ignore request.params.id and mutate one global store. Because omitted tool lists inherit all plugin tools, a compromised or prompt-injected agent in workspace A can replace a trusted definition that later executes in workspace B.

  4. High: Worktree deletion races unpublished workspace startup. packages/server/src/server/routes/worktrees.ts:165-176, packages/server/src/workspaces/manager.ts:166-174, packages/server/src/workspaces/manager.ts:392, packages/server/src/workspaces/manager.ts:496. Starting workspaces are reserved before publication but excluded from list() and get(). DELETE can therefore remove the worktree while runtime.launch is using it; a new startup can also begin after the route's scan and before git worktree remove.

  5. High: A recovery marker with completed actions permanently reserves the workspace. packages/server/src/workflows/manager.ts:991-1007, packages/server/src/workflows/manager.ts:1470-1476, packages/server/src/workflows/manager.ts:1612-1624, packages/server/src/workflows/manager.ts:661-666, packages/server/src/workflows/manager.ts:750-767. If terminal persistence fails after all actions complete, restart creates recovery_required with no ambiguous session IDs. Resume rejects it for lacking IDs, and cancellation also refuses to confirm termination, leaving no API path to release the reservation.

  6. Medium: Crashes before action admission are misclassified as ambiguous side effects. packages/server/src/workflows/interpreter.ts:144-147, packages/server/src/workflows/interpreter.ts:451-455, packages/server/src/workflows/manager.ts:1627-1658. An action is persisted as running before its attempt is incremented. A crash while waiting for a limiter or listing tools leaves attempt === 0 and no session, yet restart marks it unrecoverable; resume and cancel then cannot release the run despite no side effect having started.

  7. Medium: Repeat results and onExhausted outcomes are not recovery-stable. packages/server/src/workflows/interpreter.ts:251-266, packages/server/src/workflows/manager.ts:1646-1654. The loop always resumes from index zero and evaluates while before consulting completed iterations. If the last completed body changes the condition to false and the process crashes before the repeat parent is persisted, recovery can omit that body's output and turn an exhaustion failure into success. A normal run also fails immediately after the last body without checking whether that body satisfied the exit condition.

  8. Medium: Confirmed abort/recovery resends a named prompt into the interrupted conversation. packages/server/src/workflows/interpreter.ts:327-332, packages/server/src/workflows/interpreter.ts:404-422, packages/server/src/workflows/interpreter.ts:742-746, packages/server/src/workflows/manager.ts:1488-1499. Abort handling removes execution-node session references but retains sessionBindings. Retry or confirmed recovery therefore reuses the aborted session and appends the identical prompt again, potentially duplicating instructions or side effects already represented by the first user message.

  9. Medium: Human confirmations can authorize a different state from the one displayed. packages/ui/src/components/instance/shell/right-panel/tabs/WorkflowRunList.tsx:49-71, packages/server/src/server/routes/workflows.ts:98, packages/server/src/workflows/manager.ts:639-668. Approval reads the reactive current gate only after the dialog closes; if another client advances from gate A to gate B, the stale dialog approves B. Recovery confirmation carries neither an expected revision nor interrupted-node identity, so a stale dialog can authorize a later, unrelated ambiguous side effect.

  10. Medium: The Tauri 50 ms retry does not close the participant-publication race. packages/tauri-app/src-tauri/src/client_state/cross_host.rs:229-276, packages/tauri-app/src-tauri/src/client_state/cross_host.rs:296-320, packages/tauri-app/src-tauri/src/client_state/cross_host.rs:726-751. If modern Electron publishes its legacy marker but is suspended for longer than 50 ms before publishing its participant, Tauri remains permanently secondary. If Electron then crashes before acquiring ownership, Tauri never retries acquisition and client-state ownership remains vacant until restart.

  11. Medium: Side-effect checkpoints are atomic but not crash-durable. packages/server/src/workflows/manager.ts:1328-1334, packages/server/src/workflows/manager.ts:1706-1720, packages/server/src/workflows/definition-store.ts:205-214. Temporary files are renamed without syncing the file or containing directory. A power or OS crash after persist() returns but after a prompt starts can restore an older checkpoint and cause recovery to repeat an effect it believes never started.

  12. Medium: Valid definition-store limits permit multi-gigabyte concurrent reads. packages/server/src/workflows/definition-store.ts:24-25, packages/server/src/workflows/definition-store.ts:125-140. The store permits 1,000 histories of approximately 4 MiB each, while list() reads and parses every file concurrently. A catalog built entirely through valid plugin requests can make ordinary definition listing allocate several gigabytes and terminate the server.

  13. Medium: Workflow SSE delivery has unbounded buffering and quadratic snapshot traffic. packages/server/src/workflows/manager.ts:1725-1729, packages/server/src/server/routes/events.ts:39-50, packages/server/src/workflows/definition-schema.ts:16, packages/server/src/workflows/interpreter.ts:18. Every checkpoint publishes the complete accumulated run, potentially containing 10,000 nodes and 4 MiB of output. The SSE writer ignores backpressure, so slow clients retain increasingly large snapshots in memory while large runs repeatedly serialize and transmit the entire history.

Open questions / assumptions

  • Callback credentials are assumed not to confer arbitrary session-family identity.
  • Per-workspace callback tokens are assumed not to be host-wide workflow-administrator credentials.
  • OpenCode abort is assumed not to transactionally remove the original user message and partial response.
  • onExhausted: fail is assumed to mean the exit condition remains true after all permitted attempts.
  • Power/OS-crash recovery is assumed to be in scope.
  • Electron is assumed to have no enforced upper bound guaranteeing participant publication within 50 ms.

Validation gaps

  • No cancellation test covers the handoff between two serialized prompts sharing one sessionKey.
  • No restart test covers an action persisted as running with attempt === 0.
  • No recovery test covers a completed repeat body whose parent node was not persisted.
  • No test resolves or cancels marker-backed recovery containing only completed actions.
  • No integration test combines plugin-start ancestry with the real AutoAcceptManager.
  • No cross-workspace test defines plugin definition-mutation scope.
  • No mounted UI test changes gate/recovery state while confirmation dialogs are open.
  • The Tauri retry test does not cover publication after 50 ms or producer crash.
  • No power-loss/fsync, maximum-catalog, slow-SSE-client, or unpublished-workspace deletion-race test exists.

Existing checks at review time: 354/358 server tests with 4 skipped, 85/85 Tauri tests, server/UI typechecks, plugin build, and all PR CI jobs green.

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.

[Feature Request] Instance to Instance communication?

1 participant