Skip to content

Commit 2f78c5e

Browse files
icecrasher321Vikhyath MondretiVikhyath Mondreti
authored
feat(subworkflows): workflows as a block within workflows (#480)
* feat(subworkflows) workflows in workflows * revert sync changes * working output vars * fix greptile comments * add cycle detection * add tests * working tests * works * fix formatting * fix input var handling * add images --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
1 parent f850e65 commit 2f78c5e

File tree

16 files changed

+967
-1
lines changed

16 files changed

+967
-1
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"title": "Blocks",
3-
"pages": ["agent", "api", "condition", "function", "evaluator", "router"]
3+
"pages": ["agent", "api", "condition", "function", "evaluator", "router", "workflow"]
44
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
---
2+
title: Workflow
3+
description: Execute other workflows as reusable components within your current workflow
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
import { Step, Steps } from 'fumadocs-ui/components/steps'
8+
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
9+
import { ThemeImage } from '@/components/ui/theme-image'
10+
11+
The Workflow block allows you to execute other workflows as reusable components within your current workflow. This powerful feature enables modular design, code reuse, and the creation of complex nested workflows that can be composed from smaller, focused workflows.
12+
13+
<ThemeImage
14+
lightSrc="/static/light/workflow-light.png"
15+
darkSrc="/static/dark/workflow-dark.png"
16+
alt="Workflow Block"
17+
width={300}
18+
height={175}
19+
/>
20+
21+
<Callout type="info">
22+
Workflow blocks enable modular design by allowing you to compose complex workflows from smaller, reusable components.
23+
</Callout>
24+
25+
## Overview
26+
27+
The Workflow block serves as a bridge between workflows, enabling you to:
28+
29+
<Steps>
30+
<Step>
31+
<strong>Reuse existing workflows</strong>: Execute previously created workflows as components within new workflows
32+
</Step>
33+
<Step>
34+
<strong>Create modular designs</strong>: Break down complex processes into smaller, manageable workflows
35+
</Step>
36+
<Step>
37+
<strong>Maintain separation of concerns</strong>: Keep different business logic isolated in separate workflows
38+
</Step>
39+
<Step>
40+
<strong>Enable team collaboration</strong>: Share and reuse workflows across different projects and team members
41+
</Step>
42+
</Steps>
43+
44+
## How It Works
45+
46+
The Workflow block:
47+
48+
1. Takes a reference to another workflow in your workspace
49+
2. Passes input data from the current workflow to the child workflow
50+
3. Executes the child workflow in an isolated context
51+
4. Returns the results back to the parent workflow for further processing
52+
53+
## Configuration Options
54+
55+
### Workflow Selection
56+
57+
Choose which workflow to execute from a dropdown list of available workflows in your workspace. The list includes:
58+
59+
- All workflows you have access to in the current workspace
60+
- Workflows shared with you by other team members
61+
- Both enabled and disabled workflows (though only enabled workflows can be executed)
62+
63+
### Input Data
64+
65+
Define the data to pass to the child workflow:
66+
67+
- **Single Variable Input**: Select a variable or block output to pass to the child workflow
68+
- **Variable References**: Use `<variable.name>` to reference workflow variables
69+
- **Block References**: Use `<blockName.response.field>` to reference outputs from previous blocks
70+
- **Automatic Mapping**: The selected data is automatically available as `start.response.input` in the child workflow
71+
- **Optional**: The input field is optional - child workflows can run without input data
72+
- **Type Preservation**: Variable types (strings, numbers, objects, etc.) are preserved when passed to the child workflow
73+
74+
### Examples of Input References
75+
76+
- `<variable.customerData>` - Pass a workflow variable
77+
- `<dataProcessor.response.result>` - Pass the result from a previous block
78+
- `<start.response.input>` - Pass the original workflow input
79+
- `<apiCall.response.data.user>` - Pass a specific field from an API response
80+
81+
### Execution Context
82+
83+
The child workflow executes with:
84+
85+
- Its own isolated execution context
86+
- Access to the same workspace resources (API keys, environment variables)
87+
- Proper workspace membership and permission checks
88+
- Independent logging and monitoring
89+
90+
## Safety and Limitations
91+
92+
To prevent infinite recursion and ensure system stability, the Workflow block includes several safety mechanisms:
93+
94+
<Callout type="warning">
95+
**Cycle Detection**: The system automatically detects and prevents circular dependencies between workflows to avoid infinite loops.
96+
</Callout>
97+
98+
- **Maximum Depth Limit**: Nested workflows are limited to a maximum depth of 10 levels
99+
- **Cycle Detection**: Automatic detection and prevention of circular workflow dependencies
100+
- **Timeout Protection**: Child workflows inherit timeout settings to prevent indefinite execution
101+
- **Resource Limits**: Memory and execution time limits apply to prevent resource exhaustion
102+
103+
## Inputs and Outputs
104+
105+
<Tabs items={['Inputs', 'Outputs']}>
106+
<Tab>
107+
<ul className="list-disc space-y-2 pl-6">
108+
<li>
109+
<strong>Workflow ID</strong>: The identifier of the workflow to execute
110+
</li>
111+
<li>
112+
<strong>Input Variable</strong>: Variable or block reference to pass to the child workflow (e.g., `<variable.name>` or `<block.response.field>`)
113+
</li>
114+
</ul>
115+
</Tab>
116+
<Tab>
117+
<ul className="list-disc space-y-2 pl-6">
118+
<li>
119+
<strong>Response</strong>: The complete output from the child workflow execution
120+
</li>
121+
<li>
122+
<strong>Child Workflow Name</strong>: The name of the executed child workflow
123+
</li>
124+
<li>
125+
<strong>Success Status</strong>: Boolean indicating whether the child workflow completed successfully
126+
</li>
127+
<li>
128+
<strong>Error Information</strong>: Details about any errors that occurred during execution
129+
</li>
130+
<li>
131+
<strong>Execution Metadata</strong>: Information about execution time, resource usage, and performance
132+
</li>
133+
</ul>
134+
</Tab>
135+
</Tabs>
136+
137+
## Example Usage
138+
139+
Here's an example of how a Workflow block might be used to create a modular customer onboarding process:
140+
141+
### Parent Workflow: Customer Onboarding
142+
```yaml
143+
# Main customer onboarding workflow
144+
blocks:
145+
- type: workflow
146+
name: "Validate Customer Data"
147+
workflowId: "customer-validation-workflow"
148+
input: "<variable.newCustomer>"
149+
150+
- type: workflow
151+
name: "Setup Customer Account"
152+
workflowId: "account-setup-workflow"
153+
input: "<Validate Customer Data.response.result>"
154+
155+
- type: workflow
156+
name: "Send Welcome Email"
157+
workflowId: "welcome-email-workflow"
158+
input: "<Setup Customer Account.response.result.accountDetails>"
159+
```
160+
161+
### Child Workflow: Customer Validation
162+
```yaml
163+
# Reusable customer validation workflow
164+
# Access the input data using: start.response.input
165+
blocks:
166+
- type: function
167+
name: "Validate Email"
168+
code: |
169+
const customerData = start.response.input;
170+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
171+
return emailRegex.test(customerData.email);
172+
173+
- type: api
174+
name: "Check Credit Score"
175+
url: "https://api.creditcheck.com/score"
176+
method: "POST"
177+
body: "<start.response.input>"
178+
```
179+
180+
### Variable Reference Examples
181+
182+
```yaml
183+
# Using workflow variables
184+
input: "<variable.customerInfo>"
185+
186+
# Using block outputs
187+
input: "<dataProcessor.response.cleanedData>"
188+
189+
# Using nested object properties
190+
input: "<apiCall.response.data.user.profile>"
191+
192+
# Using array elements (if supported by the resolver)
193+
input: "<listProcessor.response.items[0]>"
194+
```
195+
196+
## Access Control and Permissions
197+
198+
The Workflow block respects workspace permissions and access controls:
199+
200+
- **Workspace Membership**: Only workflows within the same workspace can be executed
201+
- **Permission Inheritance**: Child workflows inherit the execution permissions of the parent workflow
202+
- **API Key Access**: Child workflows have access to the same API keys and environment variables as the parent
203+
- **User Context**: The execution maintains the original user context for audit and logging purposes
204+
205+
## Best Practices
206+
207+
- **Keep workflows focused**: Design child workflows to handle specific, well-defined tasks
208+
- **Minimize nesting depth**: Avoid deeply nested workflow hierarchies for better maintainability
209+
- **Handle errors gracefully**: Implement proper error handling for child workflow failures
210+
- **Document dependencies**: Clearly document which workflows depend on others
211+
- **Version control**: Consider versioning strategies for workflows that are used as components
212+
- **Test independently**: Ensure child workflows can be tested and validated independently
213+
- **Monitor performance**: Be aware that nested workflows can impact overall execution time
214+
215+
## Common Patterns
216+
217+
### Microservice Architecture
218+
Break down complex business processes into smaller, focused workflows that can be developed and maintained independently.
219+
220+
### Reusable Components
221+
Create library workflows for common operations like data validation, email sending, or API integrations that can be reused across multiple projects.
222+
223+
### Conditional Execution
224+
Use workflow blocks within conditional logic to execute different business processes based on runtime conditions.
225+
226+
### Parallel Processing
227+
Combine workflow blocks with parallel execution to run multiple child workflows simultaneously for improved performance.
228+
229+
<Callout type="tip">
230+
When designing modular workflows, think of each workflow as a function with clear inputs, outputs, and a single responsibility.
231+
</Callout>
38 KB
Loading
26.4 KB
Loading
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { and, eq } from 'drizzle-orm'
2+
import { NextResponse } from 'next/server'
3+
import { getSession } from '@/lib/auth'
4+
import { createLogger } from '@/lib/logs/console-logger'
5+
import { db } from '@/db'
6+
import { workflow, workspaceMember } from '@/db/schema'
7+
8+
const logger = createLogger('WorkflowDetailAPI')
9+
10+
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
11+
const requestId = crypto.randomUUID().slice(0, 8)
12+
const startTime = Date.now()
13+
14+
try {
15+
// Get the session
16+
const session = await getSession()
17+
if (!session?.user?.id) {
18+
logger.warn(`[${requestId}] Unauthorized workflow access attempt`)
19+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
20+
}
21+
22+
const { id: workflowId } = await params
23+
24+
if (!workflowId) {
25+
return NextResponse.json({ error: 'Workflow ID is required' }, { status: 400 })
26+
}
27+
28+
// Fetch the workflow from database
29+
const workflowData = await db
30+
.select()
31+
.from(workflow)
32+
.where(eq(workflow.id, workflowId))
33+
.then((rows) => rows[0])
34+
35+
if (!workflowData) {
36+
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
37+
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
38+
}
39+
40+
// Check if user has access to this workflow
41+
// User can access if they own it OR if it's in a workspace they're part of
42+
const canAccess = workflowData.userId === session.user.id
43+
44+
if (!canAccess && workflowData.workspaceId) {
45+
// Check workspace membership
46+
const membership = await db
47+
.select()
48+
.from(workspaceMember)
49+
.where(
50+
and(
51+
eq(workspaceMember.workspaceId, workflowData.workspaceId),
52+
eq(workspaceMember.userId, session.user.id)
53+
)
54+
)
55+
.then((rows) => rows[0])
56+
57+
if (membership) {
58+
// User is a member of the workspace, allow access
59+
const elapsed = Date.now() - startTime
60+
logger.info(`[${requestId}] Workflow ${workflowId} fetched in ${elapsed}ms`)
61+
return NextResponse.json({ data: workflowData }, { status: 200 })
62+
}
63+
} else if (canAccess) {
64+
// User owns the workflow, allow access
65+
const elapsed = Date.now() - startTime
66+
logger.info(`[${requestId}] Workflow ${workflowId} fetched in ${elapsed}ms`)
67+
return NextResponse.json({ data: workflowData }, { status: 200 })
68+
}
69+
70+
logger.warn(
71+
`[${requestId}] User ${session.user.id} attempted to access workflow ${workflowId} without permission`
72+
)
73+
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
74+
} catch (error: any) {
75+
const elapsed = Date.now() - startTime
76+
logger.error(`[${requestId}] Error fetching workflow after ${elapsed}ms:`, error)
77+
return NextResponse.json({ error: 'Failed to fetch workflow' }, { status: 500 })
78+
}
79+
}

0 commit comments

Comments
 (0)