@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.
Files changed (62) hide show
  1. package/dist/cli.cjs +289 -105
  2. package/dist/index.d.ts +90 -39
  3. package/dist/types/worker/adapters/lead.d.ts +1 -1
  4. package/dist/worker/index.js +2 -0
  5. package/package.json +2 -2
  6. package/reference/_navigation.md +7 -1
  7. package/reference/_reference-manifest.json +14 -0
  8. package/reference/claude-config/logs/scaffold-registry-reminder.log +3 -0
  9. package/reference/claude-config/rules/agent-start-here.md +254 -254
  10. package/reference/claude-config/rules/frontend.md +43 -43
  11. package/reference/claude-config/rules/operations.md +64 -64
  12. package/reference/claude-config/rules/organization-model.md +42 -43
  13. package/reference/claude-config/rules/organization-os.md +107 -107
  14. package/reference/claude-config/rules/shared-types.md +2 -2
  15. package/reference/claude-config/rules/task-tracking.md +1 -1
  16. package/reference/claude-config/rules/ui.md +202 -202
  17. package/reference/claude-config/rules/vibe.md +202 -202
  18. package/reference/claude-config/skills/configure/SKILL.md +98 -98
  19. package/reference/claude-config/skills/configure/operations/codify-level-a.md +100 -100
  20. package/reference/claude-config/skills/configure/operations/codify-level-b.md +158 -158
  21. package/reference/claude-config/skills/configure/operations/customers.md +150 -150
  22. package/reference/claude-config/skills/configure/operations/features.md +162 -162
  23. package/reference/claude-config/skills/configure/operations/goals.md +147 -147
  24. package/reference/claude-config/skills/configure/operations/identity.md +133 -133
  25. package/reference/claude-config/skills/configure/operations/labels.md +128 -128
  26. package/reference/claude-config/skills/configure/operations/offerings.md +159 -159
  27. package/reference/claude-config/skills/configure/operations/roles.md +153 -153
  28. package/reference/claude-config/skills/configure/operations/techStack.md +139 -139
  29. package/reference/claude-config/skills/explore/SKILL.md +78 -78
  30. package/reference/claude-config/skills/git-sync/SKILL.md +126 -0
  31. package/reference/claude-config/skills/save/SKILL.md +183 -183
  32. package/reference/claude-config/skills/setup/SKILL.md +275 -275
  33. package/reference/claude-config/skills/sync/SKILL.md +10 -44
  34. package/reference/claude-config/sync-notes/2026-04-22-git-sync-and-sync-notes.md +27 -0
  35. package/reference/claude-config/sync-notes/2026-04-22-lead-gen-deliverability-removal.md +30 -0
  36. package/reference/claude-config/sync-notes/2026-04-24-ui-consolidation-and-sdk-cli-train.md +86 -0
  37. package/reference/claude-config/sync-notes/README.md +43 -0
  38. package/reference/deployment/index.mdx +42 -7
  39. package/reference/examples/organization-model.ts +689 -0
  40. package/reference/index.mdx +6 -5
  41. package/reference/packages/core/src/README.md +39 -36
  42. package/reference/packages/core/src/business/README.md +52 -52
  43. package/reference/packages/core/src/organization-model/README.md +97 -97
  44. package/reference/packages/core/src/test-utils/README.md +42 -0
  45. package/reference/scaffold/core/organization-graph.mdx +272 -272
  46. package/reference/scaffold/core/organization-model.mdx +320 -320
  47. package/reference/scaffold/index.mdx +64 -64
  48. package/reference/scaffold/operations/propagation-pipeline.md +125 -104
  49. package/reference/scaffold/operations/scaffold-maintenance.md +122 -122
  50. package/reference/scaffold/operations/workflow-recipes.md +436 -436
  51. package/reference/scaffold/recipes/add-a-feature.md +158 -158
  52. package/reference/scaffold/recipes/add-a-resource.md +158 -158
  53. package/reference/scaffold/recipes/customize-organization-model.md +400 -400
  54. package/reference/scaffold/recipes/extend-a-base-entity.md +140 -140
  55. package/reference/scaffold/recipes/gate-by-feature-or-admin.md +158 -158
  56. package/reference/scaffold/recipes/index.md +32 -32
  57. package/reference/scaffold/reference/contracts.md +608 -607
  58. package/reference/scaffold/reference/feature-registry.md +2 -0
  59. package/reference/scaffold/reference/glossary.md +105 -105
  60. package/reference/scaffold/ui/composition-extensibility.mdx +1 -1
  61. package/reference/scaffold/ui/feature-flags-and-gating.md +1 -1
  62. 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.