@unstable-dev/unmeshed-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +11 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.js +97 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +31 -0
- package/dist/get-docs.d.ts +8 -0
- package/dist/get-docs.js +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +203 -0
- package/knowledge/README.md +16 -0
- package/knowledge/SKILL.md +359 -0
- package/knowledge/assets/patterns.md +637 -0
- package/knowledge/execution/debugging-guide.md +18 -0
- package/knowledge/execution/process-run.schema.md +24 -0
- package/knowledge/execution/step-run.schema.md +21 -0
- package/knowledge/process-definition.schema.md +36 -0
- package/knowledge/references/integrations.md +914 -0
- package/knowledge/references/steps-knowledge.md +834 -0
- package/knowledge/step-definition.schema.md +140 -0
- package/knowledge/step-output-paths.md +45 -0
- package/knowledge/steps/DECISION_ENGINE.md +248 -0
- package/knowledge/steps/DEPENDSON.md +296 -0
- package/knowledge/steps/EXIT.md +220 -0
- package/knowledge/steps/FAIL.md +198 -0
- package/knowledge/steps/FLOW_GATEWAY.md +405 -0
- package/knowledge/steps/FOREACH.md +250 -0
- package/knowledge/steps/HTTP.md +183 -0
- package/knowledge/steps/JAVASCRIPT.md +192 -0
- package/knowledge/steps/JQ.md +189 -0
- package/knowledge/steps/LIST.md +279 -0
- package/knowledge/steps/NOOP.md +165 -0
- package/knowledge/steps/PARALLEL.md +366 -0
- package/knowledge/steps/PYTHON.md +206 -0
- package/knowledge/steps/SEND_RESPONSE.md +301 -0
- package/knowledge/steps/SQLITE.md +301 -0
- package/knowledge/steps/SUB_PROCESS.md +296 -0
- package/knowledge/steps/SWITCH.md +369 -0
- package/knowledge/steps/UPDATE_STEP.md +257 -0
- package/knowledge/steps/WAIT.md +218 -0
- package/knowledge/steps/WHILE.md +328 -0
- package/knowledge/steps/WORKER.md +233 -0
- package/knowledge/system-prompt.md +274 -0
- package/package.json +39 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
# Unmeshed Workflow Patterns & Templates
|
|
2
|
+
|
|
3
|
+
Use these as starting scaffolds. Replace all `<PLACEHOLDER>` values.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Pattern A — Linear Data Pipeline
|
|
8
|
+
|
|
9
|
+
**Use case:** Receive API input → transform → call integration → format output → store
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"orgId": 1,
|
|
14
|
+
"namespace": "default",
|
|
15
|
+
"name": "<workflow-name>",
|
|
16
|
+
"version": 1,
|
|
17
|
+
"type": "API_ORCHESTRATION",
|
|
18
|
+
"description": "<description>",
|
|
19
|
+
"configuration": null,
|
|
20
|
+
"steps": [
|
|
21
|
+
{
|
|
22
|
+
"orgId": 1, "namespace": "default",
|
|
23
|
+
"name": "map_input_fields", "type": "JAVASCRIPT", "ref": "map_input_fields",
|
|
24
|
+
"optional": false, "createdBy": "system", "updatedBy": "system",
|
|
25
|
+
"description": "Map and validate incoming API input", "label": null,
|
|
26
|
+
"created": 1700000000000, "updated": 1700000000000,
|
|
27
|
+
"configuration": { "errorPolicyName": null, "useCache": false, "cacheKey": null, "cacheTimeoutSeconds": 0, "stream": false, "streamAllStatuses": false, "preExecutionScript": null, "constructInputFromScript": false, "scriptLanguage": null, "jqTransformer": null, "rateLimitMaxRequests": 0, "rateLimitWindowSeconds": 0 },
|
|
28
|
+
"children": [],
|
|
29
|
+
"input": {
|
|
30
|
+
"script": "(steps, context) => {\n const input = context.input || {};\n return {\n field1: input.field1 || '',\n field2: input.field2 || ''\n };\n}"
|
|
31
|
+
},
|
|
32
|
+
"output": null
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"orgId": 1, "namespace": "default",
|
|
36
|
+
"name": "<integration_step>", "type": "INTEGRATION", "ref": "<integration_ref>_1",
|
|
37
|
+
"optional": false, "createdBy": "system", "updatedBy": "system",
|
|
38
|
+
"description": null, "label": null,
|
|
39
|
+
"created": 1700000000000, "updated": 1700000000000,
|
|
40
|
+
"configuration": { "errorPolicyName": null, "useCache": false, "cacheKey": null, "cacheTimeoutSeconds": 0, "stream": false, "streamAllStatuses": false, "preExecutionScript": null, "constructInputFromScript": false, "scriptLanguage": null, "jqTransformer": null, "rateLimitMaxRequests": 0, "rateLimitWindowSeconds": 0 },
|
|
41
|
+
"children": [],
|
|
42
|
+
"input": { /* see integrations.md */ },
|
|
43
|
+
"output": null
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"defaultInput": null, "defaultOutput": null, "outputMapping": null,
|
|
47
|
+
"signature": null, "metadata": null, "tags": null, "dependencies": null, "dependents": null
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Pattern B — LLM Classify → Decision Route
|
|
54
|
+
|
|
55
|
+
**Use case:** Input → map fields → LLM classifies → DECISION_ENGINE routes
|
|
56
|
+
|
|
57
|
+
Steps: `JAVASCRIPT (map)` → `INTEGRATION (llm-claude)` → `DECISION_ENGINE`
|
|
58
|
+
|
|
59
|
+
Key wiring:
|
|
60
|
+
- LLM output accessed as: `steps.llm_claude_1.output.results.<field>`
|
|
61
|
+
- Decision context maps LLM output fields to decision table column names
|
|
62
|
+
- Always defensively read LLM output: `(steps.llm_claude_1.output.results || steps.llm_claude_1.output.result || {})`
|
|
63
|
+
|
|
64
|
+
JAVASCRIPT (map) input:
|
|
65
|
+
```javascript
|
|
66
|
+
(steps, context) => {
|
|
67
|
+
const input = context.input || {};
|
|
68
|
+
return {
|
|
69
|
+
text: input.text || '',
|
|
70
|
+
userId: input.userId || ''
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
INTEGRATION (llm-claude) input:
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"type": "llm-claude",
|
|
79
|
+
"name": "unmeshed",
|
|
80
|
+
"publishProperties": { "allowedTokens": 300, "temperature": 0.0 },
|
|
81
|
+
"messageBody": {
|
|
82
|
+
"systemPrompt": "Classify the input. Return only valid JSON with fields: category, urgency.",
|
|
83
|
+
"userPrompt": "Text: {{steps.map_input_fields.output.result.text}}\n\nReturn: {\"category\": \"...\", \"urgency\": \"low|medium|high\"}"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
DECISION_ENGINE input:
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"decisionRuleStrategy": "FIRST_MATCH",
|
|
92
|
+
"decisionTable": "<your-table-name>",
|
|
93
|
+
"decisionContext": {
|
|
94
|
+
"Category": "{{steps.llm_claude_1.output.results.category}}",
|
|
95
|
+
"Urgency": "{{steps.llm_claude_1.output.results.urgency}}"
|
|
96
|
+
},
|
|
97
|
+
"decisionTableVersion": "",
|
|
98
|
+
"decisionOutputColumns": ["AssignTo", "Priority"]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Pattern C — State-Gated Branch (Create or Reuse Resource)
|
|
105
|
+
|
|
106
|
+
**Use case:** Check if resource exists in state → if yes use it, if no create it
|
|
107
|
+
|
|
108
|
+
Steps:
|
|
109
|
+
1. `PERSISTED_STATE` (READ) — check if resource ID is stored
|
|
110
|
+
2. `SWITCH` — branch on `steps.persisted_state_1.output.result` being null or not
|
|
111
|
+
- Branch `new-resource` (LIST): create resource → store ID in PERSISTED_STATE (UPSERT)
|
|
112
|
+
- Branch `existing-resource` (LIST or NOOP): use stored ID directly
|
|
113
|
+
|
|
114
|
+
SWITCH script pattern:
|
|
115
|
+
```javascript
|
|
116
|
+
(steps, context) => {
|
|
117
|
+
if (steps.persisted_state_1.output.result) {
|
|
118
|
+
return 'existing-resource';
|
|
119
|
+
}
|
|
120
|
+
return 'new-resource';
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
SWITCH responseMapping:
|
|
125
|
+
```json
|
|
126
|
+
[
|
|
127
|
+
{ "targetRef": "existing-resource", "value": "existing-resource" },
|
|
128
|
+
{ "targetRef": "new-resource", "value": "new-resource" },
|
|
129
|
+
{ "targetRef": "new-resource", "defaultBranch": true }
|
|
130
|
+
]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
PERSISTED_STATE (READ) input:
|
|
134
|
+
```json
|
|
135
|
+
{ "operation": "READ", "name": "my-spreadsheet-id", "path": "$" }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
PERSISTED_STATE (UPSERT, inside LIST branch) input:
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"operation": "UPSERT",
|
|
142
|
+
"name": "my-spreadsheet-id",
|
|
143
|
+
"path": "$",
|
|
144
|
+
"value": "{{steps.google_sheets_1.output.spreadsheetId}}"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Pattern D — Parallel Sub-Process Fan-out
|
|
151
|
+
|
|
152
|
+
**Use case:** Gate check → run multiple workflows concurrently
|
|
153
|
+
|
|
154
|
+
Steps:
|
|
155
|
+
1. `PERSISTED_STATE` (READ) — read a flag
|
|
156
|
+
2. `SWITCH` — if flag true: proceed, else EXIT
|
|
157
|
+
- Branch NOOP → continues to next top-level step
|
|
158
|
+
- Branch EXIT → terminates
|
|
159
|
+
3. `PARALLEL` — children are all SUB_PROCESS steps
|
|
160
|
+
|
|
161
|
+
PARALLEL input:
|
|
162
|
+
```json
|
|
163
|
+
{ "failIfAnyBranchFails": true }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
PARALLEL children (SUB_PROCESS steps):
|
|
167
|
+
```json
|
|
168
|
+
[
|
|
169
|
+
{
|
|
170
|
+
"orgId": 1, "namespace": "default",
|
|
171
|
+
"name": "process-a", "type": "SUB_PROCESS", "ref": "process-a",
|
|
172
|
+
"optional": false, "createdBy": "system", "updatedBy": "system",
|
|
173
|
+
"description": null, "label": null,
|
|
174
|
+
"created": 1700000000000, "updated": 1700000000000,
|
|
175
|
+
"configuration": { "errorPolicyName": null, "useCache": false, "cacheKey": null, "cacheTimeoutSeconds": 0, "stream": false, "streamAllStatuses": false, "preExecutionScript": null, "constructInputFromScript": false, "scriptLanguage": null, "jqTransformer": null, "rateLimitMaxRequests": 0, "rateLimitWindowSeconds": 0 },
|
|
176
|
+
"children": [],
|
|
177
|
+
"input": { "processName": "process-a", "waitForCompletion": true },
|
|
178
|
+
"output": null
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Pattern E — Classify → Store All → Conditionally Notify
|
|
186
|
+
|
|
187
|
+
**Use case:** Every record is stored (e.g. Google Sheets), but notifications (e.g. Slack) only fire for specific conditions (e.g. critical urgency).
|
|
188
|
+
|
|
189
|
+
Steps:
|
|
190
|
+
1. `JAVASCRIPT` — map/validate input fields
|
|
191
|
+
2. `INTEGRATION` (llm-claude) — classify/analyse with LLM
|
|
192
|
+
3. `DECISION_ENGINE` — route based on classification rules
|
|
193
|
+
4. `JAVASCRIPT` — format output record + pre-build notification message string
|
|
194
|
+
5. `INTEGRATION` (google-sheets APPEND_ROWS) — always write to sheet unconditionally
|
|
195
|
+
6. `SWITCH` — check condition (e.g. `topic === 'technical_issue' && urgency === 'critical'`)
|
|
196
|
+
- Branch `notify` → `INTEGRATION` (slack-messaging) directly (no LIST wrapper needed)
|
|
197
|
+
- Branch `skip` → `NOOP` (do nothing)
|
|
198
|
+
|
|
199
|
+
**Key insight:** The SWITCH comes **after** the unconditional storage step. This ensures every record is persisted regardless of whether a notification fires.
|
|
200
|
+
|
|
201
|
+
SWITCH branch direct to INTEGRATION example:
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"type": "SWITCH",
|
|
205
|
+
"children": [
|
|
206
|
+
{
|
|
207
|
+
"orgId": 1, "namespace": "default",
|
|
208
|
+
"name": "slack_messaging", "type": "INTEGRATION", "ref": "slack_messaging_1",
|
|
209
|
+
"optional": false, "createdBy": "system", "updatedBy": "system",
|
|
210
|
+
"description": null, "label": null,
|
|
211
|
+
"created": 1700000000000, "updated": 1700000000000,
|
|
212
|
+
"configuration": { "errorPolicyName": null, "useCache": false, "cacheKey": null, "cacheTimeoutSeconds": 0, "stream": false, "streamAllStatuses": false, "preExecutionScript": null, "constructInputFromScript": false, "scriptLanguage": null, "jqTransformer": null, "rateLimitMaxRequests": 0, "rateLimitWindowSeconds": 0 },
|
|
213
|
+
"children": [],
|
|
214
|
+
"input": {
|
|
215
|
+
"type": "slack-messaging",
|
|
216
|
+
"name": "",
|
|
217
|
+
"messageBody": {
|
|
218
|
+
"type": "MESSAGE",
|
|
219
|
+
"message": "{{steps.format_step.output.result.slackMessage}}",
|
|
220
|
+
"messageBlocks": [{ "type": "section", "text": { "type": "mrkdwn", "text": "{{steps.format_step.output.result.slackMessage}}" } }]
|
|
221
|
+
},
|
|
222
|
+
"publishProperties": { "durationType": "MINUTES", "receiverType": "CHANNEL" }
|
|
223
|
+
},
|
|
224
|
+
"output": null
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"orgId": 1, "namespace": "default",
|
|
228
|
+
"name": "no_notification", "type": "NOOP", "ref": "no-notification",
|
|
229
|
+
"optional": false, "createdBy": "system", "updatedBy": "system",
|
|
230
|
+
"description": null, "label": null,
|
|
231
|
+
"created": 1700000000000, "updated": 1700000000000,
|
|
232
|
+
"configuration": { "errorPolicyName": null, "useCache": false, "cacheKey": null, "cacheTimeoutSeconds": 0, "stream": false, "streamAllStatuses": false, "preExecutionScript": null, "constructInputFromScript": false, "scriptLanguage": null, "jqTransformer": null, "rateLimitMaxRequests": 0, "rateLimitWindowSeconds": 0 },
|
|
233
|
+
"children": [],
|
|
234
|
+
"input": {},
|
|
235
|
+
"output": null
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
"input": {
|
|
239
|
+
"script": "(steps, context) => {\n const topic = String(steps.format_step.output.result.topic || '').toLowerCase();\n const urgency = String(steps.format_step.output.result.urgency || '').toLowerCase();\n if (topic === 'technical_issue' && urgency === 'critical') return 'notify';\n return 'skip';\n}",
|
|
240
|
+
"responseMapping": [
|
|
241
|
+
{ "targetRef": "slack_messaging_1", "value": "notify" },
|
|
242
|
+
{ "targetRef": "no-notification", "value": "skip" },
|
|
243
|
+
{ "targetRef": "no-notification", "defaultBranch": true }
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Pattern F — HTTP API Call → Normalize → Store
|
|
252
|
+
|
|
253
|
+
**Use case:** Call external REST API, normalize the response, store to DB or sheet.
|
|
254
|
+
|
|
255
|
+
Steps:
|
|
256
|
+
1. `JAVASCRIPT` — build request payload from context input
|
|
257
|
+
2. `HTTP` — call the external API
|
|
258
|
+
3. `JAVASCRIPT` — normalize/extract fields from API response
|
|
259
|
+
4. `INTEGRATION` (postgres or google-sheets) — store the data
|
|
260
|
+
|
|
261
|
+
Key wiring for HTTP step:
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"type": "HTTP",
|
|
265
|
+
"ref": "http_1",
|
|
266
|
+
"input": {
|
|
267
|
+
"method": "POST",
|
|
268
|
+
"url": "https://api.example.com/data",
|
|
269
|
+
"headers": { "Content-Type": "application/json", "Authorization": "Bearer {{secrets.api_token}}" },
|
|
270
|
+
"params": {},
|
|
271
|
+
"body": {
|
|
272
|
+
"type": "json",
|
|
273
|
+
"content": {
|
|
274
|
+
"query": "{{steps.map_input_fields.output.result.query}}"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
"repeatUntilEnabled": null,
|
|
278
|
+
"repeatUntilCondition": { "script": "(steps, context) => { return false; }" },
|
|
279
|
+
"repeatIntervalSeconds": null,
|
|
280
|
+
"maxRepeatCount": null,
|
|
281
|
+
"includeFullResponseString": false,
|
|
282
|
+
"noEncode": false,
|
|
283
|
+
"extraLongTimeouts": false
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Normalize JS after HTTP:
|
|
289
|
+
```javascript
|
|
290
|
+
(steps, context) => {
|
|
291
|
+
const response = steps.http_1.output.response || {};
|
|
292
|
+
return {
|
|
293
|
+
id: response.id || '',
|
|
294
|
+
name: response.name || '',
|
|
295
|
+
value: response.data?.value || 0
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Pattern G — Error-Aware Exit Branches
|
|
303
|
+
|
|
304
|
+
**Use case:** Validate input → exit with FAILED if invalid, else continue processing.
|
|
305
|
+
|
|
306
|
+
Steps:
|
|
307
|
+
1. `JAVASCRIPT` — validate input fields, return `{ valid: true/false, reason: '...' }`
|
|
308
|
+
2. `SWITCH` — branch on `valid`
|
|
309
|
+
- Branch `invalid` → `EXIT` (status: FAILED)
|
|
310
|
+
- Branch `valid` → `LIST` (continue with main logic)
|
|
311
|
+
|
|
312
|
+
SWITCH script:
|
|
313
|
+
```javascript
|
|
314
|
+
(steps, context) => {
|
|
315
|
+
if (!steps.validate_input.output.result.valid) return 'invalid';
|
|
316
|
+
return 'valid';
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
EXIT step inside invalid branch:
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"type": "EXIT",
|
|
324
|
+
"input": {
|
|
325
|
+
"message": "{{steps.validate_input.output.result.reason}}",
|
|
326
|
+
"status": "FAILED"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Ref Naming Conventions
|
|
334
|
+
|
|
335
|
+
| Step type | Ref pattern | Example |
|
|
336
|
+
|---|---|---|
|
|
337
|
+
| INTEGRATION | `<name>_<n>` | `google_sheets_1`, `llm_claude_1`, `http_1` |
|
|
338
|
+
| JAVASCRIPT | same as `name` | `map_input_fields`, `format_output` |
|
|
339
|
+
| PYTHON | same as `name` | `extract_content` |
|
|
340
|
+
| NOOP | same as `name` | `job_config`, `settings` |
|
|
341
|
+
| SWITCH | `switch_<n>` | `switch_1` |
|
|
342
|
+
| PARALLEL | `parallel_<n>` | `parallel_1` |
|
|
343
|
+
| PERSISTED_STATE | `persisted_state_<n>` | `persisted_state_1` |
|
|
344
|
+
| LIST (branch) | descriptive kebab | `new-sheet`, `existing-sheet`, `invalid-input` |
|
|
345
|
+
| EXIT | `exit_<n>` | `exit_1` |
|
|
346
|
+
| SUB_PROCESS | same as `name` | `send-welcome-email` |
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Data Flow Cheatsheet
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
context.input.<field> ← workflow API input
|
|
354
|
+
steps.<ref>.output.response.<field> ← native HTTP response body
|
|
355
|
+
steps.<ref>.output.statusCode ← native HTTP status code
|
|
356
|
+
steps.<ref>.output.result.<field> ← JAVASCRIPT / PYTHON / most INTEGRATION output
|
|
357
|
+
steps.<ref>.output.results.<field> ← LLM Claude output (note: results plural)
|
|
358
|
+
steps.<ref>.output.<field> ← NOOP output (no .result wrapper)
|
|
359
|
+
steps.<ref>.output.spreadsheetId ← Google Sheets CREATE output
|
|
360
|
+
steps.<ref>.output.result.rows ← PostgreSQL / MySQL SELECT output
|
|
361
|
+
steps.<ref>.output.id ← Google Drive upload output (file ID)
|
|
362
|
+
steps.<ref>.output.webViewLink ← Google Drive upload output (share URL)
|
|
363
|
+
steps.<ref>.output.status ← HTTP integration response status code
|
|
364
|
+
steps.__self.id ← current step ID (inside scripts)
|
|
365
|
+
context.id ← workflow process/run ID
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Pattern H — WAIT for Async Side Effect
|
|
371
|
+
|
|
372
|
+
**Use case:** Trigger an async action (e.g. send email via sub-process), wait for it to arrive/propagate, then assert or read results.
|
|
373
|
+
|
|
374
|
+
Steps:
|
|
375
|
+
1. `SUB_PROCESS` — trigger the action (e.g. send email)
|
|
376
|
+
2. `WAIT` — pause for delivery/propagation time
|
|
377
|
+
3. `INTEGRATION` — read/verify the result (e.g. readEmail)
|
|
378
|
+
4. `JAVASCRIPT` — assert on the result
|
|
379
|
+
|
|
380
|
+
WAIT input:
|
|
381
|
+
```javascript
|
|
382
|
+
(steps, context) => {
|
|
383
|
+
return {
|
|
384
|
+
"waitUntil": steps.__self.startTime + (25 * 1000) // 25 seconds
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Key rule:** Always use `steps.__self.startTime` (not `Date.now()`) as the base — it's stable within a WAIT instance.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Pattern I — FOREACH Loop
|
|
394
|
+
|
|
395
|
+
**Use case:** Iterate over an array and run the same steps for each element.
|
|
396
|
+
|
|
397
|
+
Structure:
|
|
398
|
+
```
|
|
399
|
+
FOREACH (inputArray, concurrency)
|
|
400
|
+
└── LIST
|
|
401
|
+
└── [step(s) for each iteration]
|
|
402
|
+
JAVASCRIPT (collect results)
|
|
403
|
+
JAVASCRIPT (assert on collected results)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
FOREACH input:
|
|
407
|
+
```json
|
|
408
|
+
{
|
|
409
|
+
"inputArray": [1, 2, 3, 4, 5],
|
|
410
|
+
"concurrency": 1
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Collecting results after the loop:
|
|
415
|
+
```javascript
|
|
416
|
+
(steps, context) => {
|
|
417
|
+
const allOutputs = [];
|
|
418
|
+
for (let i = 0; i < 5; i++) {
|
|
419
|
+
allOutputs.push(steps[`http[${i}]`].output);
|
|
420
|
+
}
|
|
421
|
+
return allOutputs;
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Access individual iteration output: `steps["<ref>[0]"].output`, `steps["<ref>[1]"].output`, etc.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Pattern J — PARALLEL with LIST Branches (not SUB_PROCESS)
|
|
430
|
+
|
|
431
|
+
**Use case:** Run multiple independent test suites or processing tracks concurrently within the same workflow.
|
|
432
|
+
|
|
433
|
+
Structure: `PARALLEL → [LIST branch_1, LIST branch_2, LIST branch_3, ...]`
|
|
434
|
+
|
|
435
|
+
Each LIST branch has its own `ref` and independent `children` steps. Unlike Pattern D (SUB_PROCESS fan-out), the branches are inline within the same workflow JSON.
|
|
436
|
+
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"type": "PARALLEL",
|
|
440
|
+
"children": [
|
|
441
|
+
{ "type": "LIST", "ref": "branch_1", "children": [...], "input": {} },
|
|
442
|
+
{ "type": "LIST", "ref": "branch_2", "children": [...], "input": {} },
|
|
443
|
+
{ "type": "LIST", "ref": "branch_3", "children": [...], "input": {} }
|
|
444
|
+
],
|
|
445
|
+
"input": { "failIfAnyBranchFails": true }
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Use `"failIfAnyBranchFails": false` when branches are independent and partial failure is acceptable.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Pattern K — Rate-Limit Aware Integration
|
|
454
|
+
|
|
455
|
+
**Use case:** External API may return HTTP 429 (rate limit). Mark the step optional and use a SWITCH to gracefully skip when rate-limited.
|
|
456
|
+
|
|
457
|
+
Steps:
|
|
458
|
+
1. `INTEGRATION` (optional: true, errorPolicyName: "retry_ep") — call the external API
|
|
459
|
+
2. `SWITCH` — check if the output contains a rate-limit error
|
|
460
|
+
- Branch `plan_expired` → `JAVASCRIPT` (return skip/pass message)
|
|
461
|
+
- Branch `plan_working` → `LIST` (continue with main logic)
|
|
462
|
+
|
|
463
|
+
SWITCH script for rate-limit detection:
|
|
464
|
+
```javascript
|
|
465
|
+
(steps, context) => {
|
|
466
|
+
if (steps.get_all_tables.output.error &&
|
|
467
|
+
steps.get_all_tables.output.error.includes("HTTP 429 429 TOO_MANY_REQUESTS")) {
|
|
468
|
+
return "plan_expired";
|
|
469
|
+
}
|
|
470
|
+
return "plan_working";
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
SWITCH responseMapping:
|
|
475
|
+
```json
|
|
476
|
+
[
|
|
477
|
+
{ "targetRef": "plan_expired", "value": "plan_expired" },
|
|
478
|
+
{ "targetRef": "plan_working", "defaultBranch": true }
|
|
479
|
+
]
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Ref Naming Conventions
|
|
485
|
+
|
|
486
|
+
| Step type | Ref pattern | Example |
|
|
487
|
+
|---|---|---|
|
|
488
|
+
| INTEGRATION | `<name>_<n>` | `google_sheets_1`, `llm_claude_1`, `http_1` |
|
|
489
|
+
| JAVASCRIPT | same as `name` | `map_input_fields`, `format_output`, `assert_1` |
|
|
490
|
+
| PYTHON | same as `name` | `extract_content`, `delete_attachments` |
|
|
491
|
+
| NOOP | same as `name` | `job_config`, `settings` |
|
|
492
|
+
| SWITCH | `switch_<n>` | `switch_1` |
|
|
493
|
+
| PARALLEL | `parallel_<n>` | `parallel_1` |
|
|
494
|
+
| FOREACH | descriptive name | `foreachstep`, `foreach_users` |
|
|
495
|
+
| WAIT | `wait_<n>` | `wait_1`, `wait_2` |
|
|
496
|
+
| HTTP (native) | `http` or `http_<n>` | `http`, `http_1` |
|
|
497
|
+
| PERSISTED_STATE | `persisted_state_<n>` | `persisted_state_1` |
|
|
498
|
+
| LIST (branch) | descriptive kebab | `new-sheet`, `branch_1`, `plan_working` |
|
|
499
|
+
| EXIT | `exit_<n>` | `exit_1` |
|
|
500
|
+
| SUB_PROCESS | same as `name` | `send_sendgrid_mail` |
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Data Flow Cheatsheet
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
context.input.<field> ← workflow API input
|
|
508
|
+
steps.<ref>.output.response.<field> ← native HTTP response body
|
|
509
|
+
steps.<ref>.output.statusCode ← native HTTP status code
|
|
510
|
+
steps.<ref>.output.result.<field> ← JAVASCRIPT / PYTHON / most INTEGRATION output
|
|
511
|
+
steps.<ref>.output.results.<field> ← LLM Claude, Redis, MongoDB output
|
|
512
|
+
steps.<ref>.output.<field> ← NOOP output (no .result wrapper)
|
|
513
|
+
steps.<ref>.output.spreadsheetId ← Google Sheets CREATE output
|
|
514
|
+
steps.<ref>.output.result.rows ← PostgreSQL / MySQL SELECT output
|
|
515
|
+
steps.<ref>.output.id ← Google Drive, Notion CREATE output
|
|
516
|
+
steps.<ref>.output.webViewLink ← Google Drive upload share URL
|
|
517
|
+
steps.<ref>.output.status ← HTTP integration response status code
|
|
518
|
+
steps.<ref>.output.messages ← Outlook readEmail output (array)
|
|
519
|
+
steps.<ref>.output.tables ← Airtable GET_TABLES output (array)
|
|
520
|
+
steps.<ref>.output.records ← Airtable LIST/CREATE output (array)
|
|
521
|
+
steps.<ref>.output.fields.<FieldName> ← Airtable READ single record
|
|
522
|
+
steps.<ref>.output.issues ← Jira SEARCH_ISSUES output (array)
|
|
523
|
+
steps.<ref>.output.transitions ← Jira GET_TRANSITIONS output (array)
|
|
524
|
+
steps.<ref>.output.comments ← Jira GET_COMMENTS output (array)
|
|
525
|
+
steps.<ref>.output.results ← Notion QUERY/SEARCH/LIST_USERS/GET_BLOCK_CHILDREN
|
|
526
|
+
steps.<ref>.output.results.result ← Redis GET (string), KEYS (array), EVAL result
|
|
527
|
+
steps.<ref>.output.results.count ← MongoDB COUNT
|
|
528
|
+
steps.<ref>.output.results.success ← MongoDB CREATE/DELETE_COLLECTION
|
|
529
|
+
steps["<ref>[0]"].output ← FOREACH iteration output (index notation)
|
|
530
|
+
steps.__self.id ← current step ID (inside scripts)
|
|
531
|
+
steps.__self.startTime ← WAIT step start time (milliseconds)
|
|
532
|
+
context.id ← workflow process/run ID
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Pattern L — WHILE Loop with State Counter
|
|
538
|
+
|
|
539
|
+
**Use case:** Repeat a block of steps a dynamic number of times, tracking progress in state.
|
|
540
|
+
|
|
541
|
+
Structure:
|
|
542
|
+
```
|
|
543
|
+
WHILE (whileloop.iteration < N)
|
|
544
|
+
└── LIST
|
|
545
|
+
├── WAIT (throttle)
|
|
546
|
+
└── JAVASCRIPT (__statePut to update state)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
WHILE input:
|
|
550
|
+
```javascript
|
|
551
|
+
(steps, context) => {
|
|
552
|
+
return whileloop.iteration < 5;
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
WAIT inside loop (use WHILE step's `.updated` as base):
|
|
557
|
+
```javascript
|
|
558
|
+
(steps, context) => {
|
|
559
|
+
return { "waitUntil": steps.my_while_step.updated + 1000 };
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
State increment JAVASCRIPT:
|
|
564
|
+
```javascript
|
|
565
|
+
(steps, context) => {
|
|
566
|
+
return {
|
|
567
|
+
"__statePut": {
|
|
568
|
+
"count": (context.state.count || 0) + 1
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Read state anywhere: `context.state.count`
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Pattern M — Error Policy Retry Check
|
|
579
|
+
|
|
580
|
+
**Use case:** A step is expected to fail on early attempts (error policy retries it), then succeed. Assert that it eventually passed after N retries.
|
|
581
|
+
|
|
582
|
+
Mark the step `optional: true` and set `errorPolicyName` to a retry policy. In the assertion step, check `steps.__self.executionList.length` or verify the step eventually returned a valid output.
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
// Inside the retrying step — fail until retry count >= 3
|
|
586
|
+
(steps, context) => {
|
|
587
|
+
const index = steps.__self.executionList.length;
|
|
588
|
+
if (index < 3) {
|
|
589
|
+
throw Error("Failed the step because index < 3");
|
|
590
|
+
}
|
|
591
|
+
return { "index": index };
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Assert on failed optional step's error message:
|
|
596
|
+
```javascript
|
|
597
|
+
(steps, context) => {
|
|
598
|
+
const errorMessage = steps.fail_debug.output.error;
|
|
599
|
+
if (!errorMessage.includes("expected error text")) {
|
|
600
|
+
throw Error("Not returning expected error");
|
|
601
|
+
}
|
|
602
|
+
return { "allGood": true };
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Pattern N — Variables and Secrets in HTTP Steps
|
|
609
|
+
|
|
610
|
+
Use `{{variables.*}}` for environment-specific config and `{{secrets.*}}` for credentials. Never hardcode URLs or tokens.
|
|
611
|
+
|
|
612
|
+
```json
|
|
613
|
+
{
|
|
614
|
+
"type": "HTTP",
|
|
615
|
+
"input": {
|
|
616
|
+
"method": "GET",
|
|
617
|
+
"url": "{{variables.dev_server}}/api/resource/{{steps.create.output.response.namespace}}/{{steps.create.output.response.name}}",
|
|
618
|
+
"headers": {
|
|
619
|
+
"Authorization": "Bearer {{secrets.unmeshed_test_token}}"
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Body with mixed template refs:
|
|
626
|
+
```json
|
|
627
|
+
{
|
|
628
|
+
"body": {
|
|
629
|
+
"type": "json",
|
|
630
|
+
"content": {
|
|
631
|
+
"name": "{{steps.create.output.response.name}}",
|
|
632
|
+
"namespace": "{{steps.create.output.response.namespace}}",
|
|
633
|
+
"config": "{{steps.create.output.response.config}}"
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Process Run Debugging Guide
|
|
2
|
+
|
|
3
|
+
Use this sequence when a user asks why a process failed or produced the wrong output.
|
|
4
|
+
|
|
5
|
+
1. Fetch the process run by id with step details.
|
|
6
|
+
2. Identify process status and process-level output.
|
|
7
|
+
3. Find the first failed step.
|
|
8
|
+
4. Inspect that step's runtime input, output, and error.
|
|
9
|
+
5. Inspect previous step outputs referenced by the failed step.
|
|
10
|
+
6. Compare runtime output paths against the step output path contract.
|
|
11
|
+
7. Explain the root cause in plain English.
|
|
12
|
+
8. If the fix is clear, propose the exact workflow change.
|
|
13
|
+
|
|
14
|
+
Common HTTP-to-JavaScript issue:
|
|
15
|
+
- HTTP output lives at `steps.<http_ref>.output.response`.
|
|
16
|
+
- JavaScript/Python output lives at `steps.<js_ref>.output.result`.
|
|
17
|
+
- If a JavaScript/Python step after HTTP reads `steps.<http_ref>.output.result`, it will receive `undefined`.
|
|
18
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Process Run Schema
|
|
2
|
+
|
|
3
|
+
Use this contract when explaining or debugging an executed process.
|
|
4
|
+
|
|
5
|
+
Process run records can include:
|
|
6
|
+
- Process id
|
|
7
|
+
- Namespace
|
|
8
|
+
- Process definition name
|
|
9
|
+
- Version
|
|
10
|
+
- Status
|
|
11
|
+
- Trigger type
|
|
12
|
+
- Correlation id
|
|
13
|
+
- Request id
|
|
14
|
+
- Created/updated timestamps
|
|
15
|
+
- Input
|
|
16
|
+
- Output
|
|
17
|
+
- Step records when requested with full details
|
|
18
|
+
|
|
19
|
+
Debugging rules:
|
|
20
|
+
- Use the process id to fetch the run.
|
|
21
|
+
- Use full step details when the user asks for complete JSON, step output, failed step, or troubleshooting.
|
|
22
|
+
- Do not infer output from the process definition alone; inspect the executed run output.
|
|
23
|
+
- If process-level output is `null`, inspect step records before claiming the workflow produced no useful data.
|
|
24
|
+
|