@elevasis/sdk 1.8.2 → 1.9.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/dist/cli.cjs +289 -105
- package/dist/index.d.ts +90 -39
- package/dist/types/worker/adapters/lead.d.ts +1 -1
- package/dist/worker/index.js +2 -0
- package/package.json +2 -2
- package/reference/_navigation.md +7 -1
- package/reference/_reference-manifest.json +14 -0
- package/reference/claude-config/logs/scaffold-registry-reminder.log +3 -0
- package/reference/claude-config/rules/agent-start-here.md +254 -254
- package/reference/claude-config/rules/frontend.md +43 -43
- package/reference/claude-config/rules/operations.md +64 -64
- package/reference/claude-config/rules/organization-model.md +42 -43
- package/reference/claude-config/rules/organization-os.md +107 -107
- package/reference/claude-config/rules/shared-types.md +2 -2
- package/reference/claude-config/rules/task-tracking.md +1 -1
- package/reference/claude-config/rules/ui.md +202 -202
- package/reference/claude-config/rules/vibe.md +202 -202
- package/reference/claude-config/skills/configure/SKILL.md +98 -98
- package/reference/claude-config/skills/configure/operations/codify-level-a.md +100 -100
- package/reference/claude-config/skills/configure/operations/codify-level-b.md +158 -158
- package/reference/claude-config/skills/configure/operations/customers.md +150 -150
- package/reference/claude-config/skills/configure/operations/features.md +162 -162
- package/reference/claude-config/skills/configure/operations/goals.md +147 -147
- package/reference/claude-config/skills/configure/operations/identity.md +133 -133
- package/reference/claude-config/skills/configure/operations/labels.md +128 -128
- package/reference/claude-config/skills/configure/operations/offerings.md +159 -159
- package/reference/claude-config/skills/configure/operations/roles.md +153 -153
- package/reference/claude-config/skills/configure/operations/techStack.md +139 -139
- package/reference/claude-config/skills/explore/SKILL.md +78 -78
- package/reference/claude-config/skills/git-sync/SKILL.md +126 -0
- package/reference/claude-config/skills/save/SKILL.md +183 -183
- package/reference/claude-config/skills/setup/SKILL.md +275 -275
- package/reference/claude-config/skills/sync/SKILL.md +10 -44
- package/reference/claude-config/sync-notes/2026-04-22-git-sync-and-sync-notes.md +27 -0
- package/reference/claude-config/sync-notes/2026-04-22-lead-gen-deliverability-removal.md +30 -0
- package/reference/claude-config/sync-notes/2026-04-24-ui-consolidation-and-sdk-cli-train.md +86 -0
- package/reference/claude-config/sync-notes/README.md +43 -0
- package/reference/deployment/index.mdx +42 -7
- package/reference/examples/organization-model.ts +689 -0
- package/reference/index.mdx +6 -5
- package/reference/packages/core/src/README.md +39 -36
- package/reference/packages/core/src/business/README.md +52 -52
- package/reference/packages/core/src/organization-model/README.md +97 -97
- package/reference/packages/core/src/test-utils/README.md +42 -0
- package/reference/scaffold/core/organization-graph.mdx +272 -272
- package/reference/scaffold/core/organization-model.mdx +320 -320
- package/reference/scaffold/index.mdx +64 -64
- package/reference/scaffold/operations/propagation-pipeline.md +125 -104
- package/reference/scaffold/operations/scaffold-maintenance.md +122 -122
- package/reference/scaffold/operations/workflow-recipes.md +436 -436
- package/reference/scaffold/recipes/add-a-feature.md +158 -158
- package/reference/scaffold/recipes/add-a-resource.md +158 -158
- package/reference/scaffold/recipes/customize-organization-model.md +400 -400
- package/reference/scaffold/recipes/extend-a-base-entity.md +140 -140
- package/reference/scaffold/recipes/gate-by-feature-or-admin.md +158 -158
- package/reference/scaffold/recipes/index.md +32 -32
- package/reference/scaffold/reference/contracts.md +608 -607
- package/reference/scaffold/reference/feature-registry.md +2 -0
- package/reference/scaffold/reference/glossary.md +105 -105
- package/reference/scaffold/ui/composition-extensibility.mdx +1 -1
- package/reference/scaffold/ui/feature-flags-and-gating.md +1 -1
- package/reference/claude-config/commands/submit-request.md +0 -11
|
@@ -1,436 +1,436 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Workflow Recipes
|
|
3
|
-
description: Anatomy of a workflow, adapter usage, and trigger patterns -- runnable email-notification example replaces the trivial echo workflow. Resolves eval score 2/5 on workflow authoring.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Workflow Recipes
|
|
7
|
-
|
|
8
|
-
`🟢 Stable` -- Use these patterns as-is against `@elevasis/sdk ^1.4.0`.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. Anatomy of a Workflow
|
|
13
|
-
|
|
14
|
-
Every workflow is a `WorkflowDefinition` object with four top-level keys: `config`, `contract`, `steps`, and `entryPoint`. The `email-notification` workflow at `operations/src/email-notification/index.ts` is used as the running example throughout this section.
|
|
15
|
-
|
|
16
|
-
### Config
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
config: {
|
|
20
|
-
resourceId: 'email-notification', // Unique ID -- used in API calls and CLI
|
|
21
|
-
name: 'Email Notification', // Human-readable display name
|
|
22
|
-
type: 'workflow', // Always 'workflow' (vs 'agent')
|
|
23
|
-
description: 'Sends a notification email to a user...',
|
|
24
|
-
version: '1.0.0', // Bump on contract changes
|
|
25
|
-
status: 'dev' // 'dev' or 'prod'
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
- `resourceId` is the stable identifier used by `pnpm exec elevasis exec` and `/execute` API calls.
|
|
30
|
-
- Bump `version` whenever you change `contract.inputSchema` or `contract.outputSchema`.
|
|
31
|
-
|
|
32
|
-
### Contract
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// foundations/types/index.ts -- shared with frontend
|
|
36
|
-
export const emailNotificationInputSchema = z.object({
|
|
37
|
-
recipientEmail: z.string().email(),
|
|
38
|
-
recipientName: z.string().min(1),
|
|
39
|
-
subject: z.string().min(1).max(200),
|
|
40
|
-
body: z.string().min(1).max(10000),
|
|
41
|
-
category: z.string().min(1).default('operations'),
|
|
42
|
-
actionUrl: z.string().url().optional()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
export const emailNotificationOutputSchema = z.object({
|
|
46
|
-
delivered: z.boolean(),
|
|
47
|
-
notificationId: z.string().optional(),
|
|
48
|
-
summary: z.string()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
export type EmailNotificationInput = z.infer<typeof emailNotificationInputSchema>
|
|
52
|
-
export type EmailNotificationOutput = z.infer<typeof emailNotificationOutputSchema>
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
// operations/src/email-notification/index.ts
|
|
57
|
-
import { emailNotificationInputSchema, emailNotificationOutputSchema } from '@foundation/types'
|
|
58
|
-
|
|
59
|
-
contract: {
|
|
60
|
-
inputSchema: emailNotificationInputSchema,
|
|
61
|
-
outputSchema: emailNotificationOutputSchema
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**Rules:**
|
|
66
|
-
|
|
67
|
-
- Define schemas in `foundations/types/index.ts` -- never inline Zod schemas in workflow files.
|
|
68
|
-
- Both runtimes (frontend and platform) import from `@foundation/types`, keeping validation consistent.
|
|
69
|
-
- `@foundation/types` resolves to `foundations/types/index.ts` via the tsconfig path alias.
|
|
70
|
-
|
|
71
|
-
**Entity-backed workflows:** for workflows that operate on a domain entity, reference the entity contract rather than redeclaring it.
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
// foundations/types/index.ts
|
|
75
|
-
import { BaseDealSchema } from '@elevasis/core/entities'
|
|
76
|
-
import { DealSchema } from '@foundation/types/entities' // project-local extended version
|
|
77
|
-
|
|
78
|
-
export const closeDealInputSchema = z.object({
|
|
79
|
-
deal: DealSchema,
|
|
80
|
-
closedReason: z.string()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
export type CloseDealInput = z.infer<typeof closeDealInputSchema>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
`DealSchema` is defined in `foundations/types/entities.ts` by extending `BaseDealSchema` with project-specific metadata. See [../recipes/extend-a-base-entity.md](../recipes/extend-a-base-entity.md) for the pattern.
|
|
87
|
-
|
|
88
|
-
### Steps
|
|
89
|
-
|
|
90
|
-
Each step is a `WorkflowStep` with: `id`, `name`, `description`, `handler`, `inputSchema`, `outputSchema`, and `next`.
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
import { StepType } from '@elevasis/sdk'
|
|
94
|
-
|
|
95
|
-
steps: {
|
|
96
|
-
validate: {
|
|
97
|
-
id: 'validate',
|
|
98
|
-
name: 'Validate Input',
|
|
99
|
-
description: 'Validates recipient and content parameters before sending',
|
|
100
|
-
handler: async (rawInput, context) => {
|
|
101
|
-
const input = rawInput as EmailNotificationInput
|
|
102
|
-
context.logger.info(`[validate] Checking recipient: ${input.recipientEmail}`)
|
|
103
|
-
// ... validation logic ...
|
|
104
|
-
return input // Pass validated data to next step
|
|
105
|
-
},
|
|
106
|
-
inputSchema: emailNotificationInputSchema,
|
|
107
|
-
outputSchema: emailNotificationInputSchema, // Passes full input through to next step
|
|
108
|
-
next: { type: StepType.LINEAR, target: 'notify' }
|
|
109
|
-
},
|
|
110
|
-
notify: {
|
|
111
|
-
id: 'notify',
|
|
112
|
-
name: 'Send Notification',
|
|
113
|
-
handler: async (rawInput, context) => {
|
|
114
|
-
// ...
|
|
115
|
-
return { delivered: true, summary: '...' }
|
|
116
|
-
},
|
|
117
|
-
inputSchema: emailNotificationInputSchema,
|
|
118
|
-
outputSchema: emailNotificationOutputSchema,
|
|
119
|
-
next: null // Terminal step -- ends the workflow
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Key rules:**
|
|
125
|
-
|
|
126
|
-
- `next: null` marks a terminal step.
|
|
127
|
-
- `next: { type: StepType.LINEAR, target: 'stepId' }` chains to another step.
|
|
128
|
-
- Use `context.logger` -- never `console.log`. Platform captures `context.logger.*` only.
|
|
129
|
-
- Cast `rawInput` to your input type: `const input = rawInput as EmailNotificationInput`.
|
|
130
|
-
|
|
131
|
-
### Entrypoint
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
entryPoint: 'validate' // Must match a step id in the steps map
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
The platform starts execution here. For single-step workflows, `entryPoint` points to the only step (e.g., `echo` points to `'echo'`).
|
|
138
|
-
|
|
139
|
-
### Optional: Interface Form
|
|
140
|
-
|
|
141
|
-
Declare `interface.form` to auto-generate an execution form in AI Studio and Command Center:
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
interface: {
|
|
145
|
-
form: {
|
|
146
|
-
title: 'Send Email Notification',
|
|
147
|
-
description: 'Sends a notification email to a user.',
|
|
148
|
-
fields: [
|
|
149
|
-
{ name: 'recipientEmail', label: 'Recipient Email', type: 'text', required: true },
|
|
150
|
-
{ name: 'body', label: 'Body', type: 'text', required: true }
|
|
151
|
-
],
|
|
152
|
-
submitButton: { label: 'Send notification', loadingLabel: 'Sending...' }
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## 2. Adapter Usage
|
|
160
|
-
|
|
161
|
-
Adapters are typed wrappers over `platform.call()`. Import singletons from `@elevasis/sdk/worker`.
|
|
162
|
-
|
|
163
|
-
### notifications
|
|
164
|
-
|
|
165
|
-
Send a platform notification to the current user. `userId` and `organizationId` are injected server-side -- you never supply them.
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
import { notifications } from '@elevasis/sdk/worker'
|
|
169
|
-
|
|
170
|
-
await notifications.create({
|
|
171
|
-
category: 'operations', // Any string: 'operations', 'delivery', 'acquisition', etc.
|
|
172
|
-
title: 'Workflow complete',
|
|
173
|
-
message: 'Your email notification was sent successfully.',
|
|
174
|
-
actionUrl: '/operations' // Optional -- link the user can follow
|
|
175
|
-
})
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Available methods: `create`.
|
|
179
|
-
|
|
180
|
-
### llm
|
|
181
|
-
|
|
182
|
-
Generate text or structured output using a language model.
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
import { llm } from '@elevasis/sdk/worker'
|
|
186
|
-
|
|
187
|
-
const response = await llm.generate({
|
|
188
|
-
provider: 'anthropic', // 'anthropic' | 'openai' | 'openrouter' | 'google'
|
|
189
|
-
model: 'claude-sonnet-4-5',
|
|
190
|
-
messages: [
|
|
191
|
-
{ role: 'user', content: 'Summarize this email body: ' + input.body }
|
|
192
|
-
]
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const summary = response.output as string
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
For structured output, pass a JSON Schema as `responseSchema`:
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
const response = await llm.generate({
|
|
202
|
-
provider: 'anthropic',
|
|
203
|
-
model: 'claude-sonnet-4-5',
|
|
204
|
-
messages: [{ role: 'user', content: 'Extract the key action from: ' + input.body }],
|
|
205
|
-
responseSchema: {
|
|
206
|
-
type: 'object',
|
|
207
|
-
properties: { action: { type: 'string' }, urgency: { type: 'string' } }
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
const { action, urgency } = response.output as { action: string; urgency: string }
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Available methods: `generate`.
|
|
214
|
-
|
|
215
|
-
### storage
|
|
216
|
-
|
|
217
|
-
Upload and retrieve files scoped to the organization. All paths are automatically prefixed with the organization's storage prefix server-side.
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
import { storage } from '@elevasis/sdk/worker'
|
|
221
|
-
|
|
222
|
-
// Upload
|
|
223
|
-
await storage.upload({
|
|
224
|
-
bucket: 'operations',
|
|
225
|
-
path: 'email-logs/2026-04-16/batch-01.json',
|
|
226
|
-
content: Buffer.from(JSON.stringify(logData)).toString('base64'),
|
|
227
|
-
contentType: 'application/json'
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
// Download
|
|
231
|
-
const file = await storage.download({
|
|
232
|
-
bucket: 'operations',
|
|
233
|
-
path: 'email-logs/2026-04-16/batch-01.json'
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
// Signed URL for frontend access
|
|
237
|
-
const { signedUrl } = await storage.createSignedUrl({
|
|
238
|
-
bucket: 'operations',
|
|
239
|
-
path: 'email-logs/2026-04-16/batch-01.json',
|
|
240
|
-
expiresIn: 3600
|
|
241
|
-
})
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Available methods: `upload`, `download`, `createSignedUrl`, `delete`, `list`.
|
|
245
|
-
|
|
246
|
-
### scheduler
|
|
247
|
-
|
|
248
|
-
Schedule future or recurring workflow executions.
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
import { scheduler } from '@elevasis/sdk/worker'
|
|
252
|
-
|
|
253
|
-
const schedule = await scheduler.createSchedule({
|
|
254
|
-
name: 'Daily email digest',
|
|
255
|
-
target: { resourceType: 'workflow', resourceId: 'email-notification' },
|
|
256
|
-
scheduleConfig: {
|
|
257
|
-
type: 'cron',
|
|
258
|
-
expression: '0 9 * * 1-5' // 9am weekdays
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
context.logger.info(`[schedule] Created schedule: ${schedule.id}`)
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Available methods: `createSchedule`, `updateAnchor`, `deleteSchedule`, `getSchedule`, `listSchedules`, `cancelSchedule`, `cancelSchedulesByMetadata`, `cancelScheduleByIdempotencyKey`, `findByIdempotencyKey`, `deleteScheduleByIdempotencyKey`.
|
|
266
|
-
|
|
267
|
-
**Note on other adapters:** Integration adapters (`createResendAdapter`, `createAttioAdapter`, etc.) follow a factory pattern -- bind a credential once, use the instance for all calls:
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
import { createResendAdapter } from '@elevasis/sdk/worker'
|
|
271
|
-
|
|
272
|
-
const resend = createResendAdapter('my-resend-credential')
|
|
273
|
-
await resend.sendEmail({
|
|
274
|
-
to: input.recipientEmail,
|
|
275
|
-
subject: input.subject,
|
|
276
|
-
html: `<p>${input.body}</p>`
|
|
277
|
-
})
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
See `operations/node_modules/@elevasis/sdk/reference/` for the full adapter reference.
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## 3. Trigger Patterns from Frontend
|
|
285
|
-
|
|
286
|
-
### (a) API call via `useApiClient`
|
|
287
|
-
|
|
288
|
-
Use this pattern in React components and hooks. `apiRequest` automatically attaches the auth token and org context.
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
// ui/src/features/notifications/hooks/useSendEmailNotification.ts
|
|
292
|
-
import { useMutation } from '@tanstack/react-query'
|
|
293
|
-
import { useApiClient } from '@/lib/hooks/useApiClient'
|
|
294
|
-
import type { EmailNotificationInput, EmailNotificationOutput } from '@foundation/types'
|
|
295
|
-
|
|
296
|
-
export function useSendEmailNotification() {
|
|
297
|
-
const { apiRequest } = useApiClient()
|
|
298
|
-
|
|
299
|
-
return useMutation({
|
|
300
|
-
mutationFn: async (input: EmailNotificationInput) => {
|
|
301
|
-
return apiRequest<EmailNotificationOutput>('/execute', {
|
|
302
|
-
method: 'POST',
|
|
303
|
-
body: JSON.stringify({
|
|
304
|
-
resourceType: 'workflow',
|
|
305
|
-
resourceId: 'email-notification',
|
|
306
|
-
input
|
|
307
|
-
})
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
Usage in a component:
|
|
315
|
-
|
|
316
|
-
```tsx
|
|
317
|
-
// ui/src/features/notifications/components/SendNotificationButton.tsx
|
|
318
|
-
import { useSendEmailNotification } from '../hooks/useSendEmailNotification'
|
|
319
|
-
|
|
320
|
-
function SendNotificationButton() {
|
|
321
|
-
const { mutate, isPending, isSuccess } = useSendEmailNotification()
|
|
322
|
-
|
|
323
|
-
return (
|
|
324
|
-
<Button
|
|
325
|
-
loading={isPending}
|
|
326
|
-
onClick={() =>
|
|
327
|
-
mutate({
|
|
328
|
-
recipientEmail: 'user@example.com',
|
|
329
|
-
recipientName: 'Jane Smith',
|
|
330
|
-
subject: 'Your request is ready',
|
|
331
|
-
body: 'Hi Jane, your workflow has completed.',
|
|
332
|
-
category: 'operations'
|
|
333
|
-
})
|
|
334
|
-
}
|
|
335
|
-
>
|
|
336
|
-
Send Notification
|
|
337
|
-
</Button>
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
For async execution (long-running workflows), use `/execute-async` instead:
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
return apiRequest<{ executionId: string }>('/execute-async', {
|
|
346
|
-
method: 'POST',
|
|
347
|
-
body: JSON.stringify({
|
|
348
|
-
resourceType: 'workflow',
|
|
349
|
-
resourceId: 'email-notification',
|
|
350
|
-
input
|
|
351
|
-
})
|
|
352
|
-
})
|
|
353
|
-
// Poll /executions/email-notification/:executionId for status
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### (b) Direct SDK dispatch (CLI or scripts)
|
|
357
|
-
|
|
358
|
-
From the project root, use the platform CLI for manual invocations, testing, and scripting:
|
|
359
|
-
|
|
360
|
-
```bash
|
|
361
|
-
# Describe the schema before executing
|
|
362
|
-
pnpm exec elevasis describe Elevasis/email-notification
|
|
363
|
-
|
|
364
|
-
# Execute synchronously
|
|
365
|
-
pnpm exec elevasis exec Elevasis/email-notification --input '{
|
|
366
|
-
"recipientEmail": "user@example.com",
|
|
367
|
-
"recipientName": "Jane Smith",
|
|
368
|
-
"subject": "Hello",
|
|
369
|
-
"body": "Hi Jane, this is a test notification."
|
|
370
|
-
}'
|
|
371
|
-
|
|
372
|
-
# Execute asynchronously (for long-running workflows)
|
|
373
|
-
pnpm exec elevasis exec Elevasis/email-notification --async --input '{...}'
|
|
374
|
-
|
|
375
|
-
# View a specific execution
|
|
376
|
-
pnpm exec elevasis execution Elevasis/email-notification <executionId>
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
The `--prod` flag targets `https://api.elevasis.io` and goes **before** the command:
|
|
380
|
-
|
|
381
|
-
```bash
|
|
382
|
-
pnpm exec elevasis --prod exec Elevasis/email-notification --input '{...}'
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## 4. Registry Pattern
|
|
388
|
-
|
|
389
|
-
Workflows are discovered through `operations/src/index.ts`, which exports a `DeploymentSpec` as its default export.
|
|
390
|
-
|
|
391
|
-
**Pattern:** each feature group in `operations/src/` has its own exports barrel. The top-level spec spreads all groups:
|
|
392
|
-
|
|
393
|
-
```
|
|
394
|
-
operations/src/
|
|
395
|
-
index.ts # Top-level DeploymentSpec -- never add workflows here directly
|
|
396
|
-
example/
|
|
397
|
-
index.ts # export const workflows = [echo]; export const agents = []
|
|
398
|
-
echo.ts # WorkflowDefinition for 'echo'
|
|
399
|
-
email-notification/
|
|
400
|
-
exports.ts # export const workflows = [emailNotification]; export const agents = []
|
|
401
|
-
index.ts # WorkflowDefinition for 'email-notification'
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
Top-level registry (`operations/src/index.ts`):
|
|
405
|
-
|
|
406
|
-
```typescript
|
|
407
|
-
import type { DeploymentSpec } from '@elevasis/sdk'
|
|
408
|
-
import * as example from './example/index.js'
|
|
409
|
-
import * as emailNotification from './email-notification/exports.js'
|
|
410
|
-
|
|
411
|
-
const org: DeploymentSpec = {
|
|
412
|
-
version: '0.1.0',
|
|
413
|
-
workflows: [...example.workflows, ...emailNotification.workflows],
|
|
414
|
-
agents: [...example.agents, ...emailNotification.agents]
|
|
415
|
-
}
|
|
416
|
-
export default org
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
Feature group barrel (e.g., `email-notification/exports.ts`):
|
|
420
|
-
|
|
421
|
-
```typescript
|
|
422
|
-
import { emailNotification } from './index.js'
|
|
423
|
-
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
424
|
-
|
|
425
|
-
export const workflows: WorkflowDefinition[] = [emailNotification]
|
|
426
|
-
export const agents: never[] = []
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
**Adding a new workflow:**
|
|
430
|
-
|
|
431
|
-
1. Create `operations/src/<feature>/index.ts` with the `WorkflowDefinition`.
|
|
432
|
-
2. Create `operations/src/<feature>/exports.ts` with `workflows` and `agents` arrays.
|
|
433
|
-
3. Import the group barrel in `operations/src/index.ts` and spread into `workflows`/`agents`.
|
|
434
|
-
4. Run `pnpm -C operations check` to validate, then `pnpm -C operations deploy` to publish.
|
|
435
|
-
|
|
436
|
-
**Note:** Use `.js` extensions in imports even though the source is TypeScript. The TypeScript compiler and esbuild bundler both require this for ESM interoperability.
|
|
1
|
+
---
|
|
2
|
+
title: Workflow Recipes
|
|
3
|
+
description: Anatomy of a workflow, adapter usage, and trigger patterns -- runnable email-notification example replaces the trivial echo workflow. Resolves eval score 2/5 on workflow authoring.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Workflow Recipes
|
|
7
|
+
|
|
8
|
+
`🟢 Stable` -- Use these patterns as-is against `@elevasis/sdk ^1.4.0`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Anatomy of a Workflow
|
|
13
|
+
|
|
14
|
+
Every workflow is a `WorkflowDefinition` object with four top-level keys: `config`, `contract`, `steps`, and `entryPoint`. The `email-notification` workflow at `operations/src/email-notification/index.ts` is used as the running example throughout this section.
|
|
15
|
+
|
|
16
|
+
### Config
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
config: {
|
|
20
|
+
resourceId: 'email-notification', // Unique ID -- used in API calls and CLI
|
|
21
|
+
name: 'Email Notification', // Human-readable display name
|
|
22
|
+
type: 'workflow', // Always 'workflow' (vs 'agent')
|
|
23
|
+
description: 'Sends a notification email to a user...',
|
|
24
|
+
version: '1.0.0', // Bump on contract changes
|
|
25
|
+
status: 'dev' // 'dev' or 'prod'
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- `resourceId` is the stable identifier used by `pnpm exec elevasis exec` and `/execute` API calls.
|
|
30
|
+
- Bump `version` whenever you change `contract.inputSchema` or `contract.outputSchema`.
|
|
31
|
+
|
|
32
|
+
### Contract
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// foundations/types/index.ts -- shared with frontend
|
|
36
|
+
export const emailNotificationInputSchema = z.object({
|
|
37
|
+
recipientEmail: z.string().email(),
|
|
38
|
+
recipientName: z.string().min(1),
|
|
39
|
+
subject: z.string().min(1).max(200),
|
|
40
|
+
body: z.string().min(1).max(10000),
|
|
41
|
+
category: z.string().min(1).default('operations'),
|
|
42
|
+
actionUrl: z.string().url().optional()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const emailNotificationOutputSchema = z.object({
|
|
46
|
+
delivered: z.boolean(),
|
|
47
|
+
notificationId: z.string().optional(),
|
|
48
|
+
summary: z.string()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export type EmailNotificationInput = z.infer<typeof emailNotificationInputSchema>
|
|
52
|
+
export type EmailNotificationOutput = z.infer<typeof emailNotificationOutputSchema>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// operations/src/email-notification/index.ts
|
|
57
|
+
import { emailNotificationInputSchema, emailNotificationOutputSchema } from '@foundation/types'
|
|
58
|
+
|
|
59
|
+
contract: {
|
|
60
|
+
inputSchema: emailNotificationInputSchema,
|
|
61
|
+
outputSchema: emailNotificationOutputSchema
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Rules:**
|
|
66
|
+
|
|
67
|
+
- Define schemas in `foundations/types/index.ts` -- never inline Zod schemas in workflow files.
|
|
68
|
+
- Both runtimes (frontend and platform) import from `@foundation/types`, keeping validation consistent.
|
|
69
|
+
- `@foundation/types` resolves to `foundations/types/index.ts` via the tsconfig path alias.
|
|
70
|
+
|
|
71
|
+
**Entity-backed workflows:** for workflows that operate on a domain entity, reference the entity contract rather than redeclaring it.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// foundations/types/index.ts
|
|
75
|
+
import { BaseDealSchema } from '@elevasis/core/entities'
|
|
76
|
+
import { DealSchema } from '@foundation/types/entities' // project-local extended version
|
|
77
|
+
|
|
78
|
+
export const closeDealInputSchema = z.object({
|
|
79
|
+
deal: DealSchema,
|
|
80
|
+
closedReason: z.string()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export type CloseDealInput = z.infer<typeof closeDealInputSchema>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`DealSchema` is defined in `foundations/types/entities.ts` by extending `BaseDealSchema` with project-specific metadata. See [../recipes/extend-a-base-entity.md](../recipes/extend-a-base-entity.md) for the pattern.
|
|
87
|
+
|
|
88
|
+
### Steps
|
|
89
|
+
|
|
90
|
+
Each step is a `WorkflowStep` with: `id`, `name`, `description`, `handler`, `inputSchema`, `outputSchema`, and `next`.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { StepType } from '@elevasis/sdk'
|
|
94
|
+
|
|
95
|
+
steps: {
|
|
96
|
+
validate: {
|
|
97
|
+
id: 'validate',
|
|
98
|
+
name: 'Validate Input',
|
|
99
|
+
description: 'Validates recipient and content parameters before sending',
|
|
100
|
+
handler: async (rawInput, context) => {
|
|
101
|
+
const input = rawInput as EmailNotificationInput
|
|
102
|
+
context.logger.info(`[validate] Checking recipient: ${input.recipientEmail}`)
|
|
103
|
+
// ... validation logic ...
|
|
104
|
+
return input // Pass validated data to next step
|
|
105
|
+
},
|
|
106
|
+
inputSchema: emailNotificationInputSchema,
|
|
107
|
+
outputSchema: emailNotificationInputSchema, // Passes full input through to next step
|
|
108
|
+
next: { type: StepType.LINEAR, target: 'notify' }
|
|
109
|
+
},
|
|
110
|
+
notify: {
|
|
111
|
+
id: 'notify',
|
|
112
|
+
name: 'Send Notification',
|
|
113
|
+
handler: async (rawInput, context) => {
|
|
114
|
+
// ...
|
|
115
|
+
return { delivered: true, summary: '...' }
|
|
116
|
+
},
|
|
117
|
+
inputSchema: emailNotificationInputSchema,
|
|
118
|
+
outputSchema: emailNotificationOutputSchema,
|
|
119
|
+
next: null // Terminal step -- ends the workflow
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Key rules:**
|
|
125
|
+
|
|
126
|
+
- `next: null` marks a terminal step.
|
|
127
|
+
- `next: { type: StepType.LINEAR, target: 'stepId' }` chains to another step.
|
|
128
|
+
- Use `context.logger` -- never `console.log`. Platform captures `context.logger.*` only.
|
|
129
|
+
- Cast `rawInput` to your input type: `const input = rawInput as EmailNotificationInput`.
|
|
130
|
+
|
|
131
|
+
### Entrypoint
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
entryPoint: 'validate' // Must match a step id in the steps map
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The platform starts execution here. For single-step workflows, `entryPoint` points to the only step (e.g., `echo` points to `'echo'`).
|
|
138
|
+
|
|
139
|
+
### Optional: Interface Form
|
|
140
|
+
|
|
141
|
+
Declare `interface.form` to auto-generate an execution form in AI Studio and Command Center:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
interface: {
|
|
145
|
+
form: {
|
|
146
|
+
title: 'Send Email Notification',
|
|
147
|
+
description: 'Sends a notification email to a user.',
|
|
148
|
+
fields: [
|
|
149
|
+
{ name: 'recipientEmail', label: 'Recipient Email', type: 'text', required: true },
|
|
150
|
+
{ name: 'body', label: 'Body', type: 'text', required: true }
|
|
151
|
+
],
|
|
152
|
+
submitButton: { label: 'Send notification', loadingLabel: 'Sending...' }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 2. Adapter Usage
|
|
160
|
+
|
|
161
|
+
Adapters are typed wrappers over `platform.call()`. Import singletons from `@elevasis/sdk/worker`.
|
|
162
|
+
|
|
163
|
+
### notifications
|
|
164
|
+
|
|
165
|
+
Send a platform notification to the current user. `userId` and `organizationId` are injected server-side -- you never supply them.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { notifications } from '@elevasis/sdk/worker'
|
|
169
|
+
|
|
170
|
+
await notifications.create({
|
|
171
|
+
category: 'operations', // Any string: 'operations', 'delivery', 'acquisition', etc.
|
|
172
|
+
title: 'Workflow complete',
|
|
173
|
+
message: 'Your email notification was sent successfully.',
|
|
174
|
+
actionUrl: '/operations' // Optional -- link the user can follow
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Available methods: `create`.
|
|
179
|
+
|
|
180
|
+
### llm
|
|
181
|
+
|
|
182
|
+
Generate text or structured output using a language model.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { llm } from '@elevasis/sdk/worker'
|
|
186
|
+
|
|
187
|
+
const response = await llm.generate({
|
|
188
|
+
provider: 'anthropic', // 'anthropic' | 'openai' | 'openrouter' | 'google'
|
|
189
|
+
model: 'claude-sonnet-4-5',
|
|
190
|
+
messages: [
|
|
191
|
+
{ role: 'user', content: 'Summarize this email body: ' + input.body }
|
|
192
|
+
]
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const summary = response.output as string
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For structured output, pass a JSON Schema as `responseSchema`:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const response = await llm.generate({
|
|
202
|
+
provider: 'anthropic',
|
|
203
|
+
model: 'claude-sonnet-4-5',
|
|
204
|
+
messages: [{ role: 'user', content: 'Extract the key action from: ' + input.body }],
|
|
205
|
+
responseSchema: {
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: { action: { type: 'string' }, urgency: { type: 'string' } }
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
const { action, urgency } = response.output as { action: string; urgency: string }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Available methods: `generate`.
|
|
214
|
+
|
|
215
|
+
### storage
|
|
216
|
+
|
|
217
|
+
Upload and retrieve files scoped to the organization. All paths are automatically prefixed with the organization's storage prefix server-side.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { storage } from '@elevasis/sdk/worker'
|
|
221
|
+
|
|
222
|
+
// Upload
|
|
223
|
+
await storage.upload({
|
|
224
|
+
bucket: 'operations',
|
|
225
|
+
path: 'email-logs/2026-04-16/batch-01.json',
|
|
226
|
+
content: Buffer.from(JSON.stringify(logData)).toString('base64'),
|
|
227
|
+
contentType: 'application/json'
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Download
|
|
231
|
+
const file = await storage.download({
|
|
232
|
+
bucket: 'operations',
|
|
233
|
+
path: 'email-logs/2026-04-16/batch-01.json'
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Signed URL for frontend access
|
|
237
|
+
const { signedUrl } = await storage.createSignedUrl({
|
|
238
|
+
bucket: 'operations',
|
|
239
|
+
path: 'email-logs/2026-04-16/batch-01.json',
|
|
240
|
+
expiresIn: 3600
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Available methods: `upload`, `download`, `createSignedUrl`, `delete`, `list`.
|
|
245
|
+
|
|
246
|
+
### scheduler
|
|
247
|
+
|
|
248
|
+
Schedule future or recurring workflow executions.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { scheduler } from '@elevasis/sdk/worker'
|
|
252
|
+
|
|
253
|
+
const schedule = await scheduler.createSchedule({
|
|
254
|
+
name: 'Daily email digest',
|
|
255
|
+
target: { resourceType: 'workflow', resourceId: 'email-notification' },
|
|
256
|
+
scheduleConfig: {
|
|
257
|
+
type: 'cron',
|
|
258
|
+
expression: '0 9 * * 1-5' // 9am weekdays
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
context.logger.info(`[schedule] Created schedule: ${schedule.id}`)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Available methods: `createSchedule`, `updateAnchor`, `deleteSchedule`, `getSchedule`, `listSchedules`, `cancelSchedule`, `cancelSchedulesByMetadata`, `cancelScheduleByIdempotencyKey`, `findByIdempotencyKey`, `deleteScheduleByIdempotencyKey`.
|
|
266
|
+
|
|
267
|
+
**Note on other adapters:** Integration adapters (`createResendAdapter`, `createAttioAdapter`, etc.) follow a factory pattern -- bind a credential once, use the instance for all calls:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { createResendAdapter } from '@elevasis/sdk/worker'
|
|
271
|
+
|
|
272
|
+
const resend = createResendAdapter('my-resend-credential')
|
|
273
|
+
await resend.sendEmail({
|
|
274
|
+
to: input.recipientEmail,
|
|
275
|
+
subject: input.subject,
|
|
276
|
+
html: `<p>${input.body}</p>`
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
See `operations/node_modules/@elevasis/sdk/reference/` for the full adapter reference.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 3. Trigger Patterns from Frontend
|
|
285
|
+
|
|
286
|
+
### (a) API call via `useApiClient`
|
|
287
|
+
|
|
288
|
+
Use this pattern in React components and hooks. `apiRequest` automatically attaches the auth token and org context.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// ui/src/features/notifications/hooks/useSendEmailNotification.ts
|
|
292
|
+
import { useMutation } from '@tanstack/react-query'
|
|
293
|
+
import { useApiClient } from '@/lib/hooks/useApiClient'
|
|
294
|
+
import type { EmailNotificationInput, EmailNotificationOutput } from '@foundation/types'
|
|
295
|
+
|
|
296
|
+
export function useSendEmailNotification() {
|
|
297
|
+
const { apiRequest } = useApiClient()
|
|
298
|
+
|
|
299
|
+
return useMutation({
|
|
300
|
+
mutationFn: async (input: EmailNotificationInput) => {
|
|
301
|
+
return apiRequest<EmailNotificationOutput>('/execute', {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
body: JSON.stringify({
|
|
304
|
+
resourceType: 'workflow',
|
|
305
|
+
resourceId: 'email-notification',
|
|
306
|
+
input
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Usage in a component:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
// ui/src/features/notifications/components/SendNotificationButton.tsx
|
|
318
|
+
import { useSendEmailNotification } from '../hooks/useSendEmailNotification'
|
|
319
|
+
|
|
320
|
+
function SendNotificationButton() {
|
|
321
|
+
const { mutate, isPending, isSuccess } = useSendEmailNotification()
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<Button
|
|
325
|
+
loading={isPending}
|
|
326
|
+
onClick={() =>
|
|
327
|
+
mutate({
|
|
328
|
+
recipientEmail: 'user@example.com',
|
|
329
|
+
recipientName: 'Jane Smith',
|
|
330
|
+
subject: 'Your request is ready',
|
|
331
|
+
body: 'Hi Jane, your workflow has completed.',
|
|
332
|
+
category: 'operations'
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
>
|
|
336
|
+
Send Notification
|
|
337
|
+
</Button>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
For async execution (long-running workflows), use `/execute-async` instead:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
return apiRequest<{ executionId: string }>('/execute-async', {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
body: JSON.stringify({
|
|
348
|
+
resourceType: 'workflow',
|
|
349
|
+
resourceId: 'email-notification',
|
|
350
|
+
input
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
// Poll /executions/email-notification/:executionId for status
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### (b) Direct SDK dispatch (CLI or scripts)
|
|
357
|
+
|
|
358
|
+
From the project root, use the platform CLI for manual invocations, testing, and scripting:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Describe the schema before executing
|
|
362
|
+
pnpm exec elevasis describe Elevasis/email-notification
|
|
363
|
+
|
|
364
|
+
# Execute synchronously
|
|
365
|
+
pnpm exec elevasis exec Elevasis/email-notification --input '{
|
|
366
|
+
"recipientEmail": "user@example.com",
|
|
367
|
+
"recipientName": "Jane Smith",
|
|
368
|
+
"subject": "Hello",
|
|
369
|
+
"body": "Hi Jane, this is a test notification."
|
|
370
|
+
}'
|
|
371
|
+
|
|
372
|
+
# Execute asynchronously (for long-running workflows)
|
|
373
|
+
pnpm exec elevasis exec Elevasis/email-notification --async --input '{...}'
|
|
374
|
+
|
|
375
|
+
# View a specific execution
|
|
376
|
+
pnpm exec elevasis execution Elevasis/email-notification <executionId>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The `--prod` flag targets `https://api.elevasis.io` and goes **before** the command:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
pnpm exec elevasis --prod exec Elevasis/email-notification --input '{...}'
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## 4. Registry Pattern
|
|
388
|
+
|
|
389
|
+
Workflows are discovered through `operations/src/index.ts`, which exports a `DeploymentSpec` as its default export.
|
|
390
|
+
|
|
391
|
+
**Pattern:** each feature group in `operations/src/` has its own exports barrel. The top-level spec spreads all groups:
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
operations/src/
|
|
395
|
+
index.ts # Top-level DeploymentSpec -- never add workflows here directly
|
|
396
|
+
example/
|
|
397
|
+
index.ts # export const workflows = [echo]; export const agents = []
|
|
398
|
+
echo.ts # WorkflowDefinition for 'echo'
|
|
399
|
+
email-notification/
|
|
400
|
+
exports.ts # export const workflows = [emailNotification]; export const agents = []
|
|
401
|
+
index.ts # WorkflowDefinition for 'email-notification'
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Top-level registry (`operations/src/index.ts`):
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import type { DeploymentSpec } from '@elevasis/sdk'
|
|
408
|
+
import * as example from './example/index.js'
|
|
409
|
+
import * as emailNotification from './email-notification/exports.js'
|
|
410
|
+
|
|
411
|
+
const org: DeploymentSpec = {
|
|
412
|
+
version: '0.1.0',
|
|
413
|
+
workflows: [...example.workflows, ...emailNotification.workflows],
|
|
414
|
+
agents: [...example.agents, ...emailNotification.agents]
|
|
415
|
+
}
|
|
416
|
+
export default org
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Feature group barrel (e.g., `email-notification/exports.ts`):
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { emailNotification } from './index.js'
|
|
423
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
424
|
+
|
|
425
|
+
export const workflows: WorkflowDefinition[] = [emailNotification]
|
|
426
|
+
export const agents: never[] = []
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Adding a new workflow:**
|
|
430
|
+
|
|
431
|
+
1. Create `operations/src/<feature>/index.ts` with the `WorkflowDefinition`.
|
|
432
|
+
2. Create `operations/src/<feature>/exports.ts` with `workflows` and `agents` arrays.
|
|
433
|
+
3. Import the group barrel in `operations/src/index.ts` and spread into `workflows`/`agents`.
|
|
434
|
+
4. Run `pnpm -C operations check` to validate, then `pnpm -C operations deploy` to publish.
|
|
435
|
+
|
|
436
|
+
**Note:** Use `.js` extensions in imports even though the source is TypeScript. The TypeScript compiler and esbuild bundler both require this for ESM interoperability.
|