Skip to content

feat(spec): add build-time custom step types#1987

Merged
yottahmd merged 13 commits intomainfrom
custom-step-types
Apr 10, 2026
Merged

feat(spec): add build-time custom step types#1987
yottahmd merged 13 commits intomainfrom
custom-step-types

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Apr 10, 2026

Summary

  • add build-time step_types definitions so DAGs and base config can define reusable builtin-backed step templates with validated input schemas
  • add structured exec support for direct argv execution and preserve default precedence, handler overrides, and DAG-level executor inheritance for expanded custom steps
  • propagate embedded base config for distributed execution, update the DAG JSON schema and generated proto output, and add local and distributed regression coverage for custom step type behavior

Test plan

  • go test ./internal/core/spec -count=1
  • go test ./internal/intg -run 'TestCustomStepTypes' -count=1
  • go test ./internal/intg/distr -run 'TestCustomStepTypes' -count=1

Summary by CodeRabbit

Release Notes

  • New Features
    • Custom step type definitions: Define reusable custom step types at the DAG level with input schema validation and template support
    • New exec field: Execute commands with explicit argument arrays, avoiding shell parsing overhead

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

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: 46e18c76-f927-49e1-8e46-330c85559476

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 support for custom step type definitions in DAGs, enabling reusable templated step configurations. It extends the JSON schema to define step_types at DAG level, adds structured exec field for direct command execution, and updates the build pipeline to resolve custom types using template rendering and defaults merging.

Changes

Cohort / File(s) Summary
Schema Definitions
internal/cmn/schema/dag.schema.json
Added DAG-level step_types schema with custom step type definitions; extended step schema to support exec field for structured execution; refactored executorType to allow both builtin types and custom type names; added stepExec, customStepTypeDefinition, and customStepTypes definitions.
Build Pipeline Core
internal/core/spec/builder.go, internal/core/spec/step.go, internal/core/spec/step_types.go
Refactored step construction with decodeStep, buildStepFromSpec, and finalizeBuiltStepName helpers; added custom step type resolution via registry; implemented exec field parsing with argument coercion; added comprehensive custom step type support including template rendering with $input variable expansion, schema validation, defaults application, and metadata stamping.
DAG & Spec Processing
internal/core/spec/dag.go, internal/core/spec/loader.go
Extended dag to store raw handler specs and custom step type definitions; added rawHandler method for event-type mapping; updated handler and step construction to use merged defaults and delegate to buildStepFromSpec; enhanced loader to compute and propagate custom step type registries and base defaults via docCtx.
Defaults Merging
internal/core/spec/defaults.go, internal/core/spec/defaults_test.go
Added mergeDefaults and mergeAgentDefaults functions to compose base and override defaults with conditional field replacement, additive Env prepending, and Preconditions concatenation; included comprehensive test coverage for nil-handling and merge semantics.
Configuration & Runtime Integration
internal/persis/filebaseconfig/default_base_config.go, internal/service/coordinator/handler.go, internal/test/helper.go
Extended default base config template with custom step types documentation and example; updated coordinator task/subtask creation to conditionally include base config content; updated test helper to load base config when available.
Feature & Integration Tests
internal/core/spec/step_types_feature_test.go, internal/intg/step_types_test.go, internal/intg/distr/step_types_test.go
Added feature tests validating custom step type loading, template rendering, handler support, defaults/template/call-site precedence, and exec step behavior; added integration tests for worker execution without local base config, sub-DAG propagation, and environment variable precedence.

Sequence Diagram

sequenceDiagram
    participant DAG as DAG Definition
    participant Loader as Loader
    participant Registry as Step Type Registry
    participant Builder as Builder
    participant Step as Built Step
    participant Executor as Executor

    DAG->>Loader: LoadYAML (with base config)
    Loader->>Loader: Extract step_types from base + current
    Loader->>Registry: Build custom step type registry
    Loader->>Loader: Decode steps/handlers with raw payloads
    
    Loader->>Builder: processDAGDocument (with ctx containing registry)
    Builder->>Builder: For each step: decodeStep(raw)
    Builder->>Registry: Resolve custom type (if type matches custom)
    Registry->>Registry: Render template with inputs
    Registry->>Registry: Merge defaults + template + call-site
    Registry->>Builder: Return merged specification
    Builder->>Builder: buildConcreteStep with merged spec
    Builder->>Step: Create core.Step with custom_type metadata
    
    Loader->>Executor: Return built DAG with custom steps
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary feature being added: support for custom step types at build time, matching the changeset's main objective.

✏️ 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 custom-step-types

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cmn/schema/dag.schema.json`:
- Around line 4820-4826: customStepTypes currently allows arbitrary property
names, which can create definitions that cannot be referenced by step.type
(which enforces the regex ^[A-Za-z][A-Za-z0-9_-]*$); update the customStepTypes
definition to constrain keys by adding a propertyNames schema with the same
pattern (^[A-Za-z][A-Za-z0-9_-]*$) so keys match the step.type/executorType
naming rules and are referenceable.
- Around line 1139-1150: The new mutual-exclusion constraints for
"exec"/"command" and "exec"/"script" were added as a second "allOf" key which
overwrites the earlier one; instead merge these two "not": {"required": [...]}
entries into the existing "allOf" array so there is a single combined allOf
including both constraints. Locate the JSON object that already defines "allOf"
and append the two {"not": {"required":["exec","command"]}} and {"not":
{"required":["exec","script"]}} clauses to that same array (referencing the
"allOf", "exec", "command", and "script" symbols) so the exclusivity rules are
preserved. Ensure you remove the duplicate top-level "allOf" definition so the
schema has only one "allOf" array containing all constraints.

In `@internal/core/spec/defaults.go`:
- Around line 170-210: mergeDefaults currently skips override values when they
are zero-valued (e.g., TimeoutSec==0 or Agent.Prompt==""), so explicit clears in
override are lost; fix by making scalar/string fields that must support
explicit-zero semantics nullable (e.g., change TimeoutSec to *int, Agent.Prompt
to *string, or add explicit "set" booleans) and update mergeDefaults to copy
override fields when the override pointer/flag is present (override.TimeoutSec
!= nil -> merged.TimeoutSec = override.TimeoutSec, similarly use nil checks for
override.Agent and let mergeAgentDefaults treat nil vs present empty string
appropriately), and ensure Env/Preconditions logic also treats an
explicitly-present empty value as an override rather than skipping it.

In `@internal/core/spec/step_types.go`:
- Around line 108-128: validateCustomStepTypeSpec trims names but
registry.entries uses the raw map keys, causing unreachable entries and missed
duplicate detection; before calling validateCustomStepTypeSpec and before
checking/storing into registry.entries in the loops over base and local,
normalize the key (e.g., strings.TrimSpace and any other normalization used by
validateCustomStepTypeSpec/Lookup) into a variable like normalizedName, use
normalizedName for the duplicate-exists check and as the map key when assigning
registry.entries[normalizedName] = def, and continue to call
validateCustomStepTypeSpec with the original spec but use normalizedName for all
dedupe/storage/lookup logic so whitespace-only duplicates are rejected and
lookups work consistently.

In `@internal/intg/distr/step_types_test.go`:
- Around line 92-107: The test creates a fixture with newTestFixture(...) that
currently allows the worker to inherit the scheduler's base config, letting the
custom greet step type be resolved locally; update the fixture invocation to add
withWorkerBaseConfigPath("/nonexistent/base.yaml") so the worker cannot use a
local base config and the embedded base config propagation is exercised (i.e.,
modify the newTestFixture call in this test to include
withWorkerBaseConfigPath("/nonexistent/base.yaml") alongside
withLogPersistence(), withBaseConfigPath(...), and withLabels(...)).
🪄 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: 2e8e9aec-37a2-474a-9645-074eb5d780b1

📥 Commits

Reviewing files that changed from the base of the PR and between 8b42bba and cf93da0.

📒 Files selected for processing (14)
  • internal/cmn/schema/dag.schema.json
  • internal/core/spec/builder.go
  • internal/core/spec/dag.go
  • internal/core/spec/defaults.go
  • internal/core/spec/defaults_test.go
  • internal/core/spec/loader.go
  • internal/core/spec/step.go
  • internal/core/spec/step_types.go
  • internal/core/spec/step_types_feature_test.go
  • internal/intg/distr/step_types_test.go
  • internal/intg/step_types_test.go
  • internal/persis/filebaseconfig/default_base_config.go
  • internal/service/coordinator/handler.go
  • internal/test/helper.go

Comment thread internal/cmn/schema/dag.schema.json Outdated
Comment thread internal/cmn/schema/dag.schema.json
Comment thread internal/core/spec/defaults.go Outdated
Comment thread internal/core/spec/step_types.go Outdated
Comment thread internal/intg/distr/step_types_test.go Outdated
@yottahmd yottahmd merged commit 56348b8 into main Apr 10, 2026
3 checks passed
@yottahmd yottahmd deleted the custom-step-types branch April 10, 2026 17:52
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