Skip to content

feat: add remote action executor - #2167

Merged
yohamta0 merged 19 commits into
mainfrom
feature/remote-actions
May 18, 2026
Merged

yohamta0 merged 19 commits into
mainfrom
feature/remote-actions

Conversation

@yohamta0

@yohamta0 yohamta0 commented May 17, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds remote Dagu action execution with manifest-backed action DAGs and workspace bundle support for distributed workers.

Changes

  • Add action executor support for official, GitHub, and explicit source action references
  • Add action manifest loading with input/output schema validation
  • Add deterministic workspace bundle pack, extract, storage, and coordinator transfer paths
  • Add worker workspace materialization for shared-nothing distributed action execution
  • Tighten action reference validation and cleanup failed worker workspace extraction
  • Add and update tests for action refs, workspace bundles, coordinator upload behavior, and worker cleanup

Related Issues

N/A

Checklist

  • Code follows the project style guidelines
  • Self-review of the code has been performed
  • Tests have been added or updated as needed
  • Documentation has been updated as needed
  • Changes have been tested locally

@coderabbitai

coderabbitai Bot commented May 17, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f527551-2382-4ca1-8bf3-e2299d874245

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a complete Dagu Actions system: a mechanism for DAGs to invoke other DAGs as reusable, parameterized actions. Actions can be resolved from GitHub repositories, local source directories, or official DaguCloud references, their inputs/outputs validated against JSON schemas, and their workspaces bundled and distributed for both local and coordinated execution.

Changes

Action System & Distributed Execution

Layer / File(s) Summary
Action type system and schema definitions
internal/core/step.go, internal/cmn/schema/dag.schema.json
Adds ExecutorTypeAction = "action" constant, extends builtinExecutorType enum, and introduces actionExecutorConfig schema requiring ref (remote action reference) and optional input.
Action reference parsing and step normalization
internal/core/spec/step_v2.go, internal/core/spec/step_v2_test.go
Detects and parses remote action references (GitHub owner/repo@version, official slack@version, source:path@version), validates target and version strings, normalizes to action executor config, and includes test coverage for valid/invalid reference patterns.
Action manifest loading and bundle resolution
internal/runtime/builtin/action/manifest.go, internal/runtime/builtin/action/resolver.go, internal/runtime/builtin/action/action_test.go
Loads action manifest YAML with DAG path and input/output schema validation; resolves action bundles from local directories, GitHub repos (with shallow/full clone caching), or file:// URIs; validates git refs and enforces path containment; includes unit tests for resolution, validation, and manifest structure.
Action executor implementation and execution
internal/runtime/builtin/action/action.go
Implements Executor type that resolves action bundles, loads manifests, validates inputs against JSON Schema, executes the action DAG as a sub-DAG with workspace bundling, tracks sub-run metadata, and serializes validated outputs to JSON.
Workspace bundle packing, extraction, and storage
internal/runtime/workspacebundle/bundle.go
Creates deterministic tar+gzip bundles from workspace directories (skipping .git, enforcing size/file limits), computes SHA-256 digests, safely extracts with digest verification and containment checks, and provides on-disk Store with atomic writes and integrity verification.
Coordinator workspace bundle gRPC and handler
proto/coordinator/v1/coordinator.proto, internal/service/coordinator/handler.go, internal/service/coordinator/workspace_bundle_handler.go
Extends CoordinatorService with PutWorkspaceBundle (client-streaming), HasWorkspaceBundle, and GetWorkspaceBundle (server-streaming) RPCs; augments Task message with workspace bundle metadata fields; implements coordinator handler for persisting and retrieving bundles from local store with sequence validation and size enforcement.
Coordinator client for workspace bundle transfer
internal/service/coordinator/client.go, internal/runtime/executor/task.go
Implements PutWorkspaceBundle and GetWorkspaceBundle on coordinator client with digest verification, health-based coordinator selection, chunked streaming, and final integrity checks; adds WithWorkspaceBundle task option for metadata propagation.
SubDAGExecutor workspace seed and materialization
internal/runtime/executor/dag_runner.go
Adds WorkspaceSeed type and SetWorkspaceSeed method to SubDAGExecutor, enables workspace materialization for local execution with deferred cleanup, routes workspace-seeded DAGs through workspace bundle coordinator APIs, and introduces NewSubDAGExecutorForDAG constructor for pre-loaded DAG execution.
Worker task handling with workspace materialization
internal/service/worker/action_workspace.go, internal/service/worker/handler.go, internal/service/worker/remote_handler.go, internal/service/worker/worker.go
Validates task workspace bundle configuration, materializes bundles into temporary directories via coordinator client, injects --default-working-dir flag into sub-DAG commands, computes workspace default directories from DAG paths, and wires bundle client into worker task handler during initialization.
Testing, integration, and supporting changes
internal/intg/action_test.go, internal/core/spec/step_types.go, internal/cmd/retry.go, scripts/installer.sh
Adds integration test verifying action DAG execution with input/output validation and sub-run metadata; adds "action" to builtin step types; applies default working directory during DAG retry; updates installer PATH to include npm/pnpm/bun directories and remove Deno.
Action executor registration with builtin system
internal/runtime/builtin/builtin.go
Registers action executor package via blank import, ensuring executor initialization and schema registration during runtime startup.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • dagucloud/dagu#2122: Related modifications to v2 action step schema and normalization logic in internal/core/spec/step_v2.go and schema validation.
  • dagucloud/dagu#2111: Related changes to retry DAG restore logic in internal/cmd/retry.go for working directory preservation.
  • dagucloud/dagu#1986: Related distributed task option wiring in internal/runtime/executor/dag_runner.go coordinator dispatch flow.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add remote action executor' clearly and concisely describes the primary change—adding a remote action executor capability—which aligns with the substantial feature additions across multiple files.
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.
Description check ✅ Passed The PR description is well-structured, follows the template requirements with Summary, Changes, and Checklist sections, and provides clear context about the feature being added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/remote-actions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
internal/runtime/workspacebundle/bundle.go (1)

451-454: 💤 Low value

Nil receiver checks are unusual and may mask configuration bugs.

The s == nil checks in Put and Get return errors at runtime rather than failing fast during construction. If the store is not configured, this should ideally be caught earlier (e.g., at handler initialization) rather than silently failing on each call.

This is acceptable if the store is intentionally optional, but consider documenting this behavior or validating configuration earlier.

Also applies to: 501-504

🤖 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 `@internal/runtime/workspacebundle/bundle.go` around lines 451 - 454, The
nil-receiver guards in Store.Put and Store.Get (methods on type Store) mask
configuration bugs by returning runtime errors instead of failing fast; remove
the s == nil checks from these methods and either (a) validate the Store during
construction/initialization (e.g., in NewStore or the handler that receives
*Store) and return an error or panic there if dir is empty/nil, or (b)
explicitly document that the Store is optional and provide an
IsConfigured/Enabled method callers must check before calling Put/Get; update
Store.Put and Store.Get to only validate s.dir (or assume non-nil receiver) and
rely on the constructor/initializer to enforce correctness.
internal/service/coordinator/client.go (1)

833-879: 💤 Low value

Upload fails fast on any coordinator error, which may be overly strict.

The current implementation returns immediately if hasWorkspaceBundleInMember or putWorkspaceBundleToMember fails for any coordinator. This ensures all coordinators have the bundle, but a single transient coordinator failure will abort the entire upload even if other coordinators could accept it.

If the intent is "at least one coordinator must have the bundle," consider continuing on error and only failing if satisfied == 0. If strict replication is required, the current behavior is correct but should be documented.

🤖 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 `@internal/service/coordinator/client.go` around lines 833 - 879,
PutWorkspaceBundle currently aborts on the first error from
hasWorkspaceBundleInMember or putWorkspaceBundleToMember which makes uploads
fail-fast for transient coordinator errors; change PutWorkspaceBundle so that
errors from hasWorkspaceBundleInMember and putWorkspaceBundleToMember do not
immediately return but instead set lastErr and continue to the next member
(increment satisfied only on success or when exists==true), and only return a
composite error at the end if satisfied == 0; update the error wrapping to
include member.ID and ensure getOrCreateClient and isHealthy behavior remains
unchanged (symbols: PutWorkspaceBundle, hasWorkspaceBundleInMember,
putWorkspaceBundleToMember, satisfied, lastErr).
🤖 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 `@internal/cmn/schema/dag.schema.json`:
- Around line 2147-2158: The schema block that applies when "type": "action"
currently only requires "with" and references actionExecutorConfig but allows
remote refs that normalizeStepExecutionRaw would reject; update this conditional
(the "if" / "then" for type: "action" in dag.schema.json) to enforce the same
remote-ref validation used by action: normalization—i.e., tighten the
regex/format constraints on the "with" / ref fields or reuse the exact validator
used by actionExecutorConfig/action normalization so that
isSafeActionVersion-equivalent checks are applied for steps declared with type:
action; ensure the change references actionExecutorConfig and aligns with
normalizeStepExecutionRaw and isSafeActionVersion behavior.

In `@internal/core/spec/step_v2.go`:
- Around line 189-195: The code treats any action starting with "source:" as
remote without validating the ref, which lets malformed or unsafe refs slip
through; update the logic in the isRemoteActionReference handling (and the
similar blocks referenced) to parse the ref via actionWith(raw) and then
validate the parsed ref's version using isSafeActionVersion (or return a clear
error) before calling normalizeRemoteAction; in other words, ensure
actionWith(raw) succeeds and the returned with.Version passes
isSafeActionVersion for the given action/normalized/action variables, and if
not, return an error here rather than letting normalizeRemoteAction/resolver
handle it downstream.

In `@internal/service/worker/action_workspace.go`:
- Around line 71-73: When workspacebundle.Extract fails, ensure the partially
materialized workDir is removed before returning so retries don't see stale
files: after the Extract call in the same block that currently returns
fmt.Errorf("extract workspace bundle %s: %w", desc.Digest, err), call
os.RemoveAll(workDir) (or equivalent cleanup) and handle any RemoveAll error
without masking the original Extract error (e.g., log or append cleanup error to
the returned error), then return the original wrapped extract error referencing
desc.Digest.

---

Nitpick comments:
In `@internal/runtime/workspacebundle/bundle.go`:
- Around line 451-454: The nil-receiver guards in Store.Put and Store.Get
(methods on type Store) mask configuration bugs by returning runtime errors
instead of failing fast; remove the s == nil checks from these methods and
either (a) validate the Store during construction/initialization (e.g., in
NewStore or the handler that receives *Store) and return an error or panic there
if dir is empty/nil, or (b) explicitly document that the Store is optional and
provide an IsConfigured/Enabled method callers must check before calling
Put/Get; update Store.Put and Store.Get to only validate s.dir (or assume
non-nil receiver) and rely on the constructor/initializer to enforce
correctness.

In `@internal/service/coordinator/client.go`:
- Around line 833-879: PutWorkspaceBundle currently aborts on the first error
from hasWorkspaceBundleInMember or putWorkspaceBundleToMember which makes
uploads fail-fast for transient coordinator errors; change PutWorkspaceBundle so
that errors from hasWorkspaceBundleInMember and putWorkspaceBundleToMember do
not immediately return but instead set lastErr and continue to the next member
(increment satisfied only on success or when exists==true), and only return a
composite error at the end if satisfied == 0; update the error wrapping to
include member.ID and ensure getOrCreateClient and isHealthy behavior remains
unchanged (symbols: PutWorkspaceBundle, hasWorkspaceBundleInMember,
putWorkspaceBundleToMember, satisfied, lastErr).
🪄 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: 0c29f513-c2a2-444f-b736-1fae786a0185

📥 Commits

Reviewing files that changed from the base of the PR and between 71ec737 and 2d46d24.

⛔ Files ignored due to path filters (3)
  • proto/coordinator/v1/coordinator.pb.go is excluded by !**/*.pb.go
  • proto/coordinator/v1/coordinator_grpc.pb.go is excluded by !**/*.pb.go
  • proto/coordinator/v1/coordinator_protoopaque.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (25)
  • internal/cmd/coord.go
  • internal/cmd/retry.go
  • internal/cmn/schema/dag.schema.json
  • internal/core/spec/step_types.go
  • internal/core/spec/step_v2.go
  • internal/core/spec/step_v2_test.go
  • internal/core/step.go
  • internal/intg/action_test.go
  • internal/runtime/builtin/action/action.go
  • internal/runtime/builtin/action/action_test.go
  • internal/runtime/builtin/action/manifest.go
  • internal/runtime/builtin/action/resolver.go
  • internal/runtime/builtin/builtin.go
  • internal/runtime/executor/dag_runner.go
  • internal/runtime/executor/task.go
  • internal/runtime/workspacebundle/bundle.go
  • internal/service/coordinator/client.go
  • internal/service/coordinator/handler.go
  • internal/service/coordinator/workspace_bundle_handler.go
  • internal/service/worker/action_workspace.go
  • internal/service/worker/handler.go
  • internal/service/worker/remote_handler.go
  • internal/service/worker/worker.go
  • proto/coordinator/v1/coordinator.proto
  • scripts/installer.sh
💤 Files with no reviewable changes (1)
  • scripts/installer.sh

Comment thread internal/cmn/schema/dag.schema.json
Comment thread internal/core/spec/step_v2.go
Comment thread internal/service/worker/action_workspace.go
@yohamta0
yohamta0 merged commit aded506 into main May 18, 2026
10 checks passed
@yohamta0
yohamta0 deleted the feature/remote-actions branch May 18, 2026 04:37
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