-
-
Notifications
You must be signed in to change notification settings - Fork 461
feat: add workflow control steps (branch, foreach, loop, map, sleep) #929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 3ca150d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📝 WalkthroughWalkthroughAdds workflow control primitives (sleep, sleep-until, foreach, loop, branch, map), guardrail runtime and steps, per-step and workflow retry propagation, abort-aware wait utilities, tracing helpers, expanded types/serialization, tests, examples, and docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Runner as Workflow Runner
participant Step as Workflow Step
participant Guard as Guardrail Runtime / Agent
participant Tracer as Tracing
participant Signal as AbortSignal
participant Store as Persistence
Runner->>Tracer: start span for step
Runner->>Step: execute(context, retryCount=0)
alt step applies input guardrails
Step->>Guard: apply input guardrails
Guard-->>Step: transformed input or block/error
end
alt sleeping or waiting
Step->>Signal: waitWithSignal(delay, signal)
Signal-->>Step: (abort) reason
end
alt step spawns sub-executions (foreach/branch/loop)
Step->>Tracer: start child spans per sub-execution
Step->>Step: execute sub-step(s) (isolated context)
Step-->>Tracer: end child spans
end
alt error thrown
Step->>Tracer: record error, end span
Step-->>Runner: throw error
alt retries available
Runner->>Runner: apply retry delay
Runner->>Step: execute(context, retryCount+1)
end
else success
Step->>Guard: apply output guardrails (if any)
Guard-->>Step: transformed output
Step-->>Runner: return result
Step->>Tracer: end span (success)
end
Runner->>Store: persist final/suspend state (as needed)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
Comment |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 issues found across 35 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="website/docs/workflows/steps/and-branch.md">
<violation number="1" location="website/docs/workflows/steps/and-branch.md:17">
P2: Example uses mutually exclusive conditions that never both execute, failing to demonstrate andBranch's key feature: running multiple branches when multiple conditions are true. Consider using conditions like `amount > 1000` and `amount > 500` so readers can see both branches execute for values like 1500.</violation>
</file>
<file name=".changeset/social-humans-hammer.md">
<violation number="1" location=".changeset/social-humans-hammer.md:68">
P1: The example chains `andDoWhile` after `andForEach`, but `andForEach` returns an array while the loop logic expects a single number. After doubling `[1, 2, 3]` → `[2, 4, 6]`, the increment step `data + 1` and condition `data < 3` would operate on an array, not a number, causing a type error.</violation>
<violation number="2" location=".changeset/social-humans-hammer.md:117">
P1: The `date` parameter in `andSleepUntil` should be a function returning a Date, not a Date object. Using `new Date(Date.now() + 60_000)` will evaluate at workflow definition time, not execution time, causing the sleep to target a past timestamp if the workflow runs later.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Deploying voltagent with
|
| Latest commit: |
3ca150d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://34e95595.voltagent.pages.dev |
| Branch Preview URL: | https://feat-add-workflow-control-st.voltagent.pages.dev |
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/core/src/utils/node-utils.ts (2)
52-63: Bug:getNodeTypeFromNodeIdcan’t parse NodeTypes containing underscores.Example: nodeId starting with
"workflow_map_step_"yieldsparts[0] === "workflow"and will never match"workflow_map_step".Proposed fix
export const getNodeTypeFromNodeId = (nodeId: string): NodeType | null => { - const parts = nodeId.split("_"); - if (parts.length >= 1) { - const typePart = parts[0].toLowerCase(); - for (const type of Object.values(NodeType)) { - if (typePart === type) { - return type as NodeType; - } - } - } - return null; + const lower = nodeId.toLowerCase(); + for (const type of Object.values(NodeType)) { + const t = type.toLowerCase(); + if (lower === t || lower.startsWith(`${t}_`)) { + return type as NodeType; + } + } + return null; };
200-213: Inconsistent:createWorkflowStepNodeIdcan embedstepNamefor all steps, but extractor only handles"func".If node IDs for
"sleep" | "branch" | ...include a name suffix,extractWorkflowStepInfowill currently drop it.Proposed fix
if (rest.length > 0) { const identifier = rest.join("_"); - if (stepType === "agent") { - (result as any).agentId = identifier; - } else if (stepType === "func") { - (result as any).stepName = identifier; - } else if (identifier.startsWith("parallel_")) { + if (identifier.startsWith("parallel_")) { const parallelIndex = Number.parseInt(identifier.replace("parallel_", "")); if (!Number.isNaN(parallelIndex)) { (result as any).parallelIndex = parallelIndex; } + } else if (stepType === "agent") { + (result as any).agentId = identifier; + } else { + (result as any).stepName = identifier; } }
🤖 Fix all issues with AI agents
In @packages/core/src/workflow/steps/and-foreach.spec.ts:
- Around line 6-24: Add several unit tests for andForEach: keep the existing
happy-path using andForEach and andThen, then add tests that call step.execute
with an empty array (expect []), a single-item array, and mixed data types to
verify type behavior; add a test that sets the concurrency parameter on
andForEach and uses async inner steps to assert parallelism limits and order
preservation; add a test where the inner step (the andThen.execute
implementation) throws to assert error propagation/handling; and add a test
invoking step.execute with a non-array input to assert validation/rejection
behavior. Use createMockWorkflowExecuteContext to build inputs and reference
andForEach, andThen, step.execute, and the concurrency option so tests locate
the right implementation.
In @packages/core/src/workflow/steps/and-map.ts:
- Around line 46-74: resolveMapEntry currently returns entry.fn(context) without
awaiting, so async InternalWorkflowFunc results remain unresolved; change the
"fn" branch in resolveMapEntry to "return await entry.fn(context);" and ensure
callers (e.g., the andMap flow that collects map entries) await resolveMapEntry
(use Promise.all when mapping over entries) so the final result contains
resolved values not Promises; keep resolveMapEntry signature async and update
any usages that assumed synchronous returns.
In @packages/core/src/workflow/steps/and-sleep.spec.ts:
- Around line 7-20: Add more unit tests for andSleep: keep the existing
zero-duration case, add a positive-duration test (e.g., 100ms) that measures
elapsed time around step.execute created via createMockWorkflowExecuteContext to
assert it waited at least the duration, add invalid-input tests that pass
negative duration and non-numeric values to andSleep and assert it
rejects/throws, and consider using Jest fake timers or real timing checks
(Date.now() before/after) to verify actual delay; reference the andSleep factory
and the step.execute call to locate where to invoke and assert behavior.
- Around line 22-35: Add unit tests for andSleepUntil to cover missing
scenarios: create a test where the provided date is in the future and assert the
step delays execution until the target time (use Jest timers or a time-mocking
utility to advance time and verify step.execute resumes only after the target);
add tests for invalid inputs (null, undefined, new Date("invalid")) and confirm
the step rejects or returns expected error behavior; add tests for dynamic date
suppliers by passing a function (ctx => Date or async function) into
andSleepUntil and assert correct resolution and waiting behavior; include a
timing verification test that measures elapsed time (or uses fake timers) to
ensure the workflow pauses until the specified date; use the existing helpers
createMockWorkflowExecuteContext and the andSleepUntil() factory and assert on
step.execute results and thrown errors as appropriate.
🧹 Nitpick comments (12)
website/docs/workflows/steps/and-loop.md (1)
1-68: Documentation looks good and examples are clear.The documentation effectively explains the do-while and do-until loop semantics with practical examples. The function signatures are clearly documented, and the notes section appropriately highlights that the step runs at least once.
Consider adding brief examples of:
- Return value/output of the loop (what data flows to the next step)
- How to handle errors within the loop condition or step
- Common use cases (e.g., retry logic, polling)
📝 Optional enhancement: Add return value example
After line 63, you could add:
## Return Value The loop returns the final result from the last execution of the step: \```typescript const result = await workflow.execute(0); // result will be the final incremented value after the loop completes \```packages/core/src/workflow/steps/and-sleep.spec.ts (1)
6-6: Misleading describe block name.The describe block is named "andSleep" but contains tests for both
andSleepandandSleepUntil. Consider using a more inclusive name like "Sleep Steps" or split into separate describe blocks for each function.♻️ Proposed fix
-describe("andSleep", () => { +describe("Sleep Steps", () => { + describe("andSleep", () => { it("returns input data after sleeping", async () => { const step = andSleep({ id: "sleep", duration: 0, }); const result = await step.execute( createMockWorkflowExecuteContext({ data: { ok: true }, }), ); expect(result).toEqual({ ok: true }); }); + }); + describe("andSleepUntil", () => { it("returns input data when sleepUntil is in the past", async () => { const step = andSleepUntil({ id: "sleep-until", date: new Date(Date.now() - 1000), }); const result = await step.execute( createMockWorkflowExecuteContext({ data: { ok: true }, }), ); expect(result).toEqual({ ok: true }); }); + }); });website/sidebars.ts (1)
120-128: Consider alphabetizing workflow step entries for better discoverability.The new workflow steps are not in alphabetical order (e.g., "and-branch" appears before "and-tap", but "and-foreach" appears after "and-race"). Consistent alphabetical ordering would improve user navigation and make it easier to locate specific steps in the documentation sidebar.
📋 Proposed alphabetical ordering
"workflows/steps/and-agent", + "workflows/steps/and-all", + "workflows/steps/and-branch", + "workflows/steps/and-foreach", + "workflows/steps/and-loop", + "workflows/steps/and-map", + "workflows/steps/and-race", + "workflows/steps/and-sleep", + "workflows/steps/and-sleep-until", + "workflows/steps/and-tap", "workflows/steps/and-when", - "workflows/steps/and-branch", - "workflows/steps/and-tap", - "workflows/steps/and-all", - "workflows/steps/and-race", - "workflows/steps/and-foreach", - "workflows/steps/and-loop", - "workflows/steps/and-sleep", - "workflows/steps/and-sleep-until", - "workflows/steps/and-map",examples/with-workflow/src/index.ts (1)
486-503: Simplify branch result selection logic.The branch result selection uses a broad type assertion and unnecessary
findoperation. Since the branches are mutually exclusive (counter >= 3 vs counter < 3), you can simplify this logic and improve type safety.♻️ Proposed simplification
.andThen({ id: "select-branch", execute: async ({ data }) => { - const results = Array.isArray(data) ? data : []; - const selected = results.find((entry) => entry !== undefined) as - | { counter: number; label: "ready" | "warmup" } - | undefined; - - if (!selected) { - return { counter: 0, label: "warmup" }; - } - - return { - counter: selected.counter, - label: selected.label, - }; + // andBranch returns an array; find the first defined result + const results = Array.isArray(data) ? data : []; + const selected = results.find((entry) => entry !== undefined); + + // Fallback to default if no branch executed + return selected ?? { counter: 0, label: "warmup" as const }; }, });packages/core/src/workflow/steps/and-loop.spec.ts (1)
6-44: Test logic is correct; consider edge case coverage.Both test cases correctly verify the core loop semantics (do-while continues while condition is true; do-until continues until condition is true). The assertions match the expected behavior based on the provided loop configurations.
💡 Optional: Add edge case tests
Consider adding tests for:
- Condition that's immediately false (do-while should still run once)
- Error handling within the loop step
- Loop with async condition evaluation
- Maximum iteration safeguards (if implemented)
Example:
it("runs do-while at least once even if condition starts false", async () => { const step = andDoWhile({ id: "loop", step: andThen({ id: "increment", execute: async ({ data }) => data + 1, }), condition: async () => false, }); const result = await step.execute( createMockWorkflowExecuteContext({ data: 0 }) ); expect(result).toBe(1); // Ran once despite false condition });Based on learnings, ensure all tests pass before committing.
website/docs/workflows/steps/and-branch.md (1)
1-52: Clarify multi-branch execution behavior and add output example.The documentation provides a solid foundation, but could be enhanced for clarity:
- The description states "All branches whose condition is true will execute," but doesn't specify whether execution is parallel or sequential.
- The example shows two branches, but doesn't demonstrate an output where one or both branches run (or don't run).
- The Notes section mentions the array structure and
undefinedvalues but would benefit from a concrete example.💡 Suggested enhancement
Consider adding an "Output" section after the Quick Start example:
## Example Output When executed with `{ amount: 1500 }`: ```typescript [ { amount: 1500, large: true }, // First branch ran undefined // Second branch didn't run ] ``` When executed with `{ amount: -50 }`: ```typescript [ undefined, // First branch didn't run { amount: -50, invalid: true } // Second branch ran ] ```Also clarify in the description whether branches execute in parallel or sequentially (e.g., "All branches whose condition is true will execute in parallel").
packages/core/src/workflow/steps/and-map.spec.ts (1)
5-38: Comprehensive test for all map source types; consider edge cases.The test thoroughly exercises all five map source types (data, input, context, step, value) and correctly validates the aggregated output object. The mock setup is well-structured with appropriate test data for each source.
💡 Optional: Add edge case tests
Consider adding tests for error conditions and edge cases:
it("handles missing paths gracefully", async () => { const step = andMap({ id: "map", map: { missing: { source: "data", path: "nonExistent" }, }, }); const result = await step.execute( createMockWorkflowExecuteContext({ data: {} }) ); expect(result.missing).toBeUndefined(); }); it("handles non-existent step IDs", async () => { const step = andMap({ id: "map", map: { fromStep: { source: "step", stepId: "does-not-exist", path: "value" }, }, }); const result = await step.execute( createMockWorkflowExecuteContext({ getStepData: () => undefined, }) ); expect(result.fromStep).toBeUndefined(); });Based on learnings, ensure all tests pass before committing.
packages/core/src/workflow/steps/and-sleep.ts (1)
17-30: Small type/validation polish fordurationand return value.
- Consider normalizing/validating
durationMshere (and optionally throwing on non-finite), rather than silently deferring towaitWithSignal’s clamp-to-0 behavior.return context.data as DATA;should be redundant ifcontextis properly typed asWorkflowExecuteContext<INPUT, DATA, ...>.packages/core/src/workflow/steps/and-map.ts (2)
10-27:readPathedge-cases: leading dots / empty segments / optional chaining behavior.Today, paths like
".foo"produce an empty first segment, and missing mid-path segments throw"Invalid path ...". If you want “undefined when missing” semantics (or to tolerate leading dots), this function needs adjustment.
87-99: Optional: consider parallel resolution of independent map entries.If map entries are independent,
Promise.all(entries.map(...))can reduce latency vs serialfor...of.packages/core/src/workflow/steps/signal.ts (1)
1-56: Abortable wait + cleanup looks good.One small consideration: if downstream relies on structured error typing, you may want a dedicated error type/shape instead of encoding
"WORKFLOW_CANCELLED"/"WORKFLOW_SUSPENDED"inError.message.packages/core/src/workflow/steps/types.ts (1)
180-181: Clarify branch execution semantics in documentation.The result type
Array<RESULT | undefined>suggests all branches are evaluated concurrently, withundefinedfor non-matching conditions. This is a valid multi-branch pattern but may differ from typical "first-match-wins" branching semantics users expect.Ensure documentation clearly explains that multiple branches can execute and return results simultaneously.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
.changeset/social-humans-hammer.mdexamples/with-workflow/src/index.tspackages/core/src/index.tspackages/core/src/test-utils/mocks/workflows.tspackages/core/src/utils/node-utils.tspackages/core/src/workflow/chain.tspackages/core/src/workflow/context.tspackages/core/src/workflow/core.tspackages/core/src/workflow/index.tspackages/core/src/workflow/internal/types.tspackages/core/src/workflow/internal/utils.tspackages/core/src/workflow/steps/and-branch.spec.tspackages/core/src/workflow/steps/and-branch.tspackages/core/src/workflow/steps/and-foreach.spec.tspackages/core/src/workflow/steps/and-foreach.tspackages/core/src/workflow/steps/and-loop.spec.tspackages/core/src/workflow/steps/and-loop.tspackages/core/src/workflow/steps/and-map.spec.tspackages/core/src/workflow/steps/and-map.tspackages/core/src/workflow/steps/and-sleep-until.tspackages/core/src/workflow/steps/and-sleep.spec.tspackages/core/src/workflow/steps/and-sleep.tspackages/core/src/workflow/steps/helpers.tspackages/core/src/workflow/steps/index.tspackages/core/src/workflow/steps/signal.tspackages/core/src/workflow/steps/types.tspackages/core/src/workflow/types.tswebsite/docs/workflows/steps/and-branch.mdwebsite/docs/workflows/steps/and-foreach.mdwebsite/docs/workflows/steps/and-loop.mdwebsite/docs/workflows/steps/and-map.mdwebsite/docs/workflows/steps/and-sleep-until.mdwebsite/docs/workflows/steps/and-sleep.mdwebsite/recipes/workflows.mdwebsite/sidebars.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/workflow/steps/signal.tspackages/core/src/workflow/steps/and-sleep.spec.tspackages/core/src/workflow/steps/and-sleep-until.tspackages/core/src/workflow/steps/and-branch.tspackages/core/src/index.tspackages/core/src/workflow/internal/types.tspackages/core/src/workflow/context.tspackages/core/src/workflow/steps/and-map.tspackages/core/src/workflow/types.tswebsite/sidebars.tspackages/core/src/workflow/internal/utils.tspackages/core/src/workflow/steps/and-foreach.tspackages/core/src/workflow/steps/and-foreach.spec.tspackages/core/src/workflow/steps/and-loop.tspackages/core/src/workflow/steps/and-sleep.tspackages/core/src/workflow/core.tspackages/core/src/workflow/steps/helpers.tspackages/core/src/workflow/steps/and-map.spec.tspackages/core/src/utils/node-utils.tspackages/core/src/workflow/steps/and-loop.spec.tspackages/core/src/workflow/index.tspackages/core/src/test-utils/mocks/workflows.tspackages/core/src/workflow/steps/and-branch.spec.tspackages/core/src/workflow/chain.tspackages/core/src/workflow/steps/index.tsexamples/with-workflow/src/index.tspackages/core/src/workflow/steps/types.ts
🧠 Learnings (1)
📚 Learning: 2026-01-07T05:09:23.217Z
Learnt from: CR
Repo: VoltAgent/voltagent PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T05:09:23.217Z
Learning: Applies to **/*.test.ts : Test your changes - ensure all tests pass before committing
Applied to files:
packages/core/src/workflow/steps/and-sleep.spec.tspackages/core/src/workflow/steps/and-foreach.spec.tspackages/core/src/workflow/steps/and-map.spec.tspackages/core/src/workflow/steps/and-loop.spec.tspackages/core/src/workflow/steps/and-branch.spec.ts
🧬 Code graph analysis (11)
packages/core/src/workflow/steps/and-sleep-until.ts (5)
packages/core/src/workflow/chain.ts (1)
andSleepUntil(562-567)packages/core/src/workflow/steps/types.ts (2)
WorkflowStepSleepUntilConfig(138-140)WorkflowStepSleepUntil(142-146)packages/core/src/workflow/internal/utils.ts (1)
defaultStepConfig(54-60)packages/core/src/workflow/internal/types.ts (1)
WorkflowExecuteContext(41-57)packages/core/src/workflow/steps/signal.ts (1)
waitWithSignal(26-56)
packages/core/src/workflow/steps/and-branch.ts (5)
packages/core/src/workflow/chain.ts (1)
andBranch(572-589)packages/core/src/workflow/steps/types.ts (2)
WorkflowStepBranchConfig(173-178)WorkflowStepBranch(180-187)packages/core/src/workflow/internal/utils.ts (1)
defaultStepConfig(54-60)packages/core/src/workflow/steps/signal.ts (1)
throwIfAborted(19-24)packages/core/src/workflow/steps/helpers.ts (1)
matchStep(9-29)
packages/core/src/workflow/steps/and-map.ts (7)
packages/core/src/index.ts (2)
context(110-110)andMap(17-17)packages/core/src/workflow/index.ts (2)
WorkflowExecuteContext(33-33)andMap(15-15)packages/core/src/workflow/internal/types.ts (1)
WorkflowExecuteContext(41-57)packages/core/src/workflow/steps/index.ts (4)
WorkflowStepMapEntry(32-32)andMap(12-12)WorkflowStepMapConfig(31-31)WorkflowStepMapResult(33-33)packages/core/src/workflow/steps/types.ts (4)
WorkflowStepMapEntry(189-195)WorkflowStepMapConfig(207-215)WorkflowStepMapResult(203-205)WorkflowStepMap(217-224)packages/core/src/workflow/chain.ts (1)
andMap(642-661)packages/core/src/workflow/internal/utils.ts (1)
defaultStepConfig(54-60)
packages/core/src/workflow/steps/and-loop.ts (3)
packages/core/src/workflow/steps/helpers.ts (1)
matchStep(9-29)packages/core/src/workflow/internal/utils.ts (1)
defaultStepConfig(54-60)packages/core/src/workflow/steps/signal.ts (1)
throwIfAborted(19-24)
packages/core/src/workflow/core.ts (1)
packages/core/src/workflow/steps/types.ts (1)
WorkflowStep(234-247)
packages/core/src/workflow/steps/and-map.spec.ts (6)
packages/core/src/index.ts (1)
andMap(17-17)packages/core/src/workflow/chain.ts (1)
andMap(642-661)packages/core/src/workflow/index.ts (1)
andMap(15-15)packages/core/src/workflow/steps/and-map.ts (1)
andMap(79-101)packages/core/src/workflow/steps/index.ts (1)
andMap(12-12)packages/core/src/test-utils/mocks/workflows.ts (1)
createMockWorkflowExecuteContext(19-41)
packages/core/src/workflow/steps/and-loop.spec.ts (3)
packages/core/src/workflow/chain.ts (2)
andDoWhile(610-621)andDoUntil(626-637)packages/core/src/workflow/steps/and-loop.ts (2)
andDoWhile(97-101)andDoUntil(106-110)packages/core/src/test-utils/mocks/workflows.ts (1)
createMockWorkflowExecuteContext(19-41)
packages/core/src/workflow/steps/and-branch.spec.ts (6)
packages/core/src/index.ts (2)
andBranch(14-14)andThen(6-6)packages/core/src/workflow/chain.ts (2)
andBranch(572-589)andThen(363-369)packages/core/src/workflow/index.ts (2)
andBranch(12-12)andThen(3-3)packages/core/src/workflow/steps/and-branch.ts (1)
andBranch(10-91)packages/core/src/workflow/steps/index.ts (2)
andBranch(10-10)andThen(2-2)packages/core/src/test-utils/mocks/workflows.ts (1)
createMockWorkflowExecuteContext(19-41)
packages/core/src/workflow/chain.ts (6)
packages/core/src/workflow/steps/and-sleep.ts (1)
andSleep(17-31)packages/core/src/workflow/steps/and-sleep-until.ts (1)
andSleepUntil(17-37)packages/core/src/workflow/steps/and-branch.ts (1)
andBranch(10-91)packages/core/src/workflow/steps/and-foreach.ts (1)
andForEach(10-112)packages/core/src/workflow/steps/and-loop.ts (2)
andDoWhile(97-101)andDoUntil(106-110)packages/core/src/workflow/steps/and-map.ts (1)
andMap(79-101)
examples/with-workflow/src/index.ts (3)
packages/core/src/index.ts (2)
createWorkflowChain(3-3)andThen(6-6)packages/core/src/workflow/chain.ts (2)
createWorkflowChain(906-919)andThen(363-369)packages/core/src/workflow/steps/and-then.ts (1)
andThen(31-85)
packages/core/src/workflow/steps/types.ts (2)
packages/core/src/workflow/internal/types.ts (4)
InternalWorkflowStepConfig(68-82)InternalWorkflowFunc(64-66)InternalBaseWorkflowStep(88-129)InternalAnyWorkflowStep(135-143)packages/core/src/workflow/types.ts (1)
Workflow(379-466)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Build (Node 22)
- GitHub Check: Build (Node 24)
- GitHub Check: Build (Node 20)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (44)
packages/core/src/test-utils/mocks/workflows.ts (1)
36-39: LGTM! Good addition for testing stream capabilities.The mock writer follows the same pattern as the existing logger mock and provides appropriate test doubles for the
writeandpipeFrommethods. This enables testing of streaming functionality in the new workflow steps.packages/core/src/workflow/steps/helpers.ts (1)
15-25: LGTM! Pattern matching correctly extended for new step types.The
matchStepfunction now handles all the new workflow control steps introduced in this PR: tap, workflow, sleep, sleep-until, foreach, loop, branch, and map. The implementation follows the existing pattern consistently.packages/core/src/workflow/internal/utils.ts (1)
70-76: Type widening is safe at the current call site.The function parameter now accepts plain
DATAinstead ofInternalExtractWorkflowInputData<DATA>. While this technically widens the accepted input types, there is only one call site (core.ts:1362) and it bindsDATAtotypeof stateManager.state.data, which is typed asDangerouslyAllowAny. The type narrowing provided byInternalExtractWorkflowInputData<DATA>has no practical effect when applied toDangerouslyAllowAny, so the removal does not compromise type safety.packages/core/src/workflow/internal/types.ts (1)
41-42: The type change is correct and poses no issues.WorkflowInput<INPUT_SCHEMA>andInternalExtractWorkflowInputData<T>have identical conditional logic, making the simplification fromInternalExtractWorkflowInputData<DATA>toDATAsemantically equivalent. All step implementations already work correctly—they receive properly typed DATA from WorkflowInput in the first step and maintain correct type inference through the step chain (DATA → RESULT → next step's DATA). Input validation occurs at the workflow.run level and is unaffected by the context type change. Existing test cases confirm proper type flow (e.g.,ctx.data.count).website/recipes/workflows.md (1)
78-93: LGTM! Comprehensive workflow methods table.The expanded table clearly documents all available workflow control methods with concise descriptions. The formatting is consistent and the new methods (branch, foreach, loop, map, sleep) are well-integrated with existing ones.
website/docs/workflows/steps/and-foreach.md (1)
1-39: LGTM! Clear and complete documentation.The documentation effectively explains the andForEach step with:
- A practical quick-start example
- Complete function signature with required and optional parameters
- Important behavioral notes about array input, order preservation, and concurrency
website/docs/workflows/steps/and-sleep-until.md (1)
1-39: LGTM! Well-documented sleep-until step.The documentation provides:
- A clear quick-start example with a future date
- Complete function signature showing both static Date and dynamic function options
- Important notes about data passthrough and past-date behavior
website/docs/workflows/steps/and-sleep.md (1)
1-39: LGTM! Well-structured documentation.The documentation is clear, concise, and complete. The Quick Start example demonstrates basic usage, and the function signature accurately reflects both static and computed duration options. The notes appropriately document signal handling behavior.
examples/with-workflow/src/index.ts (4)
2-2: LGTM! Required import for new workflow examples.The
andThenimport is correctly added to support the new workflow examples that use it as a step factory withinandForEach,andDoWhile,andDoUntil, andandBranch.
382-385: Verify date calculation doesn't target past time.The
andSleepUntildate is computed asDate.now() + 1000, which could potentially be in the past if the precedingandSleepstep takes longer than expected or if there's any delay in execution. While this is an example/demo, it could mislead users about proper usage patterns.Consider using a relative future time that accounts for the previous sleep duration, or document this limitation in comments:
.andSleepUntil({ id: "align-to-next-second", // Ensure we're always targeting a future time date: () => new Date(Date.now() + 1500), })
399-433: LGTM! Well-designed example demonstrating multiple control steps.Example 6 effectively demonstrates
andForEachwith concurrency control andandMapwith multiple source types. The defensiveArray.isArraychecks in thefnmappings (lines 426, 430) are good practice.
525-527: LGTM! New workflows correctly registered.The three new example workflows are properly registered with the VoltAgent instance alongside existing workflows.
website/docs/workflows/steps/and-map.md (1)
1-53: LGTM! Comprehensive and clear documentation.The documentation effectively demonstrates all mapping source types (value, data, input, context, step, fn) with a practical example. The function signature is complete, and the notes clarify the distinction between
dataandinputsources.packages/core/src/workflow/steps/and-loop.spec.ts (1)
1-5: LGTM! Test setup is clean and well-organized.The imports and test structure follow project conventions appropriately.
packages/core/src/workflow/context.ts (1)
92-105: LGTM! Step type union correctly expanded.The
stepTypeunion has been properly extended to include all new workflow control steps introduced in this PR:sleep,sleep-until,foreach,loop,branch,map, plustapandworkflowfor step composition. This aligns with the new step implementations and maintains type safety across the workflow system.packages/core/src/workflow/steps/and-map.spec.ts (1)
1-4: LGTM! Clean test imports.The test imports are well-organized and follow project conventions.
packages/core/src/index.ts (1)
11-17: LGTM! All new workflow control steps properly exported.The public API surface has been correctly extended with all seven new workflow control steps:
- Timing control:
andSleep,andSleepUntil- Iteration:
andForEach,andDoWhile,andDoUntil- Branching:
andBranch- Data transformation:
andMapThe exports follow the existing pattern and are appropriately placed within the workflow exports block.
packages/core/src/workflow/index.ts (1)
1-16: Exports look consistent with the new step surface.No concerns on the re-export wiring.
packages/core/src/workflow/steps/and-branch.ts (1)
20-66: VerifyworkflowContextclearing +parentStepIdassumptions in branch execution.
subState.workflowContext = undefinedmeans inner steps won’t see workflowContext (incl. any nested tracing), even though you wrap execution withtraceContext.withSpan(...). Please confirm this is intentional and doesn’t break other workflowContext consumers.parentStepId: config.id— ifidcan be absent, ensurecreateStepSpantolerates it (or fallback tofinalStep.id/ a generated parent id).packages/core/src/utils/node-utils.ts (1)
68-81: New step type mappings look complete.The
WorkflowStepTypeunion andgetWorkflowStepNodeTypeswitch cover the newly introduced step kinds.Also applies to: 132-163
.changeset/social-humans-hammer.md (1)
1-6: [No action needed—changeset bump level is correct per project convention.]Your project consistently marks feature additions as
patchreleases, as evidenced by recent releases (v2.0.7, v2.0.4, v2.0.2, v2.0.1) in CHANGELOG.md. Thepatchdesignation for the new workflow control steps follows your established versioning practice.packages/core/src/workflow/types.ts (2)
490-503: LGTM!The expanded
stepTypeunion inBaseWorkflowStepHistoryEntrycorrectly includes all new workflow control step types and is consistent with the corresponding union inWorkflowStreamEvent.
642-648: LGTM!The
stepTypeunion inWorkflowStreamEventis properly aligned with theBaseWorkflowStepHistoryEntrytype, ensuring consistency across the workflow system.packages/core/src/workflow/steps/and-sleep-until.ts (1)
17-36: LGTM!The implementation correctly:
- Supports both static
Dateand dynamic function-based date resolution- Validates the target date with proper
instanceof DateandNaNchecks- Handles past dates gracefully (negative
delayMsis clamped to 0 bywaitWithSignal)- Respects workflow abort signals for interruptible sleep
packages/core/src/workflow/steps/and-loop.ts (2)
9-92: LGTM!The loop implementation is well-structured:
- Proper abort signal checks before and after each iteration ensure responsive cancellation
- Per-iteration OpenTelemetry spans provide good observability
- The
subStatecorrectly isolates nested tracing context- The condition evaluation logic at line 84 correctly implements:
- do-while: executes at least once, continues while condition is
true- do-until: executes at least once, continues until condition becomes
true
97-110: LGTM!Clean public API for both
andDoWhileandandDoUntilthat delegates to the sharedcreateLoopStephelper.packages/core/src/workflow/steps/and-foreach.ts (1)
10-111: LGTM!Well-implemented forEach step with:
- Proper input validation ensuring array data
- Efficient short-circuit for empty arrays
- Robust concurrency normalization handling edge cases (
NaN,Infinity)- Safe concurrent execution pattern - the index claim (
const index = nextIndex; nextIndex += 1;) happens synchronously beforeawait, preventing race conditions- Per-item tracing spans for observability
- Results correctly ordered by input index regardless of completion order
packages/core/src/workflow/steps/index.ts (2)
7-12: LGTM!New step implementations are properly exported, maintaining consistency with the existing export structure.
26-33: LGTM!Associated type exports are complete, enabling consumers to properly type their workflow configurations.
packages/core/src/workflow/core.ts (4)
2164-2189: LGTM!The
SerializedWorkflowStepinterface is properly extended with all necessary fields for the new step types, providing a complete serialization schema.
2270-2298: LGTM!Serialization for
sleepandsleep-untilsteps correctly handles both static values and dynamic functions, converting functions to strings for storage/inspection.
2300-2334: LGTM!Serialization for
foreachandloopsteps correctly captures nested step structure, concurrency settings, condition functions, and loop type metadata.
2336-2383: LGTM!Good implementation:
- Branch serialization correctly captures all branch conditions as function strings
- Map serialization properly uses
safeStringifyper coding guidelines- Workflow step serialization correctly propagates the nested
workflowIdpackages/core/src/workflow/chain.ts (3)
17-42: LGTM!Import section is properly updated with all necessary type imports for the new step configurations and builders.
549-567: LGTM!
andSleepandandSleepUntilchain methods correctly preserveCURRENT_DATAas the output type, since sleep operations don't transform the workflow data.
569-661: LGTM!The new chain builder methods are well-designed:
andBranch: ReturnsArray<NEW_DATA | undefined>correctly modeling that not all branches may executeandForEach: ReturnsNEW_DATA[]for the array of transformed itemsandDoWhile/andDoUntil: ReturnNEW_DATAas the final loop resultandMap: ReturnsWorkflowStepMapResult<MAP>providing strongly-typed key-value resultsThe type casts via
as unknown as WorkflowChain<...>are an acceptable pattern for fluent generic APIs in TypeScript.packages/core/src/workflow/steps/types.ts (8)
14-27: LGTM!The
WorkflowStepTypeunion correctly includes all new step type discriminants following the existing kebab-case naming convention.
128-136: LGTM!Sleep step correctly implements pass-through semantics (returning
DATA) and supports both static and dynamic duration values.
138-146: LGTM!SleepUntil step is consistent with the Sleep step pattern and provides flexible date-based scheduling.
148-158: LGTM!ForEach step correctly models array transformation with
ITEM[] → RESULT[]semantics. The optionalconcurrencyparameter provides useful parallel execution control.
160-171: LGTM!Loop step correctly supports both do-while and do-until semantics via the
loopTypediscriminant. The condition function appropriately receivesRESULTfor post-iteration evaluation.
189-224: LGTM!The map step types are well-designed with comprehensive entry sources and appropriate type inference. The conditional type
WorkflowStepMapEntryResultcorrectly infersVALUEfor static values and return types for functions, falling back tounknownfor path-based access where static typing isn't possible.
241-247: LGTM!The union correctly includes all new step types. The use of
anyforWorkflowStepForEachtype parameters is a pragmatic choice for union flexibility, avoiding complex variance issues while maintaining runtime type discrimination via thetypeproperty.
252-264: All usages ofInternalWorkflow.runin the codebase are already updated and consistent with theDATAinput type. The change from acceptingINPUTtoDATAis intentional—the_INPUTparameter (marked unused) indicates the internal API deliberately uses the current state type rather than the original input type. This is not a breaking change, and all call sites properly pass DATA-typed values.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @website/docs/workflows/steps/and-all.md:
- Line 65: Add a new "Retries" section to
website/docs/workflows/steps/and-all.md documenting the retries?: number
parameter: show a short example using retries: 2 and demonstrating how
retryCount is incremented on each thrown error, state that retries only apply to
thrown errors (suspend/cancel do not trigger retries), and explain interaction
with workflow-wide retryConfig by stating that per-step retries overrides the
workflow retryConfig for that step and how they combine/behave (e.g., per-step
value wins, default falls back to retryConfig if undefined). Ensure the section
follows the style of and-then.md (example + bullet points covering behavior and
override semantics).
🧹 Nitpick comments (1)
website/docs/workflows/steps/and-race.md (1)
68-75: Consider adding an example demonstrating theretriesfield.The
retriesparameter is documented in the function signature but not shown in any example. Adding a simple code snippet showing how to use retries would help users understand this feature.Optional: Example demonstrating retries usage
.andRace({ id: "race-with-retries", steps: [ andThen({ id: "cache", execute: async () => { /* ... */ } }), andThen({ id: "database", execute: async () => { /* ... */ } }), ], retries: 2 // Retry the entire race up to 2 times if all steps fail })
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.changeset/social-humans-hammer.mdpackages/core/src/workflow/core.tspackages/core/src/workflow/index.tspackages/core/src/workflow/internal/types.tspackages/core/src/workflow/internal/utils.tspackages/core/src/workflow/types.tswebsite/docs/workflows/execute-api.mdwebsite/docs/workflows/overview.mdwebsite/docs/workflows/steps/and-all.mdwebsite/docs/workflows/steps/and-branch.mdwebsite/docs/workflows/steps/and-foreach.mdwebsite/docs/workflows/steps/and-loop.mdwebsite/docs/workflows/steps/and-map.mdwebsite/docs/workflows/steps/and-race.mdwebsite/docs/workflows/steps/and-sleep-until.mdwebsite/docs/workflows/steps/and-sleep.mdwebsite/docs/workflows/steps/and-then.mdwebsite/docs/workflows/steps/and-when.md
✅ Files skipped from review due to trivial changes (1)
- website/docs/workflows/steps/and-sleep-until.md
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/core/src/workflow/internal/types.ts
- website/docs/workflows/steps/and-loop.md
- packages/core/src/workflow/index.ts
- website/docs/workflows/steps/and-branch.md
- packages/core/src/workflow/internal/utils.ts
- website/docs/workflows/steps/and-sleep.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/workflow/types.tspackages/core/src/workflow/core.ts
🧬 Code graph analysis (1)
packages/core/src/workflow/core.ts (5)
packages/core/src/workflow/types.ts (2)
WorkflowExecutionResult(136-158)WorkflowSuspensionMetadata(16-34)packages/core/src/workflow/index.ts (2)
WorkflowSuspensionMetadata(29-29)serializeWorkflowStep(17-17)packages/core/src/workflow/stream.ts (1)
NoOpWorkflowStreamWriter(91-109)packages/core/src/workflow/internal/utils.ts (1)
createStepExecutionContext(70-88)packages/core/src/workflow/steps/signal.ts (1)
waitWithSignal(26-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Test core
- GitHub Check: Test libsql
- GitHub Check: Test supabase
- GitHub Check: Build (Node 22)
- GitHub Check: Build (Node 24)
- GitHub Check: Build (Node 20)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (26)
website/docs/workflows/steps/and-race.md (1)
16-47: ✓ Documentation accurately reflects the new object-based API.All examples consistently use the updated signature with
id,steps, and optional fields. The function signature block clearly documents the new shape. Code examples are well-structured, realistic, and syntactically correct.Also applies to: 68-75, 84-104, 112-131, 139-161, 169-192, 201-208, 217-224, 230-230, 246-253
website/docs/workflows/steps/and-then.md (2)
45-50: LGTM!The documentation clearly describes the
retryCountparameter and its behavior.
53-70: LGTM!The Retries section provides clear documentation with practical examples and important behavioral notes.
packages/core/src/workflow/types.ts (3)
202-213: LGTM!The
WorkflowRetryConfiginterface is well-defined with clear documentation and sensible defaults.
263-266: LGTM!The
retryConfigfield is consistently added across all relevant interfaces with proper documentation.Also applies to: 391-394, 447-450, 465-465
516-529: LGTM!The
stepTypeunions are consistently expanded to include all new workflow control steps across bothBaseWorkflowStepHistoryEntryandWorkflowStreamEvent.Also applies to: 661-674
packages/core/src/workflow/core.ts (8)
22-22: LGTM!The import of
waitWithSignaland destructuring ofretryConfigare appropriate for the retry feature implementation.Also applies to: 633-633
843-843: LGTM!The retry configuration calculations are robust with proper validation, type checking, and fallback handling. The precedence logic (execution options > workflow config) is correct.
Also applies to: 1008-1014, 1027-1029
1270-1275: LGTM!The span attributes are properly constructed with conditional inclusion of retry-related metadata for observability.
1346-1439: LGTM!The
handleStepSuspensionfunction properly handles all aspects of step suspension: span ending, metadata creation, event emission, trace recording, observability flushing, and state persistence.
1441-1678: LGTM!The retry loop implementation is comprehensive and handles all edge cases properly:
- Each retry attempt gets proper observability tracking
- Suspension and cancellation are detected and handled during both execution and retry delays
- The
waitWithSignalintegration ensures delays respect abort signals- Error types are distinguished (cancellation vs. suspension vs. retriable errors)
- The retry count is properly tracked and passed to execution context
2296-2309: LGTM!The
SerializedWorkflowStepinterface is properly extended with optional fields for all new step types while maintaining backward compatibility.
2317-2392: LGTM!The base step serialization properly includes the
retriesfield, and existing step type serialization is preserved.
2394-2507: LGTM!All new step types are properly serialized with appropriate handling of:
- Static values vs. functions (serialized via
.toString())- Nested steps (recursively serialized)
- Conditional inclusion of optional fields
- Type-safe discriminators for different variants
The serialization logic is consistent and comprehensive across all new step types.
website/docs/workflows/steps/and-map.md (1)
1-54: LGTM!The documentation for
andMapis comprehensive and clear:
- Practical Quick Start example demonstrating all major source types
- Complete function signature with all supported sources
- Helpful notes clarifying the difference between data, input, and context
website/docs/workflows/steps/and-foreach.md (1)
1-40: LGTM!The documentation for
andForEachis clear and complete:
- Simple Quick Start example showing array iteration
- Complete function signature with all parameters
- Important behavioral notes about array input, order preservation, and concurrency
website/docs/workflows/steps/and-when.md (1)
49-50: Documentation addition looks good.The
retries?: numberparameter is correctly positioned in the Function Signature section and logically fits with the other configuration options. Consistent with the broader retry feature additions across the PR.website/docs/workflows/overview.md (1)
407-428: Well-documented retry feature, but verify for duplication.The new "Workflow Retry Policies" section is clearly written with a good example demonstrating workflow-level config, per-step override, and runtime override. However, the AI-generated summary indicates this section may appear twice in the file, duplicating the same guidance. Please verify there's no unintended duplication elsewhere in the file that wasn't captured in the provided context.
website/docs/workflows/execute-api.md (1)
12-12: Comprehensive retryCount documentation.The addition of
retryCountto the execute context is well-documented. The Quick Start example correctly includes it in the destructuring, the new dedicated section clearly explains its behavior across both per-step and workflow-level retry scenarios, and the TypeScript interface is correctly updated as an optional property. The examples effectively demonstrate real-world usage patterns.Also applies to: 150-178, 431-431
.changeset/social-humans-hammer.md (7)
1-3: Changeset format is correct.The YAML frontmatter properly specifies the package (@voltagent/core) and patch-level version bump for the changelog.
7-20: All imports are consumed.Each of the 9 imported functions (createWorkflowChain, andThen, andBranch, andForEach, andDoWhile, andDoUntil, andMap, andSleep, andSleepUntil, z) is demonstrated in the examples below. No unused imports.
24-46: Branching example is clear and correct.The example properly demonstrates conditional branching with multiple branches evaluated against input data, with each branch returning transformed data.
52-79: Verify data type consistency in chained forEach→DoWhile example.The input is defined as
z.array(z.number())(line 54). AfterandForEach(lines 56–63), the result should be an array of doubled numbers. However, the subsequentandDoWhilecondition (line 70) checksdata < 3, treatingdataas a number rather than an array.This suggests either a type mismatch in the example or an undocumented data transformation behavior. Please verify that this example accurately reflects the actual API behavior.
84-101: Data shaping example is well-structured.The
andMapexample clearly demonstrates composing output from multiple sources (data, step results, context, constant values), with explicit path and source specifications.
106-122: Sleep steps example is correct.Both
andSleep(duration-based) andandSleepUntil(date-based) are demonstrated with proper parameters, chaining naturally into a resuming step.
127-140: Workflow-level retry configuration example is clear.The example properly shows defining
retryConfigat the workflow level and overriding it per-step (withretries: 0), demonstrating flexible retry control.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 issues found across 18 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/core/src/workflow/core.ts">
<violation number="1" location="packages/core/src/workflow/core.ts:1604">
P2: Duplicate span creation code in retry delay error handling. The same interruptionSpan creation logic is repeated three times for different error types (cancellation, suspension, other). Consider extracting this into a helper function or creating the span once before the error type checks.</violation>
</file>
<file name="website/docs/workflows/steps/and-then.md">
<violation number="1" location="website/docs/workflows/steps/and-then.md:49">
P2: Ambiguous retry documentation: clarify whether 'retries: 2' means 2 retry attempts (3 total) or 2 max attempts (1 retry). Also clarify if retryCount=0 is the initial attempt or first retry.</violation>
<violation number="2" location="website/docs/workflows/steps/and-then.md:60">
P2: Inconsistent retry count explanation: comment says 'increments per retry' but earlier states 0 is the 'first attempt' (not retry). Consider clarifying: 'retryCount=0 for initial attempt, increments by 1 for each retry attempt'.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4 issues found across 11 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/core/src/workflow/types.ts">
<violation number="1" location="packages/core/src/workflow/types.ts:333">
P1: Type `Error | null` for error field is inconsistent with existing error handling patterns and overly restrictive. Use `unknown` instead to match WorkflowExecutionResult and handle non-Error thrown values.</violation>
</file>
<file name="packages/core/src/workflow/chain.ts">
<violation number="1" location="packages/core/src/workflow/chain.ts:212">
P1: Breaking change: `getStepData` return type now includes nullable `output` field and additional required `status` field. Code expecting `output` to always be present may encounter null reference errors.</violation>
</file>
<file name="website/docs/workflows/overview.md">
<violation number="1" location="website/docs/workflows/overview.md:546">
P2: The description "After each individual step completes" is ambiguous. Based on the detailed hooks documentation, onStepEnd only runs when a step succeeds, not when it fails. Consider clarifying to "After each individual step completes successfully" or "After each individual step succeeds" to match the hooks.md documentation.</violation>
</file>
<file name="packages/core/src/workflow/core.ts">
<violation number="1" location="packages/core/src/workflow/core.ts:1035">
P1: Hook calls in `runTerminalHooks` lack error handling, which can mask original errors or prevent cleanup. Wrap hook invocations in try-catch blocks to ensure workflow state remains consistent even if user hooks fail.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
packages/core/src/workflow/types.ts
Outdated
| /** | ||
| * Error from the workflow execution, if any | ||
| */ | ||
| error: Error | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Type Error | null for error field is inconsistent with existing error handling patterns and overly restrictive. Use unknown instead to match WorkflowExecutionResult and handle non-Error thrown values.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/workflow/types.ts, line 333:
<comment>Type `Error | null` for error field is inconsistent with existing error handling patterns and overly restrictive. Use `unknown` instead to match WorkflowExecutionResult and handle non-Error thrown values.</comment>
<file context>
@@ -297,6 +297,54 @@ export interface WorkflowResumeOptions {
+ /**
+ * Error from the workflow execution, if any
+ */
+ error: Error | null;
+ /**
+ * Suspension metadata when status is suspended
</file context>
| data: z.infer<IS>; | ||
| state: WorkflowStepState<WorkflowInput<INPUT_SCHEMA>>; | ||
| getStepData: (stepId: string) => { input: any; output: any } | undefined; | ||
| getStepData: (stepId: string) => WorkflowStepData | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Breaking change: getStepData return type now includes nullable output field and additional required status field. Code expecting output to always be present may encounter null reference errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/workflow/chain.ts, line 212:
<comment>Breaking change: `getStepData` return type now includes nullable `output` field and additional required `status` field. Code expecting `output` to always be present may encounter null reference errors.</comment>
<file context>
@@ -208,7 +209,7 @@ export class WorkflowChain<
data: z.infer<IS>;
state: WorkflowStepState<WorkflowInput<INPUT_SCHEMA>>;
- getStepData: (stepId: string) => { input: any; output: any } | undefined;
+ getStepData: (stepId: string) => WorkflowStepData | undefined;
suspend: (
reason?: string,
</file context>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
website/docs/workflows/hooks.md (1)
11-44: Docs update matches the new hook model; fix the missing fenced-code language (MD040).Proposed fix
-``` +```text 1. onStart ... 5. onFinish</details> Also applies to: 118-138, 209-246 </blockquote></details> <details> <summary>packages/core/src/workflow/core.ts (3)</summary><blockquote> `2288-2339`: **Critical: `executeWithSignalCheck` leaks `setInterval` timers (and listeners) on normal completion.** `Promise.race([fn(), abortPromise])` returns when `fn()` resolves, but `abortPromise` keeps the interval running forever. <details> <summary>Proposed fix (ensure cleanup on resolve/reject/abort)</summary> ```diff async function executeWithSignalCheck<T>( fn: () => Promise<T>, signal?: AbortSignal, checkInterval = 100, // Check signal every 100ms ): Promise<T> { if (!signal) { // No signal provided, just execute normally return await fn(); } - // Create a promise that rejects when signal is aborted - const abortPromise = new Promise<never>((_, reject) => { + let intervalId: ReturnType<typeof setInterval> | undefined; + let onAbort: (() => void) | undefined; + + const cleanup = () => { + if (intervalId) clearInterval(intervalId); + intervalId = undefined; + if (onAbort) signal.removeEventListener("abort", onAbort); + onAbort = undefined; + }; + + // Create a promise that rejects when signal is aborted + const abortPromise = new Promise<never>((_, reject) => { const getAbortError = () => { const reason = (signal as AbortSignal & { reason?: unknown }).reason; if (reason && typeof reason === "object" && reason !== null && "type" in reason) { const typedReason = reason as { type?: string }; if (typedReason.type === "cancelled") { return new Error("WORKFLOW_CANCELLED"); } } if (reason === "cancelled") { return new Error("WORKFLOW_CANCELLED"); } return new Error("WORKFLOW_SUSPENDED"); }; const checkSignal = () => { if (signal.aborted) { reject(getAbortError()); } }; // Check immediately checkSignal(); // Set up periodic checking - const intervalId = setInterval(checkSignal, checkInterval); + intervalId = setInterval(checkSignal, checkInterval); // Clean up on signal abort - signal.addEventListener( - "abort", - () => { - clearInterval(intervalId); - reject(getAbortError()); - }, - { once: true }, - ); + onAbort = () => { + cleanup(); + reject(getAbortError()); + }; + signal.addEventListener("abort", onAbort, { once: true }); }); - // Race between the actual function and abort signal - return Promise.race([fn(), abortPromise]); + // Race between the actual function and abort signal + try { + return await Promise.race([fn().finally(cleanup), abortPromise]); + } finally { + cleanup(); + } }
1185-1307: Major: suspension via pre-step abort signal doesn’t run terminal hooks (onSuspend/onFinish).
In the “signal is aborted and not cancelled” branch, you build suspension state + persist it, but return without callingrunTerminalHooks("suspended", { includeEnd: false }). This makes suspension behavior depend on how suspension happens (step-triggered vs externally signaled).
2354-2379: Security/privacy concern: serializing user functions via.toString()can leak source code/secrets.
This now includes more surfaces (sleep/map/loop/branch condition functions). IfserializeWorkflowStepis exposed in APIs/UI, consider redacting by default or gating behind an explicit “includeFunctionSource” option.Also applies to: 2414-2576
🤖 Fix all issues with AI agents
In @packages/core/src/workflow/core.ts:
- Around line 1562-1623: The emitted step-complete event currently hardcodes
status: "success" even when isSkipped is true; update the emitAndCollectEvent
call (the step-complete payload built near emitAndCollectEvent({...}) in the try
block) to set status: isSkipped ? "skipped" : "success" (and keep other fields
like output/result, stepIndex, stepType the same) so the event matches the
stepData.status and span state.
🧹 Nitpick comments (5)
packages/core/src/workflow/hooks.spec.ts (1)
9-13: Avoid reaching intoWorkflowRegistryinternals viaas anyfor test isolation.Clearing
(registry as any).workflowsworks, but it’s brittle. Prefer a test-only reset API (e.g.,WorkflowRegistry.__resetForTests()), or expose a minimalclear()on the registry.website/docs/workflows/hooks.md (1)
22-27: Consider printinginfo.error?.messagein examples to avoid noisy object dumps.
(Current snippets interpolateinfo.errordirectly.)Also applies to: 161-167, 202-205
packages/core/src/workflow/core.ts (1)
1010-1051: Consider isolating hook failures from workflow outcome (especially terminal hooks).
Right now, any thrown error inonSuspend/onError/onFinish/onEndcan flip a completed/suspended/cancelled workflow into the outercatchand be persisted as"error". If hooks are meant to be “observer-only”, wrap each hook withtry/catchand log instead.packages/core/src/workflow/chain.ts (1)
550-662: New chain methods are straightforward and keep data-shape typing readable.
Minor: consider returningReadonlyArray<...>forandBranchresults if you want to signal immutability at the API boundary.packages/core/src/workflow/types.ts (1)
202-267: Type surfaces for retry + terminal hooks look coherent and match the new docs/tests.
One nit: clarify in JSDoc whetherattemptsmeans “number of retries” vs “total attempts”.Also applies to: 300-395
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
.changeset/social-humans-hammer.mdpackages/core/src/workflow/chain.tspackages/core/src/workflow/context.tspackages/core/src/workflow/core.tspackages/core/src/workflow/hooks.spec.tspackages/core/src/workflow/index.tspackages/core/src/workflow/internal/types.tspackages/core/src/workflow/steps/and-map.spec.tspackages/core/src/workflow/types.tswebsite/docs/workflows/hooks.mdwebsite/docs/workflows/overview.md
✅ Files skipped from review due to trivial changes (1)
- .changeset/social-humans-hammer.md
🚧 Files skipped from review as they are similar to previous changes (3)
- website/docs/workflows/overview.md
- packages/core/src/workflow/steps/and-map.spec.ts
- packages/core/src/workflow/internal/types.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/workflow/context.tspackages/core/src/workflow/chain.tspackages/core/src/workflow/index.tspackages/core/src/workflow/core.tspackages/core/src/workflow/hooks.spec.tspackages/core/src/workflow/types.ts
🧠 Learnings (1)
📚 Learning: 2026-01-07T05:09:23.217Z
Learnt from: CR
Repo: VoltAgent/voltagent PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T05:09:23.217Z
Learning: Applies to **/*.test.ts : Test your changes - ensure all tests pass before committing
Applied to files:
packages/core/src/workflow/hooks.spec.ts
🧬 Code graph analysis (1)
packages/core/src/workflow/chain.ts (4)
packages/core/src/workflow/index.ts (2)
WorkflowStepData(34-34)andSleep(9-9)packages/core/src/workflow/types.ts (1)
WorkflowStepData(310-315)packages/core/src/workflow/steps/types.ts (2)
WorkflowStepSleepConfig(128-130)WorkflowStepSleepUntilConfig(138-140)packages/core/src/workflow/steps/and-sleep.ts (1)
andSleep(17-31)
🪛 markdownlint-cli2 (0.18.1)
website/docs/workflows/hooks.md
238-238: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Test core
- GitHub Check: Test cli
- GitHub Check: Build (Node 24)
- GitHub Check: Build (Node 22)
- GitHub Check: Build (Node 20)
🔇 Additional comments (9)
packages/core/src/workflow/hooks.spec.ts (3)
15-70: Nice coverage of “skipped” snapshots and dual hook surfaces (onFinishvsonEnd).
72-112: Good assertion of error hook ordering and step error snapshot shape.
113-153: Good contract test: suspension triggersonSuspend/onFinishand skipsonEnd.packages/core/src/workflow/index.ts (1)
1-16: Additive re-exports look fine; please sanity-check for name collisions and missing exports.Run a quick typecheck/build on the PR branch to ensure
./stepsand./typesactually export all listed names (and no duplicate export name conflicts occur).Also applies to: 23-37
packages/core/src/workflow/context.ts (2)
5-5: Good:stepDatanow uses the sharedWorkflowStepDatatype.Also applies to: 57-57
92-105: VerifystepTypeliteral strings match actual runtimestep.typevalues everywhere.
A mismatch here tends to silently break tracing/UI filters.Use a quick search/typecheck to confirm the canonical set of step type strings is consistent across
packages/core/src/workflow/steps/*,packages/core/src/utils/node-utils.ts, and stream/history typing.packages/core/src/workflow/core.ts (1)
22-22: Retry delay useswaitWithSignal—good choice (abort-aware + noJSON.stringify).Also applies to: 1660-1734
packages/core/src/workflow/chain.ts (1)
199-370: Good:getStepDatanow reflects richer step snapshots (status/error), improving type safety.Also applies to: 377-470, 477-548
packages/core/src/workflow/types.ts (1)
584-609: Good: step type unions updated across history and streaming to include new step kinds.Also applies to: 684-753
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…t/voltagent into feat/add-workflow-control-steps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
packages/core/src/workflow/steps/and-guardrail.spec.ts (1)
31-53: Consider adding edge case coverage.While the current tests cover the happy paths, consider adding tests for:
- Guardrails that block execution (pass: false)
- Multiple guardrails applied in sequence
- Error handling when guardrail handler throws
These would provide more comprehensive coverage of the andGuardrail behavior.
Based on learnings, ensure all tests pass before committing.
examples/with-workflow/src/index.ts (1)
535-551: Clarify the guardrail application order.The
andGuardrailstep at line 544-547 appliesoutputGuardrails: [redactNumbers], but the workflow also hasoutputGuardrails: [redactNumbers]at the workflow level (line 542). This means numbers will be redacted twice (once by the step guardrail, once by the workflow guardrail), which for this particular guardrail is idempotent but could be confusing as an example.Consider either removing the duplicate or adding a comment explaining the intentional double-application for demonstration purposes.
packages/core/src/workflow/internal/guardrails.ts (1)
106-107: Consider strengthening the type guard validation.The type guard checks if
valueis a string or array, but doesn't validate that arrays contain valid message types (UIMessage[]orBaseMessage[]). This could allow invalid array contents through at runtime.However, since this is a type guard primarily for TypeScript narrowing and the actual guardrail functions will validate the content, this may be acceptable for the current use case.
♻️ Optional: Add runtime validation for array contents
-export const isWorkflowGuardrailInput = (value: unknown): value is WorkflowGuardrailInput => - typeof value === "string" || Array.isArray(value); +export const isWorkflowGuardrailInput = (value: unknown): value is WorkflowGuardrailInput => { + if (typeof value === "string") return true; + if (!Array.isArray(value)) return false; + // Arrays are accepted; content validation is deferred to guardrail execution + return true; +};packages/core/src/workflow/core.ts (1)
2628-2648: Verify mapConfig serialization size.The map config serialization uses
safeStringifywhich is good. However, if the map contains many entries with large function bodies, the serialized output could become quite large. Consider if there should be any size limits or truncation for very large configurations.packages/core/src/workflow/steps/types.ts (1)
259-259: Note: WorkflowStepForEach usesanyfor ITEM and RESULT generics in the union.The
WorkflowStepForEach<INPUT, any, any>usesanyfor the ITEM and RESULT type parameters in the union. This is a pragmatic choice to avoid complex variance issues in the union type, but it does weaken type safety at the union level.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
.changeset/social-humans-hammer.mdexamples/with-workflow/src/index.tspackages/core/src/agent/guardrail.tspackages/core/src/agent/types.tspackages/core/src/index.tspackages/core/src/workflow/chain.tspackages/core/src/workflow/context.tspackages/core/src/workflow/core.tspackages/core/src/workflow/guardrails.spec.tspackages/core/src/workflow/index.tspackages/core/src/workflow/internal/guardrails.tspackages/core/src/workflow/open-telemetry/trace-context.tspackages/core/src/workflow/steps/and-guardrail.spec.tspackages/core/src/workflow/steps/and-guardrail.tspackages/core/src/workflow/steps/helpers.tspackages/core/src/workflow/steps/index.tspackages/core/src/workflow/steps/types.tspackages/core/src/workflow/types.tswebsite/docs/workflows/overview.mdwebsite/docs/workflows/steps/and-all.mdwebsite/docs/workflows/steps/and-guardrail.mdwebsite/sidebars.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/core/src/index.ts
- website/sidebars.ts
- .changeset/social-humans-hammer.md
- packages/core/src/workflow/steps/helpers.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/agent/types.tsexamples/with-workflow/src/index.tspackages/core/src/workflow/steps/and-guardrail.tspackages/core/src/agent/guardrail.tspackages/core/src/workflow/index.tspackages/core/src/workflow/open-telemetry/trace-context.tspackages/core/src/workflow/steps/and-guardrail.spec.tspackages/core/src/workflow/guardrails.spec.tspackages/core/src/workflow/context.tspackages/core/src/workflow/steps/index.tspackages/core/src/workflow/internal/guardrails.tspackages/core/src/workflow/core.tspackages/core/src/workflow/chain.tspackages/core/src/workflow/types.tspackages/core/src/workflow/steps/types.ts
🧠 Learnings (1)
📚 Learning: 2026-01-07T05:09:23.217Z
Learnt from: CR
Repo: VoltAgent/voltagent PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T05:09:23.217Z
Learning: Applies to **/*.test.ts : Test your changes - ensure all tests pass before committing
Applied to files:
packages/core/src/workflow/steps/and-guardrail.spec.tspackages/core/src/workflow/guardrails.spec.ts
🧬 Code graph analysis (5)
examples/with-workflow/src/index.ts (6)
packages/core/src/index.ts (4)
createWorkflowChain(3-3)andThen(6-6)createInputGuardrail(78-78)createOutputGuardrail(78-78)packages/core/src/workflow/chain.ts (2)
createWorkflowChain(919-932)andThen(366-372)packages/core/src/workflow/index.ts (2)
createWorkflowChain(20-20)andThen(3-3)packages/core/src/workflow/steps/index.ts (1)
andThen(2-2)packages/core/src/workflow/steps/and-then.ts (1)
andThen(31-85)packages/core/src/agent/guardrail.ts (2)
createInputGuardrail(62-72)createOutputGuardrail(74-89)
packages/core/src/workflow/steps/and-guardrail.spec.ts (4)
packages/core/src/agent/guardrail.ts (2)
createOutputGuardrail(74-89)createInputGuardrail(62-72)packages/core/src/workflow/chain.ts (1)
andGuardrail(555-560)packages/core/src/workflow/steps/and-guardrail.ts (1)
andGuardrail(16-84)packages/core/src/test-utils/mocks/workflows.ts (1)
createMockWorkflowExecuteContext(19-41)
packages/core/src/workflow/context.ts (3)
packages/core/src/workflow/index.ts (1)
WorkflowStepData(35-35)packages/core/src/workflow/types.ts (1)
WorkflowStepData(323-328)packages/core/src/index.ts (1)
Agent(34-34)
packages/core/src/workflow/chain.ts (7)
packages/core/src/workflow/types.ts (2)
WorkflowStepData(323-328)WorkflowInput(410-415)packages/core/src/workflow/steps/and-guardrail.ts (1)
andGuardrail(16-84)packages/core/src/workflow/steps/and-sleep.ts (1)
andSleep(17-31)packages/core/src/workflow/steps/and-sleep-until.ts (1)
andSleepUntil(17-37)packages/core/src/workflow/steps/and-branch.ts (1)
andBranch(10-91)packages/core/src/workflow/steps/and-foreach.ts (1)
andForEach(10-112)packages/core/src/workflow/steps/and-map.ts (1)
andMap(79-101)
packages/core/src/workflow/steps/types.ts (4)
packages/core/src/workflow/internal/types.ts (4)
InternalWorkflowStepConfig(69-87)InternalBaseWorkflowStep(93-138)InternalWorkflowFunc(65-67)InternalAnyWorkflowStep(144-152)packages/core/src/agent/types.ts (2)
InputGuardrail(404-404)OutputGuardrail(418-420)packages/core/src/index.ts (3)
InputGuardrail(176-176)OutputGuardrail(177-177)Workflow(26-26)packages/core/src/workflow/index.ts (1)
Workflow(26-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test core
- GitHub Check: Build (Node 24)
- GitHub Check: Build (Node 22)
- GitHub Check: Build (Node 20)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (49)
website/docs/workflows/overview.md (4)
407-429: LGTM! Clear documentation of retry policies.The retry policy section provides a clear, concise example showing workflow-level defaults and step-level overrides. The documentation correctly explains the inheritance behavior and opt-out mechanism.
430-478: LGTM! Comprehensive guardrails documentation.The guardrails section provides excellent examples and clearly documents the constraint that input guardrails only accept string or message inputs. The code examples demonstrate both workflow-level and step-level guardrail usage effectively.
592-600: LGTM! Hooks table properly expanded.The workflow hooks table has been updated with new lifecycle hooks (onSuspend, onError, onFinish) and improved descriptions. This aligns well with the PR's expansion of workflow lifecycle management.
407-478: No action needed — sections are not duplicated.The search confirms that both "Workflow Retry Policies" (line 407) and "Workflow Guardrails" (line 430) appear only once in the file. The AI summary indicating duplicates was inaccurate.
packages/core/src/workflow/steps/and-guardrail.spec.ts (1)
1-54: LGTM! Clean test coverage for andGuardrail.The test suite covers the core functionality:
- Output guardrails successfully modify data
- Input guardrails successfully modify string data
The tests are well-structured and use appropriate mocking utilities.
packages/core/src/workflow/open-telemetry/trace-context.ts (2)
256-284: LGTM! Well-designed generic child span method.The
createChildSpanmethod provides flexible span creation with:
- Proper attribute inheritance via
commonAttributes- Optional parent span support
- Configurable span kind and attributes
The implementation follows existing patterns in the class and correctly uses
safeStringifyper coding guidelines.
356-362: LGTM! Useful method for setting input after span creation.The
setInputmethod allows setting input on the root span after initialization, which is useful for scenarios where input isn't available at construction time (e.g., guardrail modifications). Correctly usessafeStringifyper coding guidelines.packages/core/src/agent/types.ts (1)
491-496: LGTM! Appropriate type extension for workflow evaluation.Adding "workflow" to the
AgentEvalOperationTypeunion enables evaluation support for workflow operations, aligning with the broader workflow feature additions in this PR. The change is purely additive and non-breaking.packages/core/src/workflow/guardrails.spec.ts (1)
1-86: LGTM! Comprehensive guardrail integration tests.The test suite provides excellent coverage:
- Input guardrails modify data before execution (trim example)
- Output guardrails modify data after execution (redact example)
- Blocking guardrails throw with proper error code
The tests properly use
beforeEachfor registry cleanup, ensuring test isolation. Error assertions usetoMatchObjectto verify the specific error code, which is a good practice.Based on learnings, ensure all tests pass before committing.
packages/core/src/workflow/index.ts (2)
1-17: LGTM!The new workflow step combinators (
andGuardrail,andSleep,andSleepUntil,andForEach,andBranch,andDoWhile,andDoUntil,andMap) are properly re-exported from the steps module, expanding the public API surface appropriately.
24-38: LGTM!The new type exports (
WorkflowHookContext,WorkflowHookStatus,WorkflowRetryConfig,WorkflowStepData,WorkflowStepStatus) are correctly added to the public API, aligning with the PR's lifecycle hook and retry policy enhancements.packages/core/src/agent/guardrail.ts (1)
165-186: LGTM!The addition of the generic type parameter
<TOutput = any>tonormalizeOutputGuardrailListimproves type safety for consumers while maintaining backward compatibility through the defaultanytype. The internal normalization correctly continues to use<any>for the return type.examples/with-workflow/src/index.ts (5)
2-10: LGTM!The expanded imports correctly bring in the new workflow helpers (
andGuardrail,andThen,createInputGuardrail,createOutputGuardrail) needed for the new example workflows.
368-401: LGTM!The Timed Reminder Workflow is a clean example demonstrating
andSleepandandSleepUntil. Good defensive programming withMath.max(0, data.waitMs)to prevent negative durations.
403-441: LGTM!The Batch Transform Workflow effectively demonstrates
andForEachwith concurrency control andandMapfor composing outputs from multiple sources. The use of source types ("input","data","fn") provides a clear example of the mapping capabilities.
443-511: LGTM!The Loop + Branch Workflow correctly demonstrates
andDoWhile,andDoUntil, andandBranch. Theselect-branchstep properly handles the array output fromandBranch(which runs all matching branches and returns aligned results).
573-576: LGTM!The new workflows are correctly registered with the VoltAgent configuration.
website/docs/workflows/steps/and-all.md (1)
65-100: LGTM!The documentation for the new
retriesoption is comprehensive. The example clearly demonstrates howretryCountworks, and the behavioral rules are well-documented (retry on errors only, not suspend/cancel; override semantics; scoped behavior across parallel steps).website/docs/workflows/steps/and-guardrail.md (1)
1-112: LGTM!The documentation for
andGuardrailis well-structured with clear examples covering:
- Basic output guardrail usage
- Input guardrails for string/message data
- Output guardrails for structured data
- The distinction between when to use input vs output guardrails
The note about
guardrailAgentfor guardrails needing agent APIs is helpful.packages/core/src/workflow/steps/and-guardrail.ts (3)
16-26: LGTM!Good design choice to normalize guardrail lists at step creation time rather than execution time, improving runtime efficiency. The generic type parameters properly flow through to the execute context.
33-55: LGTM!The early return for empty guardrails is efficient. The guardrail runtime is properly constructed with all necessary context including workflow IDs, trace context, logger, and the optional
guardrailAgentfor guardrails that need agent APIs.
59-79: LGTM!The input guardrail type check at line 60 with a descriptive error message provides clear guidance when users misconfigure guardrails. The sequential application of input then output guardrails follows the documented behavior.
packages/core/src/workflow/context.ts (4)
3-6: LGTM!The new imports for
AgentandWorkflowStepDatatypes support the expanded context properties added in this PR.
58-58: LGTM!The
stepDatatype update from an inline type toWorkflowStepDatais an improvement, asWorkflowStepDataincludes additional fields (status,error) that provide richer step execution tracking.
79-82: LGTM!The optional
guardrailAgentproperty enables workflows to supply an agent instance for guardrails that require agent APIs or metadata, aligning with the newandGuardrailstep functionality.
97-111: LGTM!The expanded
stepTypeunion comprehensively covers all the new step types introduced in this PR:"tap","workflow","guardrail","sleep","sleep-until","foreach","loop","branch", and"map".packages/core/src/workflow/steps/index.ts (2)
7-13: LGTM!The new step exports are well-organized and follow the existing pattern. All new workflow control step creators (
andGuardrail,andSleep,andSleepUntil,andForEach,andBranch,andDoWhile,andDoUntil,andMap) are properly exported for public consumption.
26-36: LGTM!The type exports are comprehensive and properly aligned with the new step creators. All configuration and result types are correctly exported.
packages/core/src/workflow/internal/guardrails.ts (3)
83-104: LGTM! Good use of Proxy for the stub agent.The Proxy pattern provides clear, actionable error messaging when users attempt to call unsupported agent methods, guiding them to provide a
guardrailAgentin the workflow config or run options.
147-195: LGTM!The
createWorkflowGuardrailRuntimefunction properly assembles the operation context with all required fields including trace context, logger, and abort controller. The deterministicoperationIdgeneration is a good pattern for debugging and tracing.
197-235: LGTM!The
applyWorkflowInputGuardrailsandapplyWorkflowOutputGuardrailsfunctions properly short-circuit when no guardrails are configured and correctly delegate to the underlying guardrail runners.packages/core/src/workflow/chain.ts (4)
552-560: LGTM!The
andGuardrailmethod properly follows the existing fluent API pattern and returnsthissince guardrails don't transform the data type.
562-580: LGTM!The
andSleepandandSleepUntilmethods correctly preserve the current data type since sleep operations don't modify the workflow data.
582-618: LGTM!The
andBranchandandForEachmethods properly transform the data type -andBranchreturnsArray<NEW_DATA | undefined>andandForEachreturnsNEW_DATA[], which correctly reflects the semantics of these operations.
620-674: LGTM!The
andDoWhile,andDoUntil, andandMapmethods follow the established pattern. The loop methods returnNEW_DATA(the result of the last iteration), andandMapreturnsWorkflowStepMapResult<MAP>as expected.packages/core/src/workflow/core.ts (6)
1051-1057: LGTM!The retry configuration normalization properly handles edge cases with
Number.isFinitechecks andMath.max(0, ...)to ensure non-negative values.
1059-1092: LGTM!The
buildHookContextandrunTerminalHookshelpers centralize terminal hook invocation logic. The conditional logic foronEnd(excluding it for suspended status by default) is sensible since suspended workflows may resume.
1552-1568: Verify step data status updates within retry loop.The step data status is reset to "running" at the start of each retry attempt (lines 1554-1558), which is correct. However, ensure that this doesn't conflict with any external observers that might read the step data during execution.
1704-1720: LGTM!The retry logging includes the current attempt count and limit, which is valuable for debugging. The error details are properly captured.
1721-1793: LGTM! Retry delay properly handles signals.The retry delay using
waitWithSignalcorrectly handles both cancellation and suspension signals during the delay period, preventing the workflow from continuing retries if a signal is received.
2546-2648: LGTM!The serialization logic for new step types (sleep, sleep-until, foreach, loop, branch, map, guardrail) properly handles both static values and function references, converting functions to strings for serialization.
packages/core/src/workflow/types.ts (4)
203-214: LGTM!The
WorkflowRetryConfiginterface is well-documented with clear JSDoc comments indicating the default values. The optional nature of both fields provides flexibility.
313-359: LGTM!The new hook-related types provide a comprehensive view of workflow state at terminal points:
WorkflowHookStatuscovers all terminal statesWorkflowStepStatusincludes all possible step statuses including "skipped"WorkflowStepDatacaptures input, output, status, and error for each stepWorkflowHookContextaggregates all this information for hook consumers
381-407: LGTM!The expanded
WorkflowHooksinterface withonSuspend,onError, andonFinishprovides more granular control over lifecycle events. The updatedonEndsignature accepting an optionalWorkflowHookContextmaintains backward compatibility while enabling richer debugging.
627-641: LGTM!The extended
stepTypeunion inBaseWorkflowStepHistoryEntrycorrectly includes all new step types for proper history tracking.packages/core/src/workflow/steps/types.ts (4)
62-72: LGTM!The guardrail step types properly reuse the existing
InputGuardrailandOutputGuardrailtypes from the agent module, maintaining consistency across the codebase.
142-160: LGTM!The sleep step types properly support both static values and dynamic functions for duration/date, providing flexibility for time-based workflow control.
203-238: LGTM!The map step types are well-designed:
WorkflowStepMapEntryprovides a comprehensive union of data sources (value, data, input, step, context, fn)WorkflowStepMapEntryResultuses conditional types to infer the result typeWorkflowStepMapResultmaps over the configuration to produce the output type
267-279: No issues found. The signature change fromInternalExtractWorkflowInputData<DATA>toDATAis compatible with all existing callers. SinceInternalExtractWorkflowInputData<T>resolves toTwhen T is a concrete type, the two signatures are functionally equivalent for actual usage. All callers already passDATAvalues directly toworkflow.run(), and no breaking changes exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 11 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/core/src/workflow/types.ts">
<violation number="1" location="packages/core/src/workflow/types.ts:325">
P2: Breaking change: Changed `output` from required field with null union (`output: Type | null`) to optional field (`output?: Type`). This is a breaking change because code checking `output === null` will now receive `undefined`, and code expecting the field to always be present may fail. While this aligns with implementation usage, it may break existing consumers.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| export type WorkflowStepData = { | ||
| input: DangerouslyAllowAny; | ||
| output?: DangerouslyAllowAny; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Breaking change: Changed output from required field with null union (output: Type | null) to optional field (output?: Type). This is a breaking change because code checking output === null will now receive undefined, and code expecting the field to always be present may fail. While this aligns with implementation usage, it may break existing consumers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/workflow/types.ts, line 325:
<comment>Breaking change: Changed `output` from required field with null union (`output: Type | null`) to optional field (`output?: Type`). This is a breaking change because code checking `output === null` will now receive `undefined`, and code expecting the field to always be present may fail. While this aligns with implementation usage, it may break existing consumers.</comment>
<file context>
@@ -322,7 +322,7 @@ export type WorkflowStepStatus =
export type WorkflowStepData = {
input: DangerouslyAllowAny;
- output: DangerouslyAllowAny | null;
+ output?: DangerouslyAllowAny;
status: WorkflowStepStatus;
error?: Error | null;
</file context>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.changeset/social-humans-hammer.md:
- Around line 131-145: The example workflow references data.userId but never
defines the workflow input, so update the createWorkflowChain call to declare an
input schema (e.g., input: z.object({ userId: z.string() })) or modify the
example to use a clearly defined prior step output; specifically edit the
createWorkflowChain invocation to include an input definition and ensure the
"fetch-user" step (id "fetch-user") reads from that input (data.userId), and
keep the "no-retry-step" behavior unchanged.
🧹 Nitpick comments (10)
packages/core/src/workflow/steps/and-foreach.spec.ts (3)
61-82: Potential flakiness: timer-based ordering assertions
setTimeout-driven delays can be noisy under CI load; consider making completion order deterministic without relying on wall-clock timing (e.g., deferred promises you resolve in a chosen order), while still exercisingconcurrency.
84-110: Concurrency test can false-pass for serial execution; also ensure decrement on failure
expect(maxInFlight).toBeLessThanOrEqual(2)would still pass if the implementation is fully serial (maxInFlight === 1). Also,inFlight -= 1should be in afinallyto avoid leaking state if anything throws.Proposed tightening
- execute: async ({ data }) => { - inFlight += 1; - maxInFlight = Math.max(maxInFlight, inFlight); - await new Promise((resolve) => setTimeout(resolve, 20)); - inFlight -= 1; - return data; - }, + execute: async ({ data }) => { + inFlight += 1; + maxInFlight = Math.max(maxInFlight, inFlight); + try { + await new Promise((resolve) => setTimeout(resolve, 20)); + return data; + } finally { + inFlight -= 1; + } + }, }), }); await step.execute( createMockWorkflowExecuteContext({ data: [1, 2, 3, 4], }), ); expect(maxInFlight).toBeLessThanOrEqual(2); + // Guards against a fully-serial implementation “passing” this test. + expect(maxInFlight).toBeGreaterThan(1);
112-128: Avoidas anyin TS tests; prefer@ts-expect-error/unknownand loosen message matchingUsing
as anysidesteps type-safety unnecessarily; also, matching the full error string can be brittle.Proposed adjustment
await expect( step.execute( createMockWorkflowExecuteContext({ - data: { value: 1 } as any, + // @ts-expect-error - intentionally invalid input + data: { value: 1 }, }), ), - ).rejects.toThrow("andForEach expects array input data"); + ).rejects.toThrow(/andForEach expects array input data/);packages/core/src/workflow/steps/and-map.spec.ts (2)
6-39: Avoidas anyin the mock state to keep tests resilient to type changes.The cast on Line 24 can hide breaking changes in
WorkflowExecuteContext/ state shape; prefer updatingcreateMockWorkflowExecuteContextto accept a typedstate(or expose a helper to build it).
41-62: Use Vitest fake timers to avoid flakiness from realsetTimeout.Line 48 introduces a timing dependency; fake timers (or
vi.waitFor) makes this deterministic and faster..changeset/social-humans-hammer.md (1)
149-170: Consider clarifying hook param shapes in the snippet (esp.info.steps).The example assumes
info.stepsis an object (Line 165); if it’s aMapor different structure, this will mislead—maybe add a short comment or align to the exported type shape.packages/core/src/workflow/steps/and-map.ts (3)
10-27: Validatepathsegments to avoid surprising lookups ("", leading/trailing dots).As-is,
readPath(value, "")will accessvalue[""](Line 15-20). If that’s not intended, reject empty segments early.Proposed fix
const readPath = (value: unknown, path?: string) => { if (path === undefined || path === ".") { return value; } - const parts = path.split("."); + const parts = path.split("."); + if (parts.some((p) => p.length === 0)) { + throw new Error(`Invalid path '${path}'`); + } let current: any = value; for (const part of parts) { if (current && typeof current === "object") { current = current[part]; } else { throw new Error(`Invalid path '${path}'`); } } return current; };
46-74: Improve error detail for unsupported sources (easier debugging).Line 72 throws a generic error; including
entry.source(or using anassertNever) makes misconfigurations easier to diagnose.
79-101: Consider resolving map entries concurrently (optional perf win).Current loop (Line 91-96) awaits sequentially; for many independent entries (esp. multiple
fnsources),Promise.allcan reduce latency while keeping a stable output shape.Possible refactor
execute: async (context) => { const entries = Object.entries(map) as Array<[keyof MAP, MAP[keyof MAP]]>; const result = {} as WorkflowStepMapResult<MAP>; - for (const [key, entry] of entries) { - result[key] = (await resolveMapEntry( - entry, - context, - )) as WorkflowStepMapResult<MAP>[typeof key]; - } + await Promise.all( + entries.map(async ([key, entry]) => { + result[key] = (await resolveMapEntry( + entry, + context, + )) as WorkflowStepMapResult<MAP>[typeof key]; + }), + ); return result; },packages/core/src/workflow/core.ts (1)
1568-1790: Consider extracting retry logic for clarityThe retry loop implementation is functionally correct but has high cognitive complexity:
- Infinite
while (true)loop with internal break- Nested error handling within retry delay (lines 1737-1779)
- Multiple interleaved concerns (execution, retry, cancellation, suspension)
♻️ Optional refactor to improve maintainability
Consider extracting the retry loop into a separate function:
async function executeStepWithRetries<T>( stepExecutor: () => Promise<T>, options: { stepRetryLimit: number; retryDelayMs: number; signal?: AbortSignal; onRetry?: (retryCount: number) => void; } ): Promise<T> { let retryCount = 0; let lastError: unknown; while (retryCount <= options.stepRetryLimit) { try { return await stepExecutor(); } catch (error) { lastError = error; if (shouldPropagateError(error) || retryCount >= options.stepRetryLimit) { throw error; } retryCount++; options.onRetry?.(retryCount); if (options.retryDelayMs > 0) { await waitWithSignal(options.retryDelayMs, options.signal); } } } throw lastError; }This would make the main execution loop cleaner and the retry logic more testable.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
.changeset/social-humans-hammer.mdpackages/core/src/workflow/core.tspackages/core/src/workflow/hooks.spec.tspackages/core/src/workflow/steps/and-branch.spec.tspackages/core/src/workflow/steps/and-foreach.spec.tspackages/core/src/workflow/steps/and-map.spec.tspackages/core/src/workflow/steps/and-map.tspackages/core/src/workflow/steps/and-sleep.spec.tspackages/core/src/workflow/types.tswebsite/docs/workflows/steps/and-branch.mdwebsite/docs/workflows/steps/and-then.md
✅ Files skipped from review due to trivial changes (1)
- website/docs/workflows/steps/and-branch.md
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/core/src/workflow/steps/and-sleep.spec.ts
- packages/core/src/workflow/hooks.spec.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/workflow/steps/and-map.tspackages/core/src/workflow/steps/and-foreach.spec.tspackages/core/src/workflow/steps/and-branch.spec.tspackages/core/src/workflow/steps/and-map.spec.tspackages/core/src/workflow/types.tspackages/core/src/workflow/core.ts
🧠 Learnings (1)
📚 Learning: 2026-01-07T05:09:23.217Z
Learnt from: CR
Repo: VoltAgent/voltagent PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T05:09:23.217Z
Learning: Applies to **/*.test.ts : Test your changes - ensure all tests pass before committing
Applied to files:
packages/core/src/workflow/steps/and-foreach.spec.tspackages/core/src/workflow/steps/and-branch.spec.tspackages/core/src/workflow/steps/and-map.spec.ts
🧬 Code graph analysis (2)
packages/core/src/workflow/steps/and-branch.spec.ts (2)
packages/core/src/workflow/steps/and-branch.ts (1)
andBranch(10-91)packages/core/src/test-utils/mocks/workflows.ts (1)
createMockWorkflowExecuteContext(19-41)
packages/core/src/workflow/types.ts (3)
packages/core/src/workflow/index.ts (5)
WorkflowRetryConfig(29-29)WorkflowHookStatus(28-28)WorkflowStepStatus(36-36)WorkflowStepData(35-35)WorkflowHookContext(27-27)packages/core/src/index.ts (4)
InputGuardrail(176-176)OutputGuardrail(177-177)Agent(34-34)context(111-111)packages/internal/src/types/index.ts (1)
DangerouslyAllowAny(6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Test core
- GitHub Check: Test cli
- GitHub Check: Build (Node 20)
- GitHub Check: Build (Node 22)
- GitHub Check: Build (Node 24)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (23)
packages/core/src/workflow/steps/and-foreach.spec.ts (3)
1-6: Solid baseline coverage + readable structureImports and the overall spec layout are clean and focused on behavior.
7-59: Core behavior tests look good (map/empty/single)These cover the main “happy path” and key edge cases without over-specifying internals.
130-148: Error propagation coverage is goodThis is the right level of assertion to ensure inner step failures surface properly.
website/docs/workflows/steps/and-then.md (2)
45-50: LGTM: Clear parameter documentationThe retryCount parameter is well-documented, with a clear explanation of its zero-based indexing for the initial attempt.
53-72: LGTM: Comprehensive retry documentationThe Retries section clearly explains:
- Retry behavior applies only to thrown errors
- How retryCount increments across attempts
- Relationship between per-step retries and workflow-wide retryConfig
This provides users with a complete understanding of the retry mechanism.
packages/core/src/workflow/steps/and-branch.spec.ts (6)
7-42: LGTM: Comprehensive test for branch index alignmentThis test correctly verifies that andBranch maintains result array alignment with branch definition order, returning undefined for non-matching branches. The test data (value: 5) properly exercises multiple conditions.
44-72: LGTM: Clear test for non-matching branchesProperly validates that non-matching branches return undefined while maintaining array structure.
74-87: LGTM: Good edge case coverageThis test properly validates the behavior when no branches are provided.
89-112: LGTM: Proper error propagation testCorrectly verifies that errors thrown within branch steps are propagated to the caller.
114-139: LGTM: Cancellation handling testProperly validates that andBranch respects workflow cancellation signals and throws the expected error.
141-166: LGTM: Suspension handling testCorrectly validates that andBranch respects workflow suspension signals.
packages/core/src/workflow/core.ts (7)
968-996: LGTM: Well-structured guardrail integrationThe guardrail runtime setup is clean:
- Resolves guardrail sets from multiple sources
- Conditionally creates runtime only when needed
- Properly integrates guardrailAgent into execution context
This follows a sensible pattern for optional feature integration.
1051-1058: LGTM: Robust retry configuration handlingThe retry config calculation properly:
- Falls back to workflow-level defaults
- Validates numeric inputs with Number.isFinite
- Ensures non-negative integers with Math.max and Math.floor
This prevents invalid retry configurations.
1059-1109: LGTM: Well-designed terminal hook orchestrationThe
runTerminalHookshelper provides:
- Consistent hook context building via
buildHookContext- Safe hook execution with individual try/catch blocks
- Proper conditional logic for onEnd (excludes suspended unless explicitly requested)
- Step data snapshots in the hook context
This is a clean abstraction that reduces duplication across terminal states.
1111-1129: LGTM: Proper guardrail application placementInput and output guardrails are correctly applied:
- Input guardrails: before workflow execution with validation
- Output guardrails: after all steps complete
- State properly updated with guardrailed data
The error thrown for invalid input structure (line 1113-1116) provides clear guidance to users.
Also applies to: 1793-1805
1425-1430: LGTM: Complete step lifecycle trackingStep data tracking properly maintains input/output/status/error throughout the step lifecycle:
- Initialization with running status
- Reset on retry attempts
- Success/skipped/error status updates
- Used in hook contexts to provide step snapshots
This enables detailed workflow execution introspection.
2532-2661: LGTM: Comprehensive step type serializationThe serialization logic for new step types (sleep, sleep-until, foreach, loop, branch, map, guardrail, workflow) is well-structured:
- Extracts type-specific configuration
- Recursively serializes nested steps
- Converts functions to strings for inspection
This enables complete workflow introspection via getFullState.
2352-2407: LGTM: Elegant signal checking implementationThe
executeWithSignalCheckhelper provides responsive suspension:
- Periodic signal polling (configurable interval)
- Proper differentiation between cancellation and suspension
- Promise.race pattern for clean async flow
- Cleanup of interval on abort
This enables "immediate" suspension mode while maintaining clean async code.
packages/core/src/workflow/types.ts (5)
203-214: LGTM: Clear retry configuration interfaceWorkflowRetryConfig provides a simple, well-documented structure for retry settings with sensible defaults (0 retries, 0 delay).
264-280: LGTM: Comprehensive run options extensionThe additions to WorkflowRunOptions properly support:
- Retry configuration override
- Input/output guardrail specification
- Custom guardrail agent injection
All fields are appropriately optional with clear documentation.
313-359: LGTM: Well-designed hook context typesThe new hook types provide a robust foundation:
WorkflowHookStatus: terminal states for workflow completionWorkflowStepStatus: comprehensive step lifecycle statesWorkflowStepData: per-step execution detailsWorkflowHookContext: unified context for terminal hooks with status, state, result, error, suspension, cancellation, and step snapshotsThese types enable rich hook implementations with complete execution context.
627-641: LGTM: Consistent step type taxonomyThe step type enumerations are consistently extended across:
BaseWorkflowStepHistoryEntry.stepTypeWorkflowStreamEvent.stepTypeAll new step types (tap, workflow, guardrail, sleep, sleep-until, foreach, loop, branch, map) are properly included.
Also applies to: 773-787
381-407: Hook signatures documented in changeset—no action required.The breaking changes to hook signatures (
onSuspend,onError,onFinishnow receiveWorkflowHookContext;onEndreceives both state and optional context) are already documented in.changeset/social-humans-hammer.mdwith working code examples. This changeset will generate the CHANGELOG entry upon release.
| ```ts | ||
| createWorkflowChain({ | ||
| id: "retry-defaults", | ||
| retryConfig: { attempts: 2, delayMs: 500 }, | ||
| }) | ||
| .andThen({ | ||
| id: "fetch-user", | ||
| execute: async ({ data }) => fetchUser(data.userId), | ||
| }) | ||
| .andThen({ | ||
| id: "no-retry-step", | ||
| retries: 0, | ||
| execute: async ({ data }) => data, | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retry example uses data.userId without defining where userId comes from.
Lines 132-139 read data.userId, but the workflow definition doesn’t declare input (or a prior step) producing it—worth adjusting to avoid confusing docs (e.g., add input: z.object({ userId: z.string() }) or change the call to use whatever input you define).
🤖 Prompt for AI Agents
In @.changeset/social-humans-hammer.md around lines 131 - 145, The example
workflow references data.userId but never defines the workflow input, so update
the createWorkflowChain call to declare an input schema (e.g., input: z.object({
userId: z.string() })) or modify the example to use a clearly defined prior step
output; specifically edit the createWorkflowChain invocation to include an input
definition and ensure the "fetch-user" step (id "fetch-user") reads from that
input (data.userId), and keep the "no-retry-step" behavior unchanged.
PR Checklist
Please check if your PR fulfills the following requirements:
Bugs / Features
What is the current behavior?
What is the new behavior?
fixes (issue)
Notes for reviewers
Summary by cubic
Adds workflow control steps (branch, foreach, loop, map, sleep/sleepUntil), workflow guardrails and retry policies, and updated lifecycle hooks with step snapshots. Updates core API, serialization, tracing, examples, and docs.
Written for commit 3ca150d. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.